This has been updated for Elm 0.17.

In a previous post, I touched on how we gently introduced Elm to our production stack at NoRedInk. But we didn’t stop there! Since that first course of Elm tasted so good, we went back for seconds, then thirds, and now we use Elm with The Elm Architecture for as much of our front-end as possible. We spend about 95% of our front-end programming time writing Elm, and React has become strictly legacy code.

Where many languages built for the browser address the question “How can we improve JS?” Elm asks a bigger one: “how can we have the best experience making user interfaces?” This back-to-basics focus can make parts of Elm unfamiliar at first, but it enables things like a package system where semantic versioning is automatically enforced for every package and large, complex applications whose production Elm code has yet to throw a runtime exception.

Let’s explore these benefits by building a live-validated signup form in Elm!

Rendering a View

Before we get to some code, you’ll probably want an editor plugin to make your code look nice. It’s optional, but I also highly recommend also installing elm-format by Aaron VonderHaar, which you can use to instantly format your source files every time you save them. It saves a lot of time fiddling with code formatting!

If you already have npm, you can get the Elm compiler by running npm install -g elm (or alternatively you can just download the Elm installer). Afterwards you should be able to run this command and see the following output:

$ elm-make --version elm-make 0.17 (Elm Platform 0.17.0)

…followed by some help text. Make sure the Elm Platform version is 0.17.0!

Let’s start by rendering the form. Where JavaScript has Handlebars, CoffeeScript has Jade, and React has JSX, Elm also has its own declarative system for rendering elements on a page. Here’s an example:

view model = form [ id "signup-form" ] [ h1 [] [ text "Sensational Signup Form" ], label [ for "username-field" ] [ text "username: " ], input [ id "username-field", type' "text", value model.username ] [], label [ for "password"] [text "password: " ], input [ id "password-field", type' "password", value model.password ] [], div [ class "signup-button" ] [ text "Sign Up!" ] ]

Like any templating system, we can freely substitute in values from the model. However, unlike Handlebars, Jade, and JSX, this is not a special templating library with its own special syntax - it’s just vanilla Elm code! (You can check out the Elm Syntax Reference for more.)

The above code simply defines a function called view with one argument ( model ). Much like Ruby and CoffeeScript, everything is an expression in Elm, and the last expression in an Elm function is always used as its return value.

In other words, the above code would look essentially like this in JavaScript:

