How to get data from APIs using Elm

This is the fourth post in a series of posts about Elm for JavaScript developers. If you're a JavaScript developer, and you want to know more about Elm, stay tuned to this series of posts or sign up for DailyDrip and check out all the episodes we have in our room topic.

Here is what we already see:

You will probably want to work with APIs at some point. How can you get data from an API into your Elm application?

What we will build

We will build a simple page that shows the current Bitcoin price in USD. We will use the coindesk api to look up the bitcoin price.

Here is our API endpoint: http://api.coindesk.com/v1/bpi/currentprice.json.

And our JSON looks like:

{
    time: {
        updated: "Jun 27, 2017 13:25:00 UTC",
        updatedISO: "2017-06-27T13:25:00+00:00",
        updateduk: "Jun 27, 2017 at 14:25 BST"
},
disclaimer: "This data was produced from the CoinDesk Bitcoin Price Index (USD).
Non-USD currency data converted using hourly conversion rate from
openexchangerates.org",
chartName: "Bitcoin",
bpi: {
    USD: {
            code: "USD",
            symbol: "$",
            rate: "2,415.8175",
            description: "United States Dollar",
            rate_float: 2415.8175
    },
    GBP: {
            code: "GBP",
            symbol: "£",
            rate: "1,893.1433",
            description: "British Pound Sterling",
            rate_float: 1893.1433
    },
    EUR: {
            code: "EUR",
            symbol: "€",
            rate: "2,142.2407",
            description: "Euro",
            rate_float: 2142.2407
        }
    }
}

In our JSON structure, we have the time and the current conversion for USD, EUR and GBP.

Creating our Elm app

To create an Elm project

mkdir bitcoin-price
cd bitcoin-price

And then we can create our Elm code.

vim Main.elm

Let's start creating a Hello World app, using the Elm Html package.

module Hello exposing (..)

import Html exposing (text)


main =
    text "Hello World"

To run it, you can use elm-reactor and this will make our Elm app works in the port 8000.

➜  bitcoin-price elm-reactor
elm-reactor 0.18.0
Listening on http://localhost:8000

You should see Hello World in your screen.

Now we have our app working. Let's try to get the data from our API!

Using Decoders

One of the cool things about Elm is there way it handles JSON decoders. Elm provides a clean, declarative way to specify how to turn json into typed data.

Personally, I'm using this approach and I posted about it before.

If we check the Elm documentation - and you always should - we can see the function at. This is used to specify a path in the json structure, and specify the way to parse the data you find there.

decodeContent : Decoder String
decodeContent =
    at [ "bpi", "USD", "rate" ] string

In this function, we are getting the value at the bpi key, then inside that we find the USD key, and then the rate key. We then parse this as a string. Our decodeContent is a Json decoder that decided a String. This string will be our bitcoin price.

In our case, our model will be just an Elm record that contains the currentPrice, and it is a string.

type alias Model =
    { currentPrice : String
    }

Our Elm Program

We are going to use Html.program, because we need to have side effects, commands. The commands will be our Http requests.

Here we will have an init function, update, our view and subscriptions. We will not have subscriptions for the moment, this will be covered in another blog post.

main : Program Never Model Msg
main =
    Html.program
        { init = init
        , update = update
        , view = view
        , subscriptions = always Sub.none
        }

Our Messages

Messages in the Elm architecture describe all of the things that can happen in our app. In our case, we will have three messages:

  • GotBitcoinPrice: We will send this message when we just got the return from our api, and we will update our model.
  • GetBitcoinPrice: We will send this message when we want to get the API request.
  • NoOp: When we will do nothing.

Let's define our Messages.

type Msg
    = GotBitcoinPrice (Result Http.Error String)
    | GetBitcoinPrice
    | NoOp

Our init function

Our init function will call the update function by passing the GetBitcoinPrice.

init : ( Model, Cmd Msg )
init =
    update GetBitcoinPrice { currentPrice = "Not yet!" }

Let's check our update function.

Our Update Function

Our update function will update our model with the current bitcoin price.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NoOp ->
            (model, Cmd.none)

        GetBitcoinPrice ->
            ( model, Http.send GotBitcoinPrice getBitcoinPrice )

        GotBitcoinPrice result ->
            case result of
                Err httpError ->
                    let
                        _ =
                            Debug.log "handleBitcoinPriceError" httpError
                    in
                        ( model, Cmd.none )

                Ok price ->
                    ( { model | currentPrice = price }, Cmd.none )

In our update function, we are handling our Messages. If we have GetBitcoinPrice, we will call Http.send with two arguments: our GotBitcoinPrice and the request returned by our function getBitcoinPrice.

To understand it more, let's see what the Http.send method is:

send : (Result Error a -> msg) -> Request a -> Cmd msg

It receives two arguments: - (Result Error a -> msg) - Request a

And it returns a Command msg. Now, let's see the implementation of our functions:

  • GotBitcoinPrice (Result Http.Error String)
  • getBitcoinPrice : Http.Request String

As you can see, it matches the definition of our Http.send method and it should be fine. Every time you don't understand a type annotation in Elm, go to the documentation and you will probably understand it better.

Once we get the price, we will have the GotBitcoinPrice message. This can have as result an error or not. If we got an error, in this case an httpError, we will just log using Debug.log. In case everything is fine, we will update our model’s currentPrice field.

      GotBitcoinPrice result ->
          case result of
              Err httpError ->
                  let
                      _ =
                          Debug.log "handleBitcoinPriceError" httpError
                  in
                      ( model, Cmd.none )

              Ok price ->
                  ( { model | currentPrice = price }, Cmd.none )

Our function get

As our last function we will talk about, we have our getBitcoinPrice. This function describes the Http request, using get. It doesn't make the request: the runtime does that when it receives a command. We use Http.send later to turn this request into a command.

Let's see its type annotation:

get : String -> Decoder a -> Request a

It receives a string and a Decoder. This is exactly what we are doing. We have our api function returning our URL and we already saw our decodeContent function.

api : String
api =
    "http://api.coindesk.com/v1/bpi/currentprice.json"


getBitcoinPrice : Http.Request String
getBitcoinPrice =
    Http.get api decodeContent

Styling our component

Let's use elm-bootstrap to make our page look like better!

view : Model -> Html Msg
view model =
    Grid.container [ class "text-center jumbotron" ]
        -- Responsive fixed width container
        [ CDN.stylesheet -- Inlined Bootstrap CSS for use with reactor
        , mainContent model
        ]

Our mainContent will be centered. Here we are using Bootstrap buttons and classes.

mainContent : { a | currentPrice : String } -> Html Msg
mainContent model =
    div []
        [ p [] [ h1 [] [ text "Bitcoin Price" ], h5 [] [ text "It costs right now:" ] ]
        , p [ class "alert alert-success" ] [ h4 [] [ text model.currentPrice, text " USD" ] ]
        , Button.button
            [ Button.outlinePrimary
            , Button.attrs [ onClick GetBitcoinPrice ]
            ]
            [ text "Update it now" ]
        , div [ class "text-right small" ]
            [ a [ href "https://github.com/dailydrip/elm-bitcoin-price" ]
                [ text
                    "Source Code"
                ]
            ]
        ]

Conclusion

Today we saw how to get data from an API in Elm. We used the notion of Decoders. We saw what our resulting Elm Program looks like. You can use a similar thing to work with any API you want.

Resources