Writing A Full Site in Phoenix and Elm

Part 2: Making HTTP Calls From Elm

Brandon Richey
7 min readJun 29, 2016

Last Updated

June 29th, 2016

Current Versions

Elixir: v1.3.1, Phoenix: v1.2.0, Elm: v0.17

Previous Post In This Series

Introduction

In our last article, we talked about getting the basics of a Phoenix and Elm site up and even set up some of the baseline API calls to simulate requesting a list of Articles. We got up to the point where we were simulating the requests with Elm but weren’t actually making the necessary HTTP calls out. Let’s fix that!

Because getting HTTP support in place is significantly different from a standard Javascript implementation and requires a lot of new concepts, this article is going to be focused entirely on making an HTTP GET request to an API endpoint that returns a list of articles only. The next tutorial will focus on using the other verbs and building an Article form and the rest, but we’ll keep this one pretty short and sweet because there’s a lot going on here.

Adding In Some New Libraries

Let’s start off by adding in the library support that will give us the functionality we need. Open up web/elm/ArticleList.elm and we’ll start making modifications. We need a couple of things here:

  1. The ability to create HTTP requests (provided by Json)
  2. The ability to perform those HTTP requests (provided by Http)
  3. The ability to decode the responses for those HTTP requests (Provided by Task)
  4. The ability to debug messages if things are going south (Provided by Debug)

So, our imports will look like this:

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

The third line here is a little trickier. The Json library includes constructors such as int, string, etc, and in my mind those make the code more complicated, so we’re going to force specifying the Json module for those constructors (instead of “something” := int, we’d have “something” := Json.int). We’re also including one thing exposed into our current namespace, the := operator, which decodes a Json key into a Json value that we specify if it has that field (more on that later).

Updating Our Msg Type To Support HTTP

Now we need to update our messages (the Msg union type) to include a few more different types of messages. We already have our NoOp (No Operation) and Fetch calls, but we also need to add messages for when the fetch succeeds and for when the fetch fails.

If the fetch is successful, we want it to return a List of Articles (Article.Model). If the fetch is unsuccessful, it needs to have a constructor that takes in an Http.Error type (since that is what will be returned in those cases).

type Msg
= NoOp
| Fetch
| FetchSucceed (List Article.Model)
| FetchFail Http.Error

So, to recap:

  1. We need to specify a success Msg. That success msg must specify the type of the data that gets decoded out. Since our server is returning back Json structures that match our Article Model (and since it is an index call, it will return a list of articles. Thus, List Article.Model).
  2. We need to specify a failure Msg. That failure msg must accept Http.Error in its constructor.

And then we need to update our update function to be able to handle these new types:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
NoOp ->
(model, Cmd.none)
Fetch ->
(model, fetchArticles)
FetchSucceed articleList ->
(Model articleList, Cmd.none)
FetchFail error ->
case error of
Http.UnexpectedPayload errorMessage ->
Debug.log errorMessage
(model, Cmd.none)
_ ->
(model, Cmd.none)

First, we need to modify our Fetch call to instead call a “fetchArticles” function. We haven’t written this function yet, but it’s the function responsible for handling the Command (Cmd) from our app. A Command is a managed effect in Elm, which is a nice way of saying that it is a Data representation of a side effect for something that we know we need to do. We change the Fetch handler to return out the current model (since we haven’t actually done the fetch via HTTP yet) and a Cmd letting Elm know that we’ll be modifying the state of our application as a result of this specified command.

Next, our FetchSucceed accepts an “articleList” argument. It just returns out a constructor of our Model, which has a built-in constructor that takes in one argument: a List of Articles. This call has no further Cmds we need to worry about, so for the Cmd we return out Cmd.none.

Finally, our FetchFail accepts the Http.Error message, which we then pattern match on. In the case of an Http.UnexpectedPayload, we capture the errorMessage that it returns and print it out to the web developer console via Debug.log (Debug.log takes a string that you wish to write out), and then return out our unmodified model with no Cmds. This lets us know if our data is set up in a wacky way or returning out something we’re not expecting to handle.

