In this section, we’ll try to understand the process a typical Elm app goes through to retrieve data from an HTTP server. We’ll first create a simple HTTP server on our local computer. After that, we’ll build an Elm app that will fetch data from that server.
Creating a Local HTTP Server
There are many different ways to create an HTTP server on our local computer. We’ll use the NPM package called http-server, which allows us to serve static files. Static files are files that are served to the user exactly as they are stored, without any changes due to the user’s input or preferences. Go ahead and install it globally using the
-g option so that it can be run from anywhere in the terminal.
Now create a file called
old-school.txt inside a new directory called
server, which should be placed in the
beginning-elm root project directory.
Add the following text to the
We are ready to start an HTTP server. Run the following command from the
beginning-elm directory in terminal.
You should see an output like this:
http-server command creates, you guessed it, an HTTP server. We give it the name of the directory to serve files from. In our case it’s
-a localhost option makes the URL look nicer. Without it, we would have to specify the IP address of the computer we are coding on like this:
-p 5016 runs the server on
By default, an HTTP server runs on port
8080. Your computer might be running some other application that already uses that port, so it’s better to run our server on a different port to avoid a conflict. Port
5016 is rarely used by other applications.
If you go to the url
http://localhost:5016/old-school.txt on a browser, you should see the contents of the
This means our local HTTP server is working. Next we’ll write some Elm code to communicate with this server.
Fetching Data from an HTTP Server
If you don’t have it installed already, go ahead and run the above command from the
beginning-elm directory in terminal. Don’t run it from the same terminal window where we ran
http-server earlier. Create a new one. When
elm install asks for your permission, answer
Here is our strategy: we will first write a simple Elm program to retrieve the contents of the
old-school.txt file. We will then parse this comma-separated string to extract the nicknames of the main characters from Old School — a cult classic — and display those nicknames on a page. Let’s start by creating a new file called
HttpExamples.elm in the
As usual, the first thing we will define is our model. Add the following code to
The string from server looks like this:
"The Godfather, The Tank, Beanie, Cheese". We will extract each nickname and store it in a list. That’s why our model’s type is
Next we’ll display the nicknames. Add the following code to the bottom of
Although our view is quite simple, let’s briefly go through each element. First, we display a button that when clicked tells the Elm runtime to dispatch
SendHttpRequest message to the
update function — we’ll implement
update in a moment. Then we add a heading followed by an unordered (bulleted) list of nicknames.
The code for rendering each nickname is extracted out to a separate function called
viewNickname. It’s a common practice in Elm to render an individual item in a list using a separate function.
List.map applies the
viewNickname function to each nickname in our model to produce a list of
li tags. All functions in
view are defined in the
Html.Events modules. Let’s import those modules in
Next up is the
update function and message type. Add the following code to the bottom of
And import the
update handles the
SendHttpRequest message by returning the original model and a command for fetching nicknames from the local HTTP server we created earlier. Here is what the
Http.get function’s type signature looks like:
It takes a record with two fields and returns a command. The
url field holds the location of the server resource. The
expect field specifies the format we expect from the server. By using
Http.expectString, we’re letting Elm know that we expect the response body to be a string. Here is what the
Http.expectString function’s type signature looks like:
Comparing Http.get to Random.generate
In the Commands section, we wrote the following code to generate a random number.
Http.get look structurally different, they both contain three ingredients required for communicating with the outside world:
- A mechanism for creating a command.
- What needs to happen when the command is run?
- Which message should be sent to the app after the command has been executed?
Comparing DataReceived to NewRandomNumber
DataReceived message looks slightly more complex than
The command for generating a random number always succeeds. We are guaranteed to receive a random number from the Elm runtime when asked. That’s why
NewRandomNumber’s definition is so simple. In contrast, fetching data from a server can fail. Perhaps the server isn’t available or the URL we’re trying to reach is incorrect. There are many other reasons why fetching data from a server may fail. Therefore, unlike
Http.get must account for those failure scenarios.
As mentioned in the Type System section, Elm has a built-in type called
Result for representing the outcome of an operation that can fail.
It accepts two arguments:
value. In our case, the type of
Http.Error and the type of
Http.Error is a built-in custom type with the following data constructors.
Whenever an HTTP request fails, we can expect to receive one of these values as
DataReceived’s payload. If the request is successful,
DataReceived’s payload will be a string. Check out the official documentation to find out what those error types mean.
Handling DataReceived Message
We need to tell the
update function what to do when the
DataReceived message arrives. Add a new
case branch to
HttpExamples.elm to handle that message as shown below.
All we are doing here is unpacking the
result payload that rides on
DataReceived’s back. If it’s a successful response, the individual nicknames are extracted from a string into a list using
String.split and that list is returned as an updated model. If the response is an error, we simply return the existing model. We’ll write proper error handling code in the Handling HTTP Errors section below.
Notice how we have managed to cram a
case expression inside another
case expression in the
update function? We can use pattern matching to get rid of nested
case expressions like this:
In Elm, tuples can be used to write concise yet clear
case expressions by matching complex patterns as we have done in the above refactoring. We’ve also replaced the payload
_ because we aren’t using it right now. It’s best practice to replace all unused parameters with
_ in Elm.
We’ve assembled all pieces required to fire an HTTP command. The following diagram shows how various components in our app interact with the Elm runtime to accomplish the task of fetching nicknames from a server.
Wiring Everything Up
Even after writing all that code, we still don’t have a working app. Let’s wire everything up by adding the
main function to the bottom of
We also need to import the
Browser module in
Instead of creating a separate
init function, we directly assigned an anonymous function that takes
flags and returns a tuple containing an empty list of nicknames and commands to the
init field in
main. It doesn’t make sense to create a separate function if all it’s going to do is return empty values, even though we did exactly that in the Commands section.
Finally, we’re ready to taste the fruits of our labor. Fire up
elm reactor from the
beginning-elm directory in terminal if it’s not running already, and go to this URL in your browser:
http://localhost:8000/src/HttpExamples.elm. You should see a view that looks like this:
Unfortunately, if you click the Get data from server button right now nothing happens. What could have gone wrong? To find out open the browser console. You should see an error message that looks like this:
What the browser is trying to tell us is that we can’t request data from a domain that’s different from the one where the request was originated from. For security reasons, most modern browsers restrict cross-origin HTTP requests initiated through an ajax call which uses the
At this point you may be wondering why we received that error when the local server domain and the client app domain are exactly the same:
localhost. As it turns out, the cross-origin policy dictates that it’s not enough for the domains to be the same. The ports also have to match, but our server and client app are running on different ports.
- Server URL:
- Client URL:
Allowing Cross-Origin Resource Sharing
How do we fix this cross-origin issue? A solution is lurking in the error message. If you look closely, the browser is telling us what it expects:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Access-Control-Allow-Origin is one of the headers included in a response sent by the server. It indicates which domains are allowed to use the response. For example, if the server returns
Access-Control-Allow-Origin: *, the response can be used by any domain. But if the server wants only certain domains to have access then it’ll return a domain name(s) instead of
*. Here’s an example:
http-server package we used earlier to create a local server provides an option called
--cors for enabling Cross-Origin Resource Sharing (CORS) via the
Access-Control-Allow-Origin header. Let’s stop our local server by pressing
Ctrl + c and then restart it using the
Now open a new browser window in private mode and go to
http://localhost:8000/src/HttpExamples.elm. After that click the Get data from server button and you should see the nicknames of some of the most popular characters in American fraternity culture.
Note: Most browsers cache CORS policies for sometime in non-private mode. That’s wny we need to open
HttpExamples.elm in private mode. Otherwise, we keep getting the same CORS error we saw earlier even after refreshing the page.
--cors option adds
Access-Control-Allow-Origin: * to the list of response headers. If you are using the Chrome browser, you can verify the presence of that header by following these steps:
Step 1. Open the page located at
http://localhost:8000/src/HttpExamples.elm in a new private window.
Step 2. Go to the Network tab in Developer Tools window.
Step 3. Click the Get data from server button from our app.
Step 4. A new row should appear in the Network tab. Click
old-school.text below the Name column.
Step 5. Look for
Access-Control-Allow-Origin in the Response Headers section.
Handling HTTP Errors
Earlier in this section, we cheated by simply returning an existing model when a request to fetch nicknames failed.
We’ll rectify that by showing a proper error message.
Storing an Error Message
The first thing we need to do is store an error message in our model. Right now it’s just a list of strings.
We could simply append the error message to this list, but that seems a bit hacky. A better alternative is to store it separately from nicknames. Let’s change our
Model to use a record instead.
errorMessage field’s type is
Maybe String instead of just
String. That’s because if the HTTP request is successful, there won’t be any error to show. What we need is a data structure that can represent the absence of a value.
Maybe fits the bill.
Fixing Compiler Errors
Changing the structure of our
Model causes the Elm compiler to throw errors when we refresh the page at
http://localhost:8000/src/HttpExamples.elm. We can actually use those errors as a guide to figure out what needs to be fixed. This is a big advantage Elm has over other languages that don’t have a robust type system. We can count on the Elm compiler to catch our mistakes — no matter how subtle — as we mercilessly refactor our code.
Let’s start with the
view function. Nicknames are now located inside a record, so we need to use the dot syntax to access them. Modify the line that contains the
ul tag in
view to this:
Next we’ll fix the
update function. Right now it splits the nicknames string into a list and returns that as a model. We can’t do that anymore. We need to assign the list to the
nicknames property inside the model. Modify the
DataReceived (Ok nicknamesStr) branch in
update to this:
The only thing remaining to fix is the value we’re assigning to the
init property in
main. Change it to the following.
Inlining an initial model like that makes our code look a bit clunky. Let’s extract it out to a separate function.
And we’re back to having a working app. Open the page at
http://localhost:8000/src/HttpExamples.elm in a new private window and click the Get data from server button to make sure that you can successfully fetch the nicknames.
Displaying an Error Message
Here is our plan for notifying the users when things go haywire: if the request to fetch nicknames succeeds, we’ll display a heading and a bulleted list of nicknames. However, if the request fails, we’ll replace the heading and nicknames with an error message. We can accomplish that by modifying our view code in
HttpExamples.elm as shown below.
We’re using separate functions to render an error message and nicknames. The core logic for rendering nicknames hasn’t changed at all. We just extracted that logic out to the
viewNicknames function. The
viewError function accepts an error message which will be determined later when we deal with the
Http.Error value. We render that error message right below a heading.
Creating an Error Message
When a request to fetch nicknames fails, the
update function is notified with a value of type
Http.Error which lays out all the different ways a request can fail.
Add a function called
buildErrorMessage right below
HttpExamples.elm. This new function determines what the error message should be in each failure case.
If a data constructor has a payload, an error message is created based on what’s inside it. Otherwise, we just hard code it. Now call
update to set the
errorMessage property inside our model as shown below.
httpError in the
DataReceived (Err httpError) -> branch because we’re now actually using the error payload. We also wrapped the return value from
Just because the
errorMessage property expects a
Maybe type. We’re ready to test the error handling code. Let’s change the URL to something invalid in
If you refresh the page at
http://localhost:8000/src/HttpExamples.elm and click the Get data from server button, you should see the following error message.
Change the URL back to
http://localhost:5016/old-school.txt. We won’t test other error types here, but if you ever receive one you now know how to handle it.
We need to go through three steps to retrieve data from a server:
1. Specify where to retrieve data from. We used the
expect fields in the record passed to the
Http.get function to let the Elm runtime know where our data resides and in what format.
2. Retrieve data. Sending and receiving data from a server causes side effects. Since Elm functions aren’t allowed to have any side effects, our application code can’t retrieve data by itself. It needs to ask the Elm runtime to do that by sending a command. We used the
Http.get function to wrap our request in a command and handed that over to the runtime. The runtime executed that command to retrieve data from our local server.
3. Notify the update function. If the request is successful, the runtime sends the
DataReceived message to
update with retrieved data as a payload. If the request fails, the payload is an error.
The sequence diagram below shows how the interaction between the Elm runtime and our code looks while fetching data. Notice how similar the interaction is to the process of generating random numbers shown in the Commands section.
In the next section, we will explore how to retrieve and decode JSON data from a server. Here is the entire code from