Requests in Elm: Just fill in the blanks

While working a new project I noticed once again that a lot of boilerplate was building up in the module I use for HTTP requests. It turns out that there is a nice way of dealing with such code bloat using parametrized type aliases. The code for this solution is packaged in a module Request, given later on (see, “The code”). We use a data structure RequestParameter to record the information needed to make a request, as in this example:

module RequestData exposing(signupParameters, etc)import Request exposing (RequestParameters)signupParameters : Model -> RequestParameters VerifiedVoter
signupParameters model =
{ api = "https://YallComeVoteNow.io"
, route = "/voters"
, payload = Data.encodeVoter model
, tagger = CreateVoter
, token = ""
, decoder = Data.verifiedVoterDecoder
, method = HB.post
}

Once signupParameters is defined, a request can be made like this:

Request.doRequest <| signupParameters model

The payload field can be any Json.Encode.Value, including Json.Encode.null. As for the resourceType, that is the type that the decoder will produce. (See the decoder field.) The token field can be " if no authentication is needed for the route in question. Otherwise, it should be a valid JSON Web Token, perhaps model.token. Notice that you have to define the HTTP method to be used — get, post, etc. The Request module imports HttpBuilder as HB, so we use HB.get, HB.post, etc.

The code for the Request module is listed below. Other than Msg , its only dependency is on core packages, plus Luke Westby’s elm-http-builder.

I’ve used this module in the current project I’m working on — a voter engagement app. At present it handles seven requests, all defined in module RequestData. With this setup it is routine to set up new requests. Just fill in the blanks.

The code

module Request exposing (doRequest, RequestParameters)import Http exposing (send)
import HttpBuilder as HB
import Json.Encode as Encode
import Json.Decode exposing (Decoder)
import Types exposing (Msg)
type alias RequestParameters resourceType =
{ api : String
, route : String
, payload : Encode.Value
, tagger: Result Http.Error resourceType -> Msg
, token : String
, decoder : Decoder resourceType
, method : String -> HB.RequestBuilder ()
}
type alias RequestPacket resourceType =
RequestParameters resourceType -> HB.RequestBuilder resourceType
putHeader : String -> HB.RequestBuilder a -> HB.RequestBuilder a
putHeader token =
if token == "" then
identity
else
HB.withHeader "Authorization" ("Bearer " ++ token)
setupRequest : RequestParameters resourceType -> RequestPacket resourceType
setupRequest requestData =
\rqData ->
(rqData.method (rqData.api ++ rqData.route)
|> HB.withJsonBody rqData.payload
|> putHeader rqData.token
|> HB.withExpect (Http.expectJson rqData.decoder)
)
doRequest : RequestParameters resourceType -> Cmd Msg
doRequest requestData =
let
request =
setupRequest requestData
in
Http.send requestData.tagger (request requestData |> HB.toRequest)

Notes (1) Many thanks to Ilias van Peer for clarifying comments. (2) I welcome suggestions on how to improve this module I’m on the Elm Slack as @jxxcarlson.

Written by

jxxcarlson on elm slack, http://jxxcarlson.github.io

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store