function view(model) { return form([ id("signup-form") ], [ h1([], [ text("Sensational Signup Form") ]), label([ for("username-field") ], [ text("username: ") ]), input([ id("username-field", type_("text"), value(model.username) ], []), label([ for("password") ], [ text("password: ") ]), input([ id("password-field", type_("password"), value(model.password) ], []), div([ class("signup-button") ], [ text("Sign Up!") ]) ]); }

Notice that in Elm you call functions with spaces rather than parentheses, and omit the commas that you’d put between arguments in JS. So in JavaScript you’d write:

Math.pow(3, 7)

…whereas in Elm you’d write:

Math.pow 3 7

When using parentheses to disambiguate, you wrap the entire expression like so:

Math.pow (Math.pow 3 7) 4

On a stylistic note, the Elm Style Guide recommends putting commas at the beginning of the line:

view model = form [ id "signup-form" ] [ h1 [] [ text "Sensational Signup Form" ] , label [ for "username-field" ] [ text "username: " ] , input [ id "username-field", type' "text", value model.username ] [] , label [ for "password" ] [ text "password: " ] , input [ id "password-field", type' "password", value model.password ] [] , div [ class "signup-button" ] [ text "Sign Up!" ] ]

When I first started using Elm I thought this looked weird, and kept using the trailing-commas style I was accustomed to. Eventually I noticed myself spending time debugging errors that turned out to have been caused because I’d forgotten a trailing comma, and realized that with leading commas it’s blindingly obvious when you forget one. I gave this style a try, got used to it, and now no longer spend time debugging those errors. It’s definitely been worth the adjustment. Give it a shot!

Since Elm compiles to a JavaScript file, the next step is to use a bit of static HTML to kick things off. We’ll be using a file that includes a basic HTML skeleton, some CSS styling, a tag to import our compiled .js file (the Elm standard library is about the size of jQuery), and one line of JavaScript that kicks off our Elm app:

Elm.SignupForm.embed(document.getElementById("elm-rendering-area"));

We’re choosing to have the Elm app render to a particular div , but we could also have it run without rendering—for example, to add a touch of Elm to an existing JavaScript code base.

Finally we need to surround our view function with some essentials that every Elm program needs: naming our module, adding relevant imports, and declaring our main function. Here’s the resulting SignupForm.elm file, with explanatory comments:

module SignupForm exposing (..) -- This is where our Elm logic lives.`module SignupForm` declares that this is -- the SignupForm module, which is how other modules will reference this one -- if they want to import it and reuse its code. import Html.App -- Elm’s "import" keyword works similarly to "require" in node.js. import Html exposing (..) -- The “exposing (..)” option says that we want to bring the Html module’s contents -- into this file’s current namespace, so that instead of writing out -- Html.form and Html.label we can use "form" and "label" without the "Html." import Html.Events exposing (..) -- This works the same way; we also want to import the entire -- Html.Events module into the current namespace. import Html.Attributes exposing (id, type', for, value, class) -- With this import we are only bringing a few specific functions into our -- namespace, specifically "id", "type'", "for", "value", and "class". view model = form [ id "signup-form" ] [ h1 [] [ text "Sensational Signup Form" ] , label [ for "username-field" ] [ text "username: " ] , input [ id "username-field", type' "text", value model.username ] [] , div [ class "validation-error" ] [ text model.errors.username ] , label [ for "password" ] [ text "password: " ] , input [ id "password-field", type' "password", value model.password ] [] , div [ class "validation-error" ] [ text model.errors.password ] , div [ class "signup-button" ] [ text "Sign Up!" ] ] -- Take a look at this starting model we’re passing to our view function. -- Note that in Elm syntax, we use = to separate fields from values -- instead of : like JavaScript uses for its object literals. getErrors model = { username = if model.username == "" then "Please enter a username!" else "" , password = if model.password == "" then "Please enter a password!" else "" } initialErrors = { username = "", password = "" } main = Html.App.program { init = ( initialModel, Cmd.none ) , view = view , update = update , subscriptions = \_ -> Sub.none }

The starting model we’re passing to the view is:

{ username = "", password = "" }

This is a record. Records in Elm work pretty much like Objects in JavaScript, except that they’re immutable and don’t have prototypes.

Let’s build this, shall we?

$ elm-make SignupForm.elm Some new packages are needed. Here is the upgrade plan. Install: elm-lang/core 4.0.1 Do you approve of this plan? (y/n)

Ah! Before we can build it, first we need to install dependencies.

elm-lang/core is a basic dependency needed by every Elm program, so we should answer y to install it.

Now we should see this:

Downloading elm-lang/core Packages configured successfully! I cannot find module 'Html.App'. Module 'SignupForm' is trying to import it. Potential problems could be: * Misspelled the module name * Need to add a source directory or new dependency to elm-package.json

Ah, right. Because we’re using features from the html package in addition to core , we’ll need to install that explicitly:

$ elm-package install elm-lang/html To install evancz/elm-html I would like to add the following dependency to elm-package.json: "elm-lang/html": "1.0.0 The package installer wants to create an elm-package.json file for you. How nice of it! Enter y to let it do this. Next it will tell you it needs a few dependencies in order to install elm-html, followed by: Do you approve of this plan? (y/n)

Again, enter y to install the virtual-dom dependency as well as html . Once it’s done, we have our dependencies installed. We’re ready to build!

Run this to build your Elm code:

$ elm-make SignupForm.elm

After this completes, you should see:

Successfully generated index.html.

Sure enough, the current folder will now have an index.html file which contains your Elm code compiled to inline JavaScript. By default, elm-make generates this complete .html file so that you can get something up and running with minimal fuss, but we’ll compile it to a separate .js file in a moment.

You should also now have an elm-stuff/ folder, which contains cached build data and your downloaded packages - similar to the node_modules/ folder for your npm packages. If you’re using version control and like to put node_modules in your ignore list, you’ll want elm-stuff/ in your ignore list as well.

If you open up index.html in a browser, you’ll see an unstyled, fairly disheveled-looking signup form. Let’s improve on that by splitting out the JavaScript so we can put it in some nicer surroundings.

$ elm-make SignupForm.elm --output elm.js Success! Compiled 0 modules. Successfully generated elm.js

The --output option specifies the file (either .html or .js) that will contain your compiled Elm code.

See how it says Compiled 0 modules ? This is because elm-make only bothers rebuilding parts of your code base that changed, and since nothing changed since your last build, it knows nothing needed to be rebuilt. All it did was output the results of its previous build into elm.js.

Go ahead and download the HTML boilerplate snippet as example.html, put it in the same directory as your elm.js file, and open it. Since example.html loads a file called elm.js, you should now see a signup form like so:

Congratulations! You’ve now built a simple static Elm view and rendered it in the browser.

Next we’ll add some interaction.

Expanding the Model

Let’s add a touch of interactivity: when the user submits an empty form, display appropriate error messages.

First, we’ll introduce some validation error messages to our model, and render them in the view:

view model = form [ id "signup-form" ] [ h1 [] [ text "Sensational Signup Form" ] , label [ for "username-field" ] [ text "username: " ] , input [ id "username-field", type' "text", value model.username ] []

, div [ class "validation-error" ] [ text model.errors.username ]

, label [ for "password" ] [ text "password: " ] , input [ id "password-field", type' "password", value model.password ] []

, div [ class "validation-error" ] [ text model.errors.password ]

, div [ class "signup-button" ] [ text "Sign Up!" ] ]

If we wrote this same logic in JavaScript, on page load we’d get a runtime exception: “ cannot read value "username" of undefined. ” Oops! We’re trying to access model.errors.username in our view function, but we forgot to alter the initial model in our main function to include an errors field.

Fortunately, Elm’s compiler is on top of things. It will actually figure out that we’ve made this mistake and give us an error at build time, before this error can reach our users!

$ elm-make SignupForm.elm --output elm.js ==================================== ERRORS ==================================== -- TYPE MISMATCH ------------------------------------------------ SignupForm.elm The argument to function `view` is causing a mismatch. 48| view { username = "", password = "" } ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Function `view` is expecting the argument to be: { b | ..., errors : ... } But it is: { ... } Detected errors in 1 module.

Let’s take a closer look at what it’s telling us.

Function `view` is expecting the argument to be: { b | ..., errors : ... } But it is: { ... }

This tells us that our view function is expecting a record with a field called errors (among other fields - hence the ... ), but the record we’ve provided does not have an errors field! Sure enough, we totally forgot to include that. Whoops.

The compiler knows this is broken because we pass that record into our view function as the model argument, and the view function then references model.errors.username and model.errors.password - but given the code we’ve written, there’s no possible way model.errors could be defined at that point.

This is a classic example of the Elm compiler saving us from a runtime exception. It’s really, really good at this - so good that our entire Elm codebase (around 30,000 lines of Elm code) still hasn’t thrown a single runtime exception in production!

Now that we know about the problem, we can fix it by adding an errors field like so:

initialErrors = { username = "bad username", password = "bad password" } main = view { username = "", password = "", errors = initialErrors }

Let’s rebuild with elm-make SignupForm.elm --output elm.js (which should succeed now) and open the browser. We should see this:

Incidentally, in a larger project we’d probably use a build tool plugin like elm-webpack-loader or gulp-elm or grunt-elm rather than directly running elm-make every time, but for this example we’ll keep things simple.

Before we move on, let’s make initialErrors start out empty, as the user should not see “bad username” before having had a chance to enter one:

initialErrors = { username = "", password = "" }

Next we’ll update the model when the user interacts.

Adding Validation Logic

Now that we’re rendering errors from the model, the next step is to write a validation function that populates those errors. We’ll add it right before our main declaration:

getErrors model = { username = if model.username == "" then "Please enter a username!" else "" , password = if model.password == "" then "Please enter a password!" else "" }

For comparison, here’s how this would look in (similarly formatted) JavaScript:

function getErrors(model) { return { username: (model.username === "" ? "Please enter a username!" : ""), password: (model.password === "" ? "Please enter a password!" : "") }; }

To recap: we have a starting model, a validation function that can translate that model into error strings, and a view that can report those errors to the user. The next step is to wire it all together. When the user clicks Sign Up, we should use all the pieces we’ve built to display any appropriate errors.

How do we do that? With a call to Html.App.program ! Let’s replace our main implementation with the following:

main = Html.App.program { init = ( initialModel, Cmd.none ) , view = view , update = update , subscriptions = \_ -> Sub.none }

Html.App.program gives us an interactive application that follows The Elm Architecture. It needs four ingredients: init , view , update , and subscriptions . We already have a view and we won’t be using subscriptions today, so the next step is to specify update and init .

Let’s add update below our getErrors function:

update msg model = if msg.msgType == "VALIDATE" then ( { model | errors = getErrors model }, Cmd.none ) else ( model, Cmd.none )

This update function’s job is to take a message (a value that describes what we want done) and a model, and return two things:

A new model, with any relevant updates applied A description of any commands we want run - such as firing AJAX requests or kicking off animations. We’ll dive into using commands later, but for now we’ll stick to no commands: Cmd.none

Have a look at what the else branch returns:

( model, Cmd.none )

Whenever you see a comma-separated list of elements inside parentheses like this, what you’re seeing is a tuple. A tuple is like a record where you don’t bother naming the fields; ( model, Cmd.none ) is essentially a variation on { model = model, command = Cmd.none } where fields are accessed by position rather than by name.

Here the else branch returns a tuple containing the original model unchanged, as well as Cmd.none - indicating we do not want any effects like AJAX requests fired off. Compare that to the if branch:

if msg.msgType == "VALIDATE" then ( { model | errors = getErrors model }, Cmd.none )

This says that if our msg record has a msgType field equal to "VALIDATE" then we should return an altered model and (once again) Cmd.none . The first element in this tuple is an example of a record update:

{ model | errors = getErrors model }

You can read this as “Return a version of model where the errors field has been replaced by the result of a call to getErrors model .” (Remember that records are immutable, so an “update” doesn’t actually change them; rather, an “update” refers to returning a separate record which has had the update applied.)

Since our view is already displaying the result of model.errors , and getErrors returns an appropriate list of errors, all that’s needed to perform the validation is to pass this update function a message record like { msgType = "VALIDATE", payload = "" } and we’ll end up displaying the new errors!

Now we’re ready to connect what we’ve done to a click handler to our submit button. We’ll change it from this:

, div [ class "signup-button" ] [ text "Sign Up!" ]

…to this:

, div [ class "signup-button", onClick { msgType = "VALIDATE", payload = "" } ] [ text "Sign Up!" ]

onClick is a function which takes a message—in this case the plain old record { msgType = "VALIDATE", payload = "" } . When the user clicks this button, the record { msgType = "VALIDATE", payload = "" } will be sent to update as the message describing what we want done. update will also recieve the current model, thus connecting view and update .

Finally, you may have noticed that init is currently defined in terms of initialModel , which we haven’t specified yet. initialModel will represent the model we want to have when the application starts up; we can define it right above main like so:

initialModel = { username = "", password = "", errors = initialErrors }

Let’s build and try to use what we’ve put together so far. Alas and alack! As soon as we submit the form, it clears out everything we’ve typed and presents us with validation errors. What gives?

This is because when we submit the form, it re-renders everything based on the values in the current model…and we never actually updated those! No matter how much you type into either the username field or the password field, those values will not make it into the model of their own free will.

We can fix that using the same technique as before. First we’ll add to our update function:

update msg model = if msg.msgType == "VALIDATE" then ( { model | errors = getErrors model }, Cmd.none ) else if msg.msgType == "SET_USERNAME" then ( { model | username = msg.payload }, Cmd.none ) else if msg.msgType == "SET_PASSWORD" then ( { model | password = msg.payload }, Cmd.none ) else ( model, Cmd.none )

Now if this function receives something like { msgType = "SET_USERNAME", payload = "rtfeldman" } , it will update the model’s username field accordingly.

Let’s create an onInput handler (since input events fire not only on keyup, but also whenever a text input otherwise changes - e.g. when you right-click and select Cut), which will dispatch one of these new messages:

, input [ id "username-field" , type' "text" , value model.username , onInput (\str -> { msgType = "SET_USERNAME", payload = str }) ] []

This introduces some new syntax: (\str -> { msgType = …) . This is how you write an anonymous function in Elm. The JavaScript equivalent of this would be:

onInput(function(str) { return { msgType: "SET_USERNAME", payload: str }; })

Now let’s give password the same treatment:

view actionDispatcher model = form [ id "signup-form" ] [ h1 [] [ text "Sensational Signup Form" ] , label [ for "username-field" ] [ text "username: " ] , input [ id "password-field" , type' "password" , value model.password , onInput (\str -> { msgType = "SET_PASSWORD", payload = str }) ] [] , div [ class "validation-error" ] [ text model.errors.username ] , label [ for "password" ] [ text "password: " ]

, input [ id "password-field" , type' "password" , value model.password , onInput (\str -> { msgType = "SET_PASSWORD", payload = str }) ] []

, div [ class "validation-error" ] [ text model.errors.password ] , div [ class "signup-button", onClick actionDispatcher { msgType = "VALIDATE", payload = "" } ] [ text "Sign Up!" ] ]

Now whenever the user types in the username field, a message is created (with its msgType field being "SET_USERNAME" and its payload field being the text the user entered. Remember that messages are just data; they don’t intrinsically do anything, but rather describe a particular model change so that other logic can actually implement that change.

Try it out! Everything should now work the way we want.

One great benefit of this architecture is that there is an extremely obvious (and guaranteed!) separation between model and view. You never have model updates sneaking into your view, causing hard-to-find bugs, because views aren’t even capable of updating models. Each step of the process is a straightforward transformation from one sort of value to the next: inputs to messages to model to view.

It may not be what you’re used to, but once you get in the Elm groove, debugging and refactoring get way easier. Based on my experience writing production Elm at NoRedInk, I can confirm that these things stay easier as your code base scales. If you’re curious about the details of how our experience getting started with Elm went, check out 6 Months of Elm in Production.

Asking a server about username availability

Finally we’re going to check whether the given username is available. Rather than setting up our own server, we’re just going to use GitHub’s API; if GitHub says that username is taken, then that’s our answer!

To do this, we’ll need to use the elm-http library to communicate via AJAX to GitHub’s API. Let’s start by installing it:

elm-package install evancz/elm-http --yes

(The --yes flag saves us time by automatically answering yes to all the prompts.)

We’ll also need to import the Http module below our other import statements:

import Http

Up to this point we haven’t written any code that deals with effects. All of our functions have been stateless; when they are given the same inputs, they return the same value, and have no side effects.

Now we’re about to…continue that trend! Not only does Elm represent messages as data, it represents effects as data too—specifically, using values called Tasks.

Tasks have some things in common with both Promises and callbacks from JavaScript. Like Promises, they can be easily chained together and have consistent ways of representing success and failure. Like callbacks, instantiating Tasks doesn’t do anything right away; you can instantiate a hundred Tasks if you like, and nothing will happen until you hand them off to something that can actually run them. Like both Promises and callbacks, Tasks can represent either synchronous or asynchronous effects.

Running a task has two steps.

Chain as many Tasks as we want together. Wrap the final Task chain in a Command, whose job is to execute the chain.

This is broken down into two steps because some operations in Elm are chainable, while others are not. The chainable ones have a Task-based API, whereas the non-chainable ones expose Commands only. (You’ll encounter these Commands when doing JavaScript interop.

As soon as a Command finishes, Elm will run update passing the Command’s result (translated into a message), and from there we can proceed using the same update techniques we’ve used up to this point.

Let’s update our "VALIDATE" message to fire off a request to GitHub’s API. In normal Elm code we wouldn’t be this verbose about it, but for learning purposes we’ll break everything down into small pieces.

if msg.msgType == "VALIDATE" then let url = "https://api.github.com/users/" ++ model.username failureToMsg err = { msgType = "USERNAME_AVAILABLE", payload = "" } successToMsg result = { msgType = "USERNAME_TAKEN", payload = "" } request = Http.get (succeed "") url cmd = Task.perform failureToMsg successToMsg request in ( { model | errors = getErrors model }, cmd )

That’s a decent bit of new stuff, so let’s unpack it!

First, the conditional is now using a let expression. These allow you to make local declarations that are scoped only to the let expression; the rest of our program can’t see things like url or request , but anything between let and the expression just after in can see them just fine. The whole let expression ultimately evaluates to the single expression after in .

Since url is a plain old string, and failureToMsg and successToMsg are plain old functions, let’s move on to something we haven’t seen before: Http.get . It takes two arguments: a Decoder and a URL string. The URL is what you’d expect, and the decoder is like targetValue from our input event handler earlier: it describes a way to translate an arbitrary JavaScript value into something more useful to us.

In this case, we’re not using a very interesting Decoder because any API response error means the username was not found (and thus is available), and any success means the username is taken. Since the Decoder only gets used if the API response came back green, we can use the succeed Decoder, which always decodes to whatever value you give it. ( succeed "" means “don’t actually decode anything; just always produce "" no matter what.”)

Finally we have the call to Task.perform , which translates Tasks into Commands. But what are those first two arguments we’re passing it?

Like JavaScript Promises, Elm Tasks can either succeed or fail, and Http.get returns a Task that can most definitely fail (with a network error, 404 Not Found, and so on). However, Commands always result in a call to update , and update requires a message! As such, we need to convert both successful Task results and failed Task results into messages.

Fortunately, the Task.perform function does exactly that. It accepts two arguments in addition to the task in question:

A function that translates a failure result into a message (in our case, failureToMsg ) A function that translates a success result into a message (in our case, successToMsg )

Armed with these functions and a Task, Task.perform can give us the Command we desire. We’re all set!

Now when we enter a username, if that username is available on GitHub (meaning the Http.get results in a 404 Not Found error and thus a failed Task), the result is { msgType = "USERNAME_AVAILABLE", payload = "" } being passed to our update function. If the username is taken (meaning the Http.get resulted in a successful Task), the result is that our “always results in usernameTakenTask ” Decoder causes { msgType = "USERNAME_TAKEN", payload = "" } to be passed to our update function instead.

Let’s incorporate these new messages into our update function:

update msg model = … else if msg.msgType == "USERNAME_TAKEN" then ( withUsernameTaken True model, Cmd.none ) else if msg.msgType == "USERNAME_AVAILABLE" then ( withUsernameTaken False model, Cmd.none ) else ( model, Cmd.none ) withUsernameTaken isTaken model = let currentErrors = model.errors newErrors = { currentErrors | usernameTaken = isTaken } in { model | errors = newErrors }

Before we can use all these fancy new libraries, we’ll need to import them at the top:

import Http import Task exposing (Task) import Json.Decode exposing (succeed)

Finally, we need to introduce the “username taken” validation error to initialErrors , getErrors , and view :

initialErrors = { username = "", password = "", usernameTaken = False } getErrors model = … , usernameTaken = model.errors.usernameTaken } view actionDispatcher model = … , div [ class "validation-error" ] [ text (viewUsernameErrors model) ] … viewUsernameErrors model = if model.errors.usernameTaken then "That username is taken!" else model.errors.username

Here’s our final result.

Now the username field is validated for availability, and everything else still works as normal. We did it!

Wrapping Up

We’ve just built a live-validated signup form with the following characteristics:

Clear, obvious, and strict separation between model and view

A compiler that told us about type errors before they could become runtime exceptions

High-performance reactive DOM updates like React and Flux

Live AJAX validation of whether a username is available

Immutability everywhere

Not a single function with a side effect

To save time, we didn’t do everything completely idiomatically. The fully idiomatic approach would have represented messages using the more concise and robust union types and case expressions instead of records and if / then / else expressions, and to document various functions using type annotations.

Here are some great resources for learning more about idiomatic Elm:

By the way, if working on Elm code in production appeals to you…we’re hiring!



Richard Feldman

@rtfeldman

Engineer at NoRedInk

Thanks to Joel Clermont, Johnny Everson, Amjith Ramanujam, Jonatan Granqvist, Brian Hicks, Mark Volkmann, and Michael Glass for reading drafts of this.