The front end side of web development has been rapidly changing for years now with tons of frameworks, tools, and tiny npm libraries. A lot of this has been due to limitations in the JavaScript programming language and its standard API present in web browsers, but plenty of frameworks exist now for implementing a rich web application using an MVC or MVC-like architecture. The rise of single page applications where the entire site is dynamically loaded and modified through JavaScript has made the need for stricter architectural patterns a modern requirement of web development.

At the end of 2016, the hottest JavaScript web frameworks appear to be React and Angular, though they are not the only ones out there (for an interesting comparison of various JS and web frameworks, see TodoMVC). As standard JavaScript (EcmaScript) development picked up the pace in recent years, many ideas that were popularized in libraries and frameworks are finding standardized APIs and improved syntax, but as any seasoned web developer knows, relying on current web standards isn’t always that pragmatic in practice due to the various standards-incompliant browsers in the wild along with older versions of browsers still being supported.

During recent years, there have been several attempts at writing new programming languages for the web including Dart, CoffeeScript, TypeScript, Flow, and Ceylon, and even some non-web languages being ported to the web including Scala.js, Clojurescript, and GopherJS (Go). However, one new language stands amongst them all as a language without the baggage of JavaScript while still being easy enough to learn that a JavaScript developer can slowly adopt it into their own projects. That language, naturally, is Elm.

Why Elm?

Why exactly should you care about yet another programming language for the web when everyone already knows JavaScript? Why should you bother learning yet another framework and set of tools to develop web applications? Elm offers several killer features, two of which you’ll never find in JavaScript: the inability to cause runtime errors and a time-traveling debugger. Just these features alone can help alleviate tons of pain from typical JS development. There are several other features that are worth mentioning including:

  • Ease of refactoring.
  • Easy to understand standard architecture for writing code.
  • Use of stateless functions to reduce state-caused bugs.
  • Reliable runtime environment.
  • Better syntax for performing callbacks/promises.
  • Friendly compiler error messages.
  • Automatic semantic versioning enforcement for libraries.
  • Nice online IDEs (and offline IDE support of course).
  • Easy to learn syntax.
  • Interoperates with existing JavaScript code with safety barriers.

Many of the above points are discussed in this article. The Elm documention provides a great getting started guide that teaches you most of what you need to know to get started using Elm in real applications. In this article, we’ll go over a simple Elm application that uses our sample blog microservice developed previously. If you’re purely a front end developer, don’t worry; we’ll go over the REST API exposed by that service.

Developing an example Elm app

In previous posts, we’ve developed a rather simple blog REST API. We have three REST calls available:

  • GET /api/blog/:id – gets a blog post by ID.
  • POST /api/blog – adds a new blog post; response gives us the new ID.
  • PUT /api/blog/:id – updates a blog post by ID.

The data model we use looks like this:

{
    "title": "Some Title",
    "author": "Some Author",
    "body": "Post contents go here. No specific formatting was specified."
}

Simple enough, right? We’re going to implement a simple web app for this API. The code for these examples are in this GitHub project. We’ll briefly go over the Elm Architecture as it is already explained rather well in the getting started guide, then we’ll implement the various functionality for our app.

The Elm Architecture

Elm applications tend to follow a very standard architecture consisting of a data model, a set of message types, an update function which receives messages and the current model and returns an updated model, and a view function which receives the current model and returns HTML with attached event listeners which send messages back to the update function. More advanced Elm applications also include an initialization function for the model, additions to the update function to return commands for performing asynchronous operations (which themselves are hooked back into your code via messages again), and subscriptions which are used for receiving updates and responding with messages.

Essentially, this architecture works by beginning with an initial model. This model is provided to the view function which transforms that model into HTML. In that HTML transformation are standard DOM event handlers, but instead of attaching functions to DOM events, we instead provide a message type for that event. When the event fires (e.g., an input event in a text <input>), Elm creates a message using the type provided and any related information to that event (e.g., an input event will provide the string value of the <input>). This message is passed to the update function along with the current model, and that function returns an updated model along with a command to execute in response. If a command is provided, this command completes by sending a message which gets fed back into the update function again. This completes the standard lifecycle of an Elm application.

A bit abstract, isn’t it? Don’t worry, it’ll become a bit clearer while we work through an example application.

Model

Generally, we can always start development by creating a model to represent the state of our application. For our purposes, we already saw most of the model above as defined by the REST API. In fact, all four pieces of data we’ll need were specified there, so let’s create our model:

