In this section, we will 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 root 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 example:
-p 5016 runs the server on
By default, an HTTP server runs on port
8080. Since your computer might be running some other application that already uses that port, 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 the
http-server command earlier. Create a new one.
elm-package will ask for your permission. Answer
y and approve the upgrade plan it proposes.
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
Here is how the string we’ll be fetching from the server looks:
"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 write code for displaying 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 string in our model to produce a list of
li tags. All functions in
view are defined in the
Html.Events modules. Let’s import them under the module declaration in
Next up is the
update function and message type. Add the following code to the bottom of
Notice how similar the above
update function is to the one we defined in the Commands section. Here it is once again for comparison:
The expressions for generating commands in both cases have a similar pattern. The following diagram illustrates that pattern.
Here is what the
Http.send function’s type signature looks like:
And here is
Random.generate’s type signature for comparison:
Http.send takes two arguments. The first argument is a function that takes a
Result value and wraps it in a message. In our case, that message is
DataReceived. We haven’t defined that message yet. Let’s do that by appending it to the
Msg type in
DataReceived’s definition looks slightly more complex than
NewRandomNumber’s from the Commands section:
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
NewRandomNumbers 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.
Http.send function must account for the failure scenario describe above. As we learned 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 yet another union type with the following data constructors.
Whenever an HTTP request fails, we can expect to receive one of these values as
DataReceived’s payload. We will cover the
Response type later in this chapter. If the request is successful,
DataReceived’s payload will be a string.
The second argument to the
Http.send function is an HTTP request as shown in its type signature:
The easiest way to create a request is by using the
Http.getString function. As shown in the type signature below, it takes a URL string and returns a
Request type is defined in the
Http module like this:
We will explore this definition in detail later, but for now all we need to understand is that the URL we passed to
Http.getString is assigned to the
url property in
RawRequest record. Additionally, the value
GET is assigned to the
method property since we would like to make an HTTP GET request.
We need to tell the
update function what to do when it receives the
DataReceived message. Add a new
case branch in the
update function 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, we use the
String.split function to extract the individual nicknames from a string into a list and return that list as an updated model. If the response is an error, we simply return the existing model. We’ll write proper error handling code later.
Notice how we have managed to cram a
case expression inside another
case expression in the
update function. For better code readability, update the nesting
case expressions using pattern matching 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. It’s best practice to replace all unused parameters with
_ in Elm.
With its two essential arguments in place, the
Http.send function is ready to fire off a 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.
Before we move on, we need to import the
Http module in
HttpExamples.elm. All HTTP related functions and types we have used thus far are defined in that module.
Putting Everything Together
Even after writing all that code, we still don’t have a working app. Let’s wire everything together by adding the
main function to the bottom of
Instead of creating a separate
init function, we directly assigned a tuple containing an empty list of nicknames and commands to the
init property 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/elm-examples/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.
- Opening browser console
- Instructions for opening the browser console depends on which browser you’re using. Please read this nice tutorial from WickedlySmart that explains how to open the console on various browsers.
When you 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. The Elm Runtime uses Ajax to send all HTTP requests under the hood. That’s why we weren’t able to fetch the nicknames.
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:
If you check out the documentation for the
http-server package we used earlier to create a local server, you’ll notice that there is an option called
--cors that enables 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 if you refresh the page located at
http://localhost:8000/elm-examples/HttpExamples.elm and click the Get data from server button, you should see the nicknames of some of the most popular characters in American fraternity culture.
--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: Refresh the page located at
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 and be done with it, 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.
Notice the type of
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/elm-examples/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. Refresh the page at
http://localhost:8000/elm-examples/HttpExamples.elm 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 is a success, 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.
See the official documentation to understand what each data constructor in
Http.Error’s definition means.
Add a function called
createErrorMessage right below
update. 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 apply
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/elm-examples/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 the data should be retrieved from. We used the
Http.getString function to create a request that contained the description of how and where the data should be retrieved from.
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.send 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 instead of the data we want.
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