Chaining HTTP Requests in Elm – An Example

I recently attended elm-conf (videos of the elm-conf presentations), which was hosted at Strange Loop on its preconference day. I’d been meaning to play around with Elm for years and was finally sparked to do so to prepare for the conference.

As I often do when trying to learn a new language, I came up with a little side project that I could implement in Elm. The project is a very basic reporting app that would pull data from a REST API.

I ran into trouble when I wanted to make a request to the API. This was immediately followed by another request that makes use of the data returned from the first. There are plenty of examples out there showing how to make a single HTTP request in Elm—but I couldn’t find anything that showed how to chain multiple requests together.

It turns out, it’s really easy to do! To help the next developer who wants to chain HTTP requests together in Elm, I’m going to walk through an example here.

(Note: The code snippets might say they’re Haskell, but they’re really Elm. Our snippet highlighter doesn’t recognize Elm yet, and the Haskell highlighter does a pretty good job.)

HTTP.get

The README for the evancz/elm-http package has a good example of making a single HTTP request:


import Http
import Json.Decode as Json exposing ((:=))
import Task exposing (..)

lookupZipCode : String -> Task Http.Error (List String)
lookupZipCode query =
    Http.get places ("http://api.zippopotam.us/us/" ++ query)


places : Json.Decoder (List String)
places =
  let place =
        Json.object2 (\city state -> city ++ ", " ++ state)
          ("place name" := Json.string)
          ("state" := Json.string)
  in
      "places" := Json.list place

Elm Architecture Cmd

In order to make an HTTP request within The Elm Architecture, you need to use Task.perform. The Tasks section of the Elm Tutorial provides the following example:


module Main exposing (..)

import Html exposing (Html, div, button, text)
import Html.Events exposing (onClick)
import Html.App
import Http
import Task exposing (Task)
import Json.Decode as Decode


-- MODEL


type alias Model =
    String


init : ( Model, Cmd Msg )
init =
    ( "", Cmd.none )



-- MESSAGES


type Msg
    = Fetch
    | FetchSuccess String
    | FetchError Http.Error



-- VIEW


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Fetch ] [ text "Fetch" ]
        , text model
        ]


decode : Decode.Decoder String
decode =
    Decode.at [ "name" ] Decode.string


url : String
url =
    "http://swapi.co/api/planets/1/?format=json"


fetchTask : Task Http.Error String
fetchTask =
    Http.get decode url


fetchCmd : Cmd Msg
fetchCmd =
    Task.perform FetchError FetchSuccess fetchTask



-- UPDATE


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Fetch ->
            ( model, fetchCmd )

        FetchSuccess name ->
            ( name, Cmd.none )

        FetchError error ->
            ( toString error, Cmd.none )



-- MAIN


main : Program Never
main =
    Html.App.program
        { init = init
        , view = view
        , update = update
        , subscriptions = (always Sub.none)
        }

In short, you call Task.perform, passing it a constructor for the failure message, a constructor for the success message, and the task to run. Cutting out everything else, here’s (a slightly modified version of) the Task.perform on a single line:


Task.perform FetchError FetchSuccess (Http.get decode "http://swapi.co/api/planets/1/?format=json")

Chaining Tasks with `andThen`

The documentation for andThen states:

This is useful for chaining tasks together. Maybe you need to get a user from your servers and then lookup their picture once you know their name.

Exactly what I wanted to do!

Once I came across andThen, I still struggled a bit thinking that I was supposed to be chaining two Task.perform results together. It wasn’t until I finally understood that an Http.get call returns a Task, and so does an andThen call, that I realized I wanted to andThen the two Http.get calls together and pass that result to the Task.perform.

So here it is. A very simple (once I finally understood what to do) example of chaining two HTTP requests together in a single command.



decodePlanet : Decode.Decoder String
decodePlanet =
    Decode.at [ "name" ] Decode.string


decodePerson : Decode.Decoder String
decodePerson =
    Decode.at [ "homeworld" ] Decode.string


fetchPlanet : String -> Task Http.Error String
fetchPlanet url =
    Http.get decodePlanet url


fetchPerson : Task Http.Error String
fetchPerson =
    Http.get decodePerson "http://swapi.co/api/people/1"


fetchCmd : Cmd Msg
fetchCmd =
    Task.perform FetchError FetchSuccess (fetchPerson `andThen` fetchPlanet)

The trick is to call Task.perform once, passing a Task to it that’s a sequence of the first Http.get (fetchPerson) followed by the second Http.get (fetchPlanet). It’s worth noting that the fetchPlanet function takes a String argument, which is the URL parsed from the first request.

Hopefully, this will help someone stuck on the same problem I was having.