type alias BlogPost =
  { id : String
  , title : String
  , author : String
  , body : String
  }

The syntax is pretty straightforward. In Elm, a type alias is what it sounds like: an alias for a type. In this case, a BlogPost will be an alias for a generic Elm record (similar to a JavaScript object) with four fields, all of which are String types. Creating a type alias also creates a constructor for that record which takes all arguments in order. For example, we can create a BlogPost record:

post = BlogPost "1" "Hello, world!" "John Doe" "Test post contents."

Update

Next, we’ll work on our update function. We start by defining a message type union along with some message types. For simplicity, we’ll only define messages related to getting a blog post for now.

type Msg
  = ChangeId String
  | GetPost
  | LoadPost (Result Http.Error BlogPost)

Let’s go over each message type in detail. First, the ChangeId String message will be used as an onInput event handler for an input field to look up a blog post by ID. We’ll use this message to update the model only; we don’t want to attempt a post lookup for every character in the ID!

Next, the GetPost message will be used as both an onClick event handler for the submit button along with an onSubmit event handler for the form itself. This message will indicate that we wish to perform a lookup for the post ID stored in our model.

Finally, the LoadPost (Result Http.Error BlogPost) message will be used to indicate that our HTTP call has completed. Since an HTTP call can succeed or fail, this uses a Result type which can contain either an Http.Error on error or a BlogPost on success. We’ll use this message to update our model in response to the completion of the HTTP call.

Now that we have some message types and knowledge of what we wish to do with said messages, next we’ll implement an update function.

update : Msg -> BlogPost -> (BlogPost, Cmd Msg)
update msg post =
  case msg of
    ChangeId id ->
      ({ post | id = id }, Cmd.none)
    GetPost ->
      (post, getBlogPost post.id)
    LoadPost (Ok loadedPost) ->
      ({ loadedPost | id = post.id }, Cmd.none)
    LoadPost (Err _) ->
      (post, Cmd.none)

The first line is a type signature specifying that update is a function that takes two parameters, a Msg and a BlogPost, and returns a tuple containing a BlogPost and a Cmd Msg. We’ll come back to the Cmd Msg part in just a bit, but let’s take a closer look at this case msg of ... expression. Recall that our Msg type is actually a type union of three different types. This means that in order determine which type of message we’ve received in this function, we need to perform what’s called pattern matching on the message.

In our first match, we check if the message is a ChangeId String type where the String is given the variable name id. Our reaction to a ChangeId message is to change the ID of our model. As records are not mutable in Elm, we must return a new record with an updated value. Elm provides a convenient syntax to return a modified copy of an existing record: { post | id = id}. Our second return parameter, Cmd.none, indicates that we do not wish to execute any commands in response to this message. One possible use case for a command here would be for implementing an autosuggest feature.

The second match checks for a message of type GetPost. When this message is received, we know we’d like to actually perform a GET call to our REST API. In response, we return an unmodified model along with a command to go get a blog post with the id specified by the model, model.id. We’ll come back to the implementation of the getBlogPost function in just a moment.

The last two matches are for LoadPost messages, the first being the success case and the second being the error case. In the error case, we don’t do actually do anything, but a more robust application might attempt to retry the API call or display an error message to the user. In the success case, we’ll receive a BlogPost model parsed from the REST API lookup, but we update the ID of the post using the ID we had previously as our API doesn’t actually return the ID of the post in its body. We return a no-op Cmd.none as well here.

Let’s dive further into our HTTP GET call.

baseUrl = "http://localhost:8080/api/blog/"

getBlogPost : String -> Cmd Msg
getBlogPost id =
  let
      url = baseUrl ++ id
      request = Http.get url (blogPostDecoder id)
  in
     Http.send LoadPost request

Our getBlogPost function takes one parameter, the ID of the blog post to look up, and it returns a command to perform an HTTP GET call along with a message to send on completion. Looking further, we use a let ... in ... expression which allows us to define local variables to that function. The ++ operator is the string concatenation operator in Elm, so the url variable is pretty simple. The request variable will be used by the Http.send function to transform a request into a command. We create the request using two parameters: a URL to GET, and a JSON decoder to parse the response with. We’ll look at the JSON decoder in more detail in just a moment. Finally, we use this request along with the LoadPost message type to create a command using Http.send. This command will be used by Elm to perform the actual HTTP request which completes by sending a LoadPost message.