We now have all of the framework code in place that we need, so let’s move on to actually writing out the HTTP calls that do the actual work!

Adding In HTTP Functionality

We’ll start by writing our fetchArticles function that we mentioned earlier as the Cmd handler for actually performing the HTTP call.

-- HTTP calls
fetchArticles : Cmd Msg
fetchArticles =
let
url = "/api/articles"
in
Task.perform FetchFail FetchSucceed (Http.get decodeArticleFetch url)

fetchArticles returns a Cmd constructor that accepts a Msg type. We then specify the url that we want to fetch from inside of our let block, and then we call out to Task.perform.

Task.perform takes a few arguments. The first is what happens when the Task fails, the second is what happens when the Task succeeds, and finally we specify what the task itself is. Http.get is another helper function that performs the actual HTTP GET request; it takes in the decode function that we’ll be passing the data to and the url that we want to perform the fetch from.

Now, we need to tackle our decoding functions. We’ll start by analyzing the structure of the data that we’ll be sending back to the client:

{ data :
[ { title: "Article Title", url: "Article URL", posted_by: "Author", posted_on: "Timestamp" }
, { title: "Article Title", url: "Article URL", posted_by: "Author", posted_on: "Timestamp" }
, { title: "Article Title", url: "Article URL", posted_by: "Author", posted_on: "Timestamp" }
]
}

It’s easier to tackle these functions in stages. If we were to parse this out ourselves, we’d:

  1. Write something to pull the data out of the “data” key
  2. Write something to pull the list of data out of the above response
  3. Write something to decode each item of the above into an Article

So let’s do precisely that:

-- Fetch the articles out of the "data" key
decodeArticleFetch : Json.Decoder (List Article.Model)
decodeArticleFetch =
Json.at ["data"] decodeArticleList

Json.at is a simple function that says “At the specified key (or structure of keys), run the data extracted against the provided decoder. This specifies a “decodeArticleList” decoder that will take the data and turn it into an Elm list.

-- Then decode the "data" key into a List of Article.Models
decodeArticleList : Json.Decoder (List Article.Model)
decodeArticleList =
Json.list decodeArticleData

Json.list is another helper function; this time, it says “apply the Json.list decoder to turn the passed-in data structure into an Elm list of decoders”. This time, we’re saying it is a List of Decoders that can parse the “Article” data structure, so let’s write that function next:

-- Finally, build the decoder for each individual Article.Model
decodeArticleData : Json.Decoder Article.Model
decodeArticleData =
Json.object4 Article.Model
("title" := Json.string)
("url" := Json.string)
("posted_by" := Json.string)
("posted_on" := Json.string)

Here, we’re at the level of each individual item. Since we’re pulling 4 keys out, we need to use the Json.object4function to pull 4 keys out of the structure and convert it into an Article.Model type. We’re expecting to see title, url, posted_by, and posted_on in the structure, so we pass those to the Article.Model constructor. The :=operator here says “Look for the key specified on the left side and use the decoder on the right side to parse the data back out if it exists”.

Conclusion

This was a shorter article but it introduced a lot of the necessary framework we’ll need to be able to piece together some of the more complicated parts of our application, and the good news is that this is one of the most complicated parts of our application that we’ll have to write, so a lot of the rest of what we’ll need to do will just be fleshing things out more and building upon this knowledge base!

In the next article, we’ll add a form to create new Articles, as well as provide ways of editing and deleting articles as well! In future articles, we’ll also discuss JWT authentication handling, web sockets, and integrating a CSS framework into our application!

Check out my new book!

Hey everyone! If you liked what you read here and want to learn more with me, check out my new book on Elixir and Phoenix web development:

I’m really excited to finally be bringing this project to the world! It’s written in the same style as my other tutorials where we will be building the scaffold of a full project from start to finish, even covering some of the trickier topics like file uploads, Twitter/Google OAuth logins, and APIs!

--

--