Let’s look at the JSON decoder function:

blogPostDecoder : String -> Decoder BlogPost
blogPostDecoder id =
  map3 (BlogPost id)
    (field "title" string)
    (field "author" string)
    (field "body" string)

Note that we’ve cheated a bit with syntax here slightly by importing the functions map3, field, and string, along with the type Decoder, all from the Json.Decode module. This saves us a bit of typing, but the above could also be written as:

blogPostDecoder : String -> Json.Decode.Decoder BlogPost
blogPostDecoder id =
  Json.Decode.map3 (BlogPost id)
    (Json.Decode.field "title" Json.Decode.string)
    (Json.Decode.field "author" Json.Decode.string)
    (Json.Decode.field "body" Json.Decode.string)

This function creates a BlogPost JSON decoder. Since we don’t receive an id field in our REST API response, we fill that in ourselves by constructing a partially applied function, (BlogPost id), which now only requires three parameters to specify the remaining fields. Note that the JSON API provided by the core Elm package is rather low level in order to provide type safety, but there are other packages available to make this a bit simpler. In the case of our decoder, we’re specifying that we expect a JSON object with three fields, all of which are strings.

View

Finally, we need to implement a view to display our UI. In this example, we’ll be using some wildcard imports for the various HTML functions for creating HTML elements and attributes. I will note ahead of time that if an HTML element or attribute name conflicts with an Elm keyword, then it’ll have an underscore suffix. For example the type attribute from HTML will be implemented as a function called type_ in Elm.

view : BlogPost -> Html Msg
view post =
  section []
    [ article []
        [ header []
            [ h1 [] (formatTitle post) ]
        , div [] [ post.body ]
        ]
    , Html.form [ onSubmit GetPost ]
        [ div []
            [ label [ for "blog-id" ] [ text "id" ]
            , input
                [ id "blog-id"
                , type_ "text"
                , onInput ChangeId
                , value post.id
                , placeholder "12345678-1234-1234-1234-1234567890ab"
                ] []
            , span []
                [ button [ onClick GetPost ] [ text "get post" ] ]
            ]
        ]
    ]

formatTitle : BlogPost -> List (Html Msg)
formatTitle post =
  if String.isEmpty post.title then
    [ text "" ]
  else
    [ text <| post.title ++ " "
    , small []
        [ text <| "by " ++ post.author
        ]
    ]

Note that I’ve removed a bit of unnecessary wrapper HTML and classes that are used in the GitHub project to provide some styling. One big thing to notice here is that Elm does not use any sort of template language. Instead, Elm creates HTML through standard Elm functions. This allows for certain optimizations in displaying the resulting HTML as well as allowing various page components to be refactored into functions instead. Based on our imports, all the HTML element functions would otherwise be prefixed by Html., the attributes would be prefixed by Html.Attributes., and the event handlers would be prefixed by Html.Events.. The text function creates a text node and is not an actual HTML element. The Html module provides all the common HTML5 elements and attributes, but custom element and attribute names can also be created through the Html.node and Html.Attributes.attribute functions respectively.

Besides all the actual markup, there are just a couple things to note in this code. First, we’ve specified that any input events on the ID text field will send ChangeId messages which we’ve implemented earlier in our update function. On both the form and the button, we’ve specified that the GetPost message should be sent for submit and click events respectively. Finally, we fill in data from our model when present into these elements. We use a fancier function for creating the blog post header so that it only display when a title is present. The only unknown syntax here is the <| operator which means “put the rest of the arguments after this in parenthesis”. That is, both of the following snippets are equivalent:

foo bar <| a b c d
foo bar (a b c d)

Putting it all together

There is a little bit of boilerplate not shown here that is required to finish the file which includes specifying a main function and a subscriptions function. Both of these functions are written in the full example. This full example also includes a form for creating and editing blog posts, markdown parsing of the post body, and some styling using Bootstrap. The GitHub project also includes instructions on running a local copy of the blog microservice (requires Java 8 and Maven). This project works using Elm 0.18 and only requires the Elm runtime.

Elm shows a lot of promise as a friendly and easy functional programming language for web development. End your JavaScript fatigue by trying out the Elm language and package ecosystem today! Elm is already used in production in several websites, and thanks to semantic versioning, can be safely used without worrying about backwards incompatible upgrades between minor versions. The best way to go about using Elm in production today is to gradually start using it with your existing JavaScript code.