What is this tutorial about?

In this tutorial you are going to learn how to create a flashcard app. A flashcard is a

card bearing information on both sides, which is intended to be used as an aid in memorization. Each flashcard bears a question on one side and an answer on the other. Flashcards are often used to memorize vocabulary, historical dates, formulae or any subject matter that can be learned via a question-and-answer format.

Stack

The stack we are going to use in this tutorial is

The reason for this eclectic mix is because it was the preference of @keinegurke_. They wanted to learn both Elm, Rust and German. But an in depth tutorial was missing. Since flashcards could help them improve their vocabulary, they soon settle on building an app and documenting their progress in this tutorial.

Prerequisites

What would you need to start with this tutorial? First of all; in interest in either Elm, Rust, or both. The focus of this tutorial is in learning these technologies. Second is a computer and some experience with running commands in the command line. Modern development relies increasingly on tools that require some proficiency with the command line.

If you qualify, dive right in!

Installing Elm

Since Elm is one of the technologies we are using to create our flashcard app we better start with installing it.

Installation

How Elm can be installed is described in their installation page. This includes setting up elm in a number of editors. Which certainly will come in handy.

What is missing do are some helpful tools. Most notably elm-test and elm-format. elm-test is used to test your Elm code, a practice that provide even more confidence in refactoring your code. elm-format automatically formats your code, so that no wars can erupt over where to place which comma.

You can learn how to install elm-test from their README. Similarly, how to install elm-format is described in their README as well.

Verification

If everything worked out as planned you should be able to execute the following commands

elm --version

This should respond with the latest version. At the time of writing that was 0.19.0.

Similarly, the following commands

elm-test --version

responds at the time of writing with 0.19.0-rev6.

Finally, verifying that elm-format is a little different. The command does not have a --version flag. Instead asking for --help

elm-format --help

Should declare it's version at the top of its help page. Again. At the time of this writing that was 0.8.1.

Celebrate

You are now all set for the next chapter. But before you go and explore the wonderful world of Elm, make sure you celebrate! Configuring your machine can be very hard.

Skeletal Elm project

With all the Elm tools installed. It is time to take them for a spin. Right from the Elm is a very friendly language. When you ask Elm for help

elm --help

It will thank you for trying out Elm, give you some pointers where to find more information and tell you about some command options. We are interested in the elm init command. It promises to

Start an Elm project. It creates a starter elm.json file and provides a link explaining what to do from there.

This is a nice starting point. Run the command to start an Elm project. The skeletal project creates the following files and directories.

.
├── elm.json
└── src

The elm.json describes the Elm project. We describe it in more detail later in the tutorial. Besides the elm.json file there is a directory called src. This is where all the source files go.

An empty src directory is an unhappy src directory. So let's go ahead and create a file named FlashCard.elm inside the src directory with the following content

module FlashCard exposing (..)

import Html


main =
    Html.text "Hello, World!"

Verification

One of the cool things about Elm is that it can serve your files. By spinning up a reactor Elm will look at your files, create a web server, serve your files and when necessary compile them on the fly.

elm reactor

The above command will start the Elm reactor. The message that it returns with asks you to got to http://localhost:8000. Going to that address you will find a file navigation pane. Navigate it to src > FlashCard.elm and be greeting by your very own Hello, World!

A first flash card

Let's focus on how the structure of the Html should look like. In order to use the capabilities of Elm way of doing Html we imported the Html module.

import Html

A module

let you break your code into multiple files

You can define you own modules, but we will wait until the opportunity arises. First we will be using modules defined in packages as found on Elm packages

Html module

The Html module has documentation as found in the Elm packages. It needs a little getting used to, but eventually you will get the hang of it.

If we would write plain Html that would display a flash card, we could write something like this

<div>
  <span>Wettervorhersage</span>
</div>

Note that wettervorhersage is a German word meaning weather forecast. Maybe the snippet of Html is missing some attributes and classes, but for now we will focus on the structure.

To mimick the snippet in Elm, we would write the following code in elm

main =
    Html.div []
        [ Html.span [] [ Html.text "Wettervorhersage" ]
        ]

There are several things going on here. Not that Elm is a functional language. This means that functions are the building blocks of creating programs.

There are three functions used to recreate the Html. Html.div, Html.span and Html.text. Almost all functions in the Html module that produce tags accept two arguments. A list of attributes, and a list of content.

We haven't defined attributes yet so the first argument of Html.div and Html.span is the empty list: []. The second argument of Html.div is a list with content, i.e. a span that itself contains text.

We will have more to say about functions but for now we can verify that everything works as expected.

Verification

When you reload the flash card app in the browser, you should see the German word for weather forecast.

A custom function

We have created our first flash card, but it can only show "Wettervorhersage". Although it is a great word, we would like to be able to show different words. Let's make that possible with the introduction of a custom function.

What we would like to achieve is to write

main =
    view "Wettervorhersage"

and let the view function return the Html for the flash card. For this we need to create a function. Functions are defined by stating their name, i.e. view, and their arguments. For us that would be the text that we want to show, so let's call the argument for our function text.

Next would comes the body of the function and it defined the return value. For us that looks very similar to our first flash card. The only difference is that instead of the hard coded "Wettervorhersage", we want to show the argument, I.e. text.

Putting all this together, the view function comes down together

view text =
    Html.div []
        [ Html.span [] [ Html.text text ]
        ]

Verification

If your reload the application you should still see our flash card that displays "Wettervorhersage". Changing the argument of the view function and reloading should reflect in the app.

Type annotations

We have just created a custom function. It is lacking one of the Elm strongest features. I.e. type information.

Types help a compiler to determine if programs are correct. They can be used to verify that the expectations you have about functions and programs are met, even before the program is run.

Up until now, Elm has inferred the types of our functions. But relying on the type inference could hide some problems. So let's annotate our view function with it's type.

Common types

Elm has some built in types that you can use right out of the gate. One that we already have used is String. It is a chunk of text. Elm also provides a myriad of functions for String but now we are only interested in it's type.

Remember that Elm is a functional language? Because functions are so important you can provide type information for them as well. This is done with an arrow ->.

On the left side of the arrow you place the type of the argument of the function. To the right of the arrow you place the type of the return value. But what is that? Let's guess it to by String and see what Elm makes of it.

Providing type information for the view function is done by

view : String -> String

This is placed right above the definition of the view function.

Verification

If you reload the application you will be greated by a friendly message from the Elm compiler

-- TYPE MISMATCH --------------------------------------------- src/FlashCard.elm

Something is off with the body of the view definition:

11|> Html.div [] 12|> [ Html.span [] [ Html.text text ] 13|> ]

This div call produces:

Html.Html msg

But the type annotation on view says it should be:

String

We guessed the return type for the view function to be String. But that is not correct. Elm noticed that the type difference from what actually is returned. Let's change the type signature of the view function to

view : String -> Html.Html ()

for now. We will go into the convoluted Html.Html () later.

Once you reload the application again. You should be greeted by "Wettervorhersage".

Namespaces

We have just specified the return type of the view function to be Html.Html (). Why are there two mentions of Html? We will answer that question in this chapter.

Look at the top of the file. There is an import line.

import Html

This imports the Html module from the Htmll package. As can be seen from the documentation, this package provides a lot of useful functions. In order to use these functions you have to use there fully qualified name. E.g. if you want to use the function div that will render a div element on the page. You write

Html.div

The reason for using the fully qualified name is to avoid name clashes. Let's illustrate a name clash with an example.

Favorite number

We are going to ask somebody to create a module providing us with their favorite number. Kirsty told us that her favorite number is 42. Daan favorite on the other hand is 37. We could create modules that record these favorites.

We will settle on the name number to record the respective favorite number. If we wanted to use both numbers in one program, we would not have a problem. We can use fully qualify the name and we would still know which number we mean.

If we wanted to use Kirsty's number we would write Kirsty.number and get 42. If instead we wanted to use Daan's number, we would wirte Daan.number.

Second Html

We learned about the first Html in the signature of the view function. It is the Html namespace. A namespace will prevent name clashes between different modules. But what about the second Html.

If you take a look at the Html documentation you will find it exposes a type alias Html. The Html type can be used to annotate functions like our view function that it is returning Html.

Exposing names

Sometimes there is no danger of name clashes. E.g. it will be unlikely that we will using two Html types. In order to shorten the Html.Html part to just the type Html we can expose the Html type into our own namespace.

We can expose names from modules when we import them. In order to expose the Html type from the Html module we would write

import Html exposing (Html)

This allows us to write the signature of the view function ascii

view : String -> Html ()

If you find it tedious to have to fully qualify the div and span functions from the Html module. the import line would read

import Html exposing (Html, div, span)

Verification

In this chapter you learned about namespaces, fully qualified names and exposing names in your own namespace. In order to verify if you have grasped these concept, try to answer the following question.

Why can't we expose the text function from the Html module?

A record type

We annotated our view function with it's type, but something does not feel right. Shouldn't a flash card have a front and a back? Just a single String does not cut it.

Let's remedy this with a record.

Records

A record

can hold many values, and each value is associated with a name.

Our flash card has a front and a back, so describe the flash card type with record seems a nice fit.

Instead of accepting a String as the argument to our view function, we should accept a record that has a front and a back.

view : { front : String, back : String } -> Html ()

Now the argument name of text isn't very well chosen anymore. Lets change it to card.

view card =
   ...

Since we renamed the argument from text to card, we can't use text in the body of the function anymore. Let's replace it with the front of the card

    div []
        [ span [] [ Html.text card.front ]
        ]

We now introduced a new type; a record with a front and a back, both of type String. But we haven't changed to argument we are calling the view function with.

main =
    view { front = "Wettervorhersage", back = "Weather forecast" }

Verification

If you reload the web app once more. You should still see "Wettervorhersage".

Type alias

Our flash card type now is { front : String, back : String } which, to say the least, does not roll off the tongue easily. Instead we would like to name our flash card type FlashCard.

This can be achieved with a type alias. A type alias will introduce a name for a different type. This has several benefits. Among them it allows you to have a recognizable name for a type.

If you want to introduce a type alias you use the type alias keywords. In our case you could use it like

type alias FlashCard =
    { front : String, back : String }

With the type alias introduced we can clean up the signature of the view function by changing it to

view : FlashCard -> Html ()

Verification

Fundamentally, nothing changed to the program, we only introduced a name for a familiar thing. The application should still show "Wettervorhersage" when reloaded.

A custom type

We have created a fuller flash card. It has a front and a back. But what way is it facing? Are we seeing the front or the back?

If we want to differentiate we should be able to express the difference. We will do that with a custom type.

Face

The following code introduces a new type Face. It will be used to determine which way our flashcard is facing.

type Face
    = Front
    | Back

The type construct is an example of an algebraic data type. That is a fancy name saying that there are certain ways of combining different types into more complex types. We will come back to that subject later on.

Extending FlashCard

With a Face we should extend our FlashCard model. In the FlashCard type alias add a field face of type Face.

type alias FlashCard =
    { front : String
    , back : String
    , face : Face
    }

This will break our program. This is because when we created a flash card we should also specify which way it is facing.

{ front = "Wettervorhersage", back = "Weather forecast", face = Front }

Verification

If it compiles, it should still show you "Wettervorhersage" when you reload the app.

A convenient function

If we want to create a flash card we have to write

{ front = "Wettervorhersage", back = "Weather forecast", face = Front }

That is a whooping 71 characters. Most of these characters are boilerplate. Can't we do better than that?

Functions to the rescue

Of course we can do better. We can create a function that helps us with creating a flash card.

Let's think about a name for the function. It creates a flash card so flash_card seems appropriate.

Having settled on the name let's think about the signature. We would create flash cards facing Front. So the only parts that are moving are the front and the back. This means our functions needs to accept two Strings and returns a FlashCard. The way the describe that in Elm is

flash_card : String -> String -> FlashCard

It needs some getting used to, but there are good reasons why the signature looks the way it does.

With the signature in place, we can go on to defining the function.

flash_card front back =
    { front = front, back = back, face = Front }

Verification

We can now use the flash_card function. We do need some parenthesis, or else Elm will get confused.

`main =
    view (flash_card "Wettervorhersage" "Weather forecast")

Other than that, everything should still work. As an other verification: what if we wanted to pass which way the flash card is facing to our flash_card function, how would the signature change?

Showing the back

We have been staring at Wettervorhersage for a while now. We would like to convince ourselves that we still know it's translation. Time to leverage the face of our FlashCard and show a different side.

Let expression

In our view function we hard-coded to show the front of the flash card.

view card =
    div []
        [ span [] [ Html.text card.front ]
        ]

We like to be able to change which text we show depending on the face that is showing. For this we are going to introduce a let-expression in our view function.

A let expression is useful when

when an expression is getting large. You can make a let to break it into smaller definitions and put them all together in a smaller expression.

We first use it to introduce a name for the text we want to show.

view card =
    let
        text = card.front
     div []
        [ span [] [ Html.text text ]
        ]

In it self this isn't a big change. It only allows us to define a name. But the value we bind this name is now under our control.

Showing the back

We would like to show the a different text when we face the back.We can achieve that by matching on the flash cards face.

text =
    case card.face of
        Front ->
            card.front

        Back ->
            card.back

this makes use of new concept: Pattern matching. A case expression

allows us to branch based on which variant we happen to see.

So if our card faces Front we want our card to show the card.front. And when it faces Back we want to see card.back.

Verification

Make a prediction what would happen when you reload the web application. If your prediction matches the reality you probably know what is up next.

Flip the card

When we reloaded the application we still saw Wettervorhersage. The reason for this is that the model we are viewing hasn't changed. In order to see something different we need to flip the card.

A flip function

Let's create a function that will do the flipping for us. We start by thinking about the signature, i.e. what goes in the function and what should come out. Since we want to flip a FlashCard picking that as a argument to our function makes sense. The result should be a FlashCard again. So our flip function has a signature of

flip: FlashCard -> FlashCard

When we start thinking of the implementation we quickly run into the problem of determining the opposite of a face. I.e. when we are facing Front the opposite should be Back and vice versa.

Let's cater to that now and so that we can use that function in the definition of flip.

An opposite function

Again, we start thinking of the signature of our function. In should go a Face and out should come a Face again.

opposite: Face -> Face

We just learned about the let-expression, which comes in handy now. Because it allows us to determine which value occurs so we can take action accordingly.

opposite : Face -> Face
opposite face =
    case face of
        Front ->
            Back

        Back ->
            Front

flip reloaded

With a new brush in our art kit we are able to continue our implementation of flip. We should return a FlashCard with the same front and back as our input, but with the opposite face.

flip card =
    { front = card.front, back = card.back , face = opposite card.face }

Pfew, that was a lot of typing. Isn't there a better way? I certainly would not want to type all those fields if our model grows.

flip revolutions

Luckily Elm allows that. It basically allows us to say; take this flashcard and only modify the fields we are interested in. The syntax is shown below and is exactly similar to the code above.

flip card =
    { card | face = opposite card.face }

Sweet!

Verification

We have a flip function, but we need to use it.

main =
    view (flip (flash_card "Wettervorhersage" "Weather forecast"))

Reloading the web application now will show "Weather forecast", but at a cost of a lot of parenthesis. Elm has a way to form a pipeline. Below you can see how it works.

main =
    flash_card "Wettervorhersage" "Weather forecast"
        |> flip
        |> view

Here we first create a flash card with the flash_card function. We pass that flash card to the flip function via the |> operator. The result of the flip function is in its turn passed to the view function via the same |> operator.

We will take a closer look at the |> operator and other functions.

Elm Architecture

We are capable to flip a card, but we want to control when we flip it. At the moment we are only generating static content. How does Elm allow values to change.

In this chapter we will look into the Elm architecture.

Model-View-Update

The Elm architecture is a pattern that help in structuring an interactive application.

It revolves around a Model, a View and a way to Update the model. Two of the parts are in place already. Our model is a FlashCard with view as it's View function.

What is missing is the Update function and how it interacts with Elm.

Pure functions

Functions in Elm are pure. A pure function is

a function that has the following properties:

  1. Its return value is the same for the same arguments (no variation with local static variables, non-local variables, mutable reference arguments or input streams from I/O devices).
  2. Its evaluation has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or I/O streams).

This property has some nice benefits. But how do we change things, if functions should always return the same on the same input?

Elm runtime

The Elm runtime is the escape hatch that allows change in our web app. The runtime is responsible for the Model. It will use a View function to show our application. It will Update the model in the way we tell it to.

The way we tell it to is by sending a Message. This Message allows the Update function to differentiate how to update the model.

We will be sending Message from the application, e.g. by clicking a button.

This probably all sounds very abstract. It will become clear in the next chapter. Note that every Elm application is structured, even recursively, with the Elm architecture. This helps developers in managing their code base.

Updating the model

Let's work on applying the Elm architecture and update the model.

Message

We first start to think about the message our model understands. At the moment we are only interested in flipping a card. A good message would be Flip. Let's start from there

type Message
    = Flip

Now we created the ability to express the fact that we want to change our model.

Model

We keep talking about our model. We know that it is a flash card, but that could change in the future. Aligning our code with the Elm way has some benefits. So let's introduce an alias.

type alias Model =
    FlashCard

This way we can talk about Model in our signatures and it will align with Elm, but we haven't lost the ability to address is as a FlashCard.

Update

Let's create an update function. The signature of the update model is determined by Elm. It should benefits

update : Message -> Model -> Model

We give it a Message and a Model. Update will return a Model depending on the two. The implementation is straight forward, since there is only one instance of the Message type.

update message model =
    case message of
        Flip ->
            flip model

Match on the message, and for each case, even when there is only one, determine what to do. The flip function is what we need in the Flip case.

Wire it all together

Now we need to tell Elm how these parts work together. There is a Browser module that offers different programs. We will be interested in sandbox.

In order to be able to use the Browser module we need to import it.

import Browser

The sandbox needs a structure with the following fields

  • init. The initial model.
  • view. The view function used to display the model.
  • update. an finally the update function used.

We all have those in place. So we can

Verification

Change the main variable to

main =
    let
        model =
            flash_card "Wettervorhersage" "Weather forecast"
    in
    Browser.sandbox
        { init = model
        , view = view
        , update = update
        }

And now we can finally flip the flash card.

Building the frontend

A browser can not execute Elm. We first must make our Elm code into a form that the browser will understand.

Luckily the elm command knows how to do that.

elm make

There is a sub-command that is able to create files that the browser knows about. It is called elm make. Running elm make --help show

The `make` command compiles Elm code into JS or HTML:

    elm make <zero-or-more-elm-files>

For example:

    elm make src/Main.elm

This tries to compile an Elm file named src/Main.elm, putting the resulting
JavaScript code in an elm.js file.

We left out the flags that can be passed to this sub command.

Let's use the sub command to make an index.html. This way we don't need to reactor to show our app.

elm make src/FlashCard.elm

The command above should create an index.html in the current work directory.

Verification

Open the just generated index.html with a browser, and check that Wettervorhersage still means Weather forecast.

Installing Rust

We will use Rust to create a web server. Before we can start writing our web server, we should install Rust.

Installation

Rust can be installed and updated via a handy tool called rustup as described on the installation page. This allows you to have multiple different versions of Rust live next to each other.

When you execute the installation command, the install script will asks some questions. Make sure you read and understand what the script will try to achieve. For this tutorial no customization is necessary.

When the scripts finished Rust is installed. This comes with a couple of tools.

Verification

To make sure that the installation was successful the following commands should run without error.

cargo --version

The above command should respond with the installed cargo version. At the time of this writing that was 1.38.0.

rustc --version

Should respond with the same version.

Creating a client directory

What we have been working on up until now is the client for our application. With Rust installed, we quickly will move into writing server code. We better setup our project source tree to reflect our intentions.

This entails little more than to creating a client directory and moving our stuff over there.

Verification

Make sure that running the Elm reactor, or building the frontend still works as expected.

Skeletal Rust project

We used our frontend tooling to create a skeletal project for us. Just like with our frontend, our backend tooling allows us to do the same.

cargo new

The cargo command is the go to place for Rust development.

cargo --help

Shows all sort of sub-commands available. The one we are interested in is the cargo new command.

cargo new --help

shows use

Create a new cargo package at

It has some options that we are going to use. The following command

cargo new --bin --name 'flashcard-server' server 

will produce the structure below.

server
├── Cargo.toml
└── src
    └── main.rs

The Cargo.toml files describes the project and there already is a main.rs source file scaffold.

Verification

Go into the server directory and run the

cargo run

command. This will compile your sources and greet you with a friendly Hello, world!.

Simple Server

In this chapter we are going to create a simple server. Our goal is to make it greet us when we make a request.

Gotham

We will be leveraging the Rust eco-system instead of reinventing the wheel our self. There are a lot of packages available to us. They live at crates.io. One of the packages we will be using is Gotham.

Gotham is a

flexible web framework that promotes stability, safety, security and speed.

Add dependency

Before we can use a package we need to declare it as a dependency. This is done in the Cargo.toml. We need to add our dependency to the dependencies table.

[dependencies]
gotham = "0.4.0"

Hello, world!

Next we will create a minimal server that responds with Hello, world! with every request.

Change main.rs with

extern crate gotham;

use gotham::state::State;

const HELLO_WORLD: &'static str = "Hello World!";

pub fn say_hello(state: State) -> (State, &'static str) {
    (state, HELLO_WORLD)
}

pub fn main() {
    let addr = "localhost:8886";
    println!("Listening for requests at http://{}", addr);
    gotham::start(addr, || Ok(say_hello))
}

This is taken from the hello-world example from the Gotham page.

Verification

Run the server with the command

cargo run

This will compile main.rs and the transitive dependencies. This can take some time. When it finishes it announces

Listening for requests at http://localhost:8886

Now open a browser and navigate to http://localhost:8886 and you will be greeted with an Hello, world!.

Serving the frontend

Wouldn't it be interesting to serve the frontend from our server? Let's work on that in this chapter.

For this it is best to have an assets directory that will hold all of our static assets. In the server directory create an assets directory and move the index.html to it.

assets/
└── index.html

Serving static files

Gotham allows us to define a router. A router allows you to control which handlers get to handle certain requests. In order to use it we need some imports.


# #![allow(unused_variables)]
#fn main() {
use gotham::router::builder::{build_simple_router, DefineSingleRoute, DrawRoutes};
#}

We don't need the HELLO_WORLD, or say_hello, so we can get rid of it. Instead we will define the router to serve our index.html. We use the build_simple_router factory for that.


# #![allow(unused_variables)]
#fn main() {
let router = build_simple_router(|route|{
    route.get("/").to_file("assets/index.html");
});
#}

The build_simple_router accepts a closure that enables us to configure the routes. We want to respond with index.html when we make a get request.

Server with routes

We now need to start the server with our router.


# #![allow(unused_variables)]
#fn main() {
gotham::start(addr, router);
#}

Verification

Restart the server by shutting it down and running cargo run again. This will recompile the server and start waiting for requests. Open your browser and open the application. It should serve the frontend application.

Decoding a FlashCard

We would like the let the server decide which flash card to show. In order for that to work, we need to be able to receive JSON data and decode that into a FlashCard.

This chapter we will be working on that.

json package

There is a json package that will help us decode, and encode, JSON into datatypes that we are interested in. Before we can use it we need to install it.

elm install elm/json

Json.Decode

We will be using the Json.Decode module from the json package. Because we want to avoid typing Json.Decode everywhere when we want to refer to a type from that module, we are importing it with a different name.

Since we will be defining a Decoder we will expose that type into our name space.

import Json.Decode as Decode exposing (Decoder)

Decoder

We want a Decoder for FlashCard. The type therefore should be

decode : Decoder FlashCard

Decoders work by composing primitive decoders into more elaborate once. Primitive decoders include

  • Decode.string that will decode a string value.
  • Decode.int that will decode an int value.
  • Decode.bool that will, well you can probably guess what type of value it decodes.

More elaborate decoders are for example Decode.map or variants. It's signature is

Decode.map : (one -> other) -> Decoder one -> Decoder other

It takes a function that maps one type into the other, a decoder for one and will produce a decoder for other. Under the cover the one decoder is used and when successful the transformation is applied to the result.

JSON

Below we will give an example of the JSON we expect to receive. It probably does not surprise you that it looks like

{ "front": "Wettervorhersage", "back": "Weather forecast", "face": "Front" }

Decoder FlashCard

We will create a decoder that will ignore the face field of the JSON. When we have a little more experience we will come back and decode that as well. That means that the decoder becomes

decode : Decoder FlashCard
decode =
    Decode.map3 FlashCard
        (Decode.field "front" Decode.string)
        (Decode.field "back" Decode.string)
        (Decode.field "face" <| Decode.succeed Front)

We use Decode.map3 and the FlashCard constructor, which got automatically created when we introduced the type alias, to map three different values into a FlashCard.

We extensively use the Decode.field decoder. It

decodes a JSON object, requiring a particular field. The object can have other fields. Lots of them! The only thing this decoder cares about is if a field is present and that the value there is an of a certain type.

Lastly we use Decode.succeed to hard code the face value to be Front.

Verification

We have created a decoder but how do we know that it behaves correctly? We can write a test to see if our decoder can decode some JSON.

In tests/FlashCardTest.elm write the following code after our other test.

        , test "decode a flash card from JSON" <|
            \_ ->
                let
                    input = """{ "front": "Wettervorhersage", "back": "Weather forecast", "face": "Back" }"""

                    expected =
                        flash_card "Wettervorhersage" "Weather forecast"
                        |> Ok

                    actual =
                        decodeString FlashCard.decode input
                in
                Expect.equal actual expected

and all the tests should still work when you import

import Json.Decode exposing (decodeString)

Testing Elm

We would like to send data from the server to the client, interpret the data as a flash card. There are a lot of things that can go wrong. In order to gain some confidence that some things are going right, we can write automated tests.

This chapter will setup everything that is needed to start writing tests for our Elm code.

Using elm-test

We have installed elm-test but we haven't used it yet. Now is a good time. Go over to the client directory and run the following command

elm-test init

This will ask to update elm.json with packages that enable us to writing tests. Agree with the proposed plan. When the command finished there is a new directory tests.

tests/
└── Example.elm

We can rename Example.elm to FlashCardTest.elm

run the test suite

Running the test suite amounts to executing the command

elm-test

It responds with the following overview

TEST RUN INCOMPLETE because there is 1 TODO remaining

Duration: 253 ms
Passed:   0
Failed:   0
Todo:     1
↓ FlashCardTest
◦ TODO: Implement our first test. See https://package.elm-lang.org/packages/elm-explorations/test/latest for how to do this!

The reason it reports that there is a TODO is because the generated test file provided it. Let's change that

Write a Test

Replace the todo with the following code

    describe "FlashCard"
        [ test "updating a flash card with a `Flip` message flips the flash card" <|
            \_ ->
                let
                    a_flash_card =
                        flash_card "Wettervorhersage" "Weather forecast"

                    expected =
                        flip a_flash_card

                    actual =
                        update Flip a_flash_card
                in
                Expect.equal actual expected
        ]

Let's start understanding this from the top. First is the describe function. It accepts a String that should tell you what is under test and a list of other tests.

Next we see a test function. It also accepts a String that should tell you what we are testing. Furthermore, test accepts as it's second argument a function that can do the actual testing.

In our case the function is a closure, i.e. an anonymous function. In the function we use a let-expression to set up some variable bindings which we use to express our expectations. This is achieved with the Expect.equal function.

For this to work we need to import our precious FlashCard functions and types. We can do this with the following line

import FlashCard exposing (..)

Verification

We have learned a lot about how a test is written, but does it work? Run the elm-test command and it should report

TEST RUN PASSED

Duration: 214 ms
Passed:   1
Failed:   0

Model on server

If we want the server to send us a flash card, the server first have to have a notion of what to send.

In this chapter we our going to model flash cards on the server.

lib.rs

The skeletal project generated a main.rs. It would be possible to write all the necessary code in this file, but that would quickly become unmaintainable. Rust recognizes this fact and allows for code to write in a library instead.

Create a file lib.rs in the src directory with the following content


# #![allow(unused_variables)]
#fn main() {
mod domain;
#}

The above definition defines a module called domain. Let's go ahead and create it.

domain/mod.rs

There are various ways to define a module. One way is to create a directory in the src directory with the same name as the module, in our case domain, and a file mod.rs in that directory.

struct

We would like to focus on modeling flash cards on the server. We have some experience on the client side. Luckily, that experience translates very well to the server side.

Rust also knows about records. Rust calls them structs. Take a look at the following code


# #![allow(unused_variables)]
#fn main() {
pub struct FlashCard {
    front: String,
    back: String,
    face: Face,
}
#}

You probably recognizes a lot from Elm's FlashCard. There are two noteworthy things two point out.

First the pub keyword. This allows other modules to use this struct. The other is the Face type. Rust doesn't know about it. So we better define it right away.

enum

Our modeling experience on the Elm side helps us on the Rust side as well. Again, there is a naming difference, but take a look at Face


# #![allow(unused_variables)]
#fn main() {
pub enum Face {
    Front,
    Back,
}
#}

Constructor

When we created our model in Elm, we quickly realized that it becomes tedious to spell out the structures every time we needed it. To mitigate that we created a constructor. Let's do the same in Rust.

It looks a bit different, but is similar enough that it is recognizable.


# #![allow(unused_variables)]
#fn main() {
impl FlashCard {
    pub fn new<S>(front: S, back: S) -> Self where S: Into<String> {
        Self { front : front.into(), back : back.into(), face : Face::Front }
    }
}
#}

The essence is the following line


# #![allow(unused_variables)]
#fn main() {
        Self { front : front.into(), back : back.into(), face : Face::Front }
#}

It looks a bit strange with the front.into() and back.into() but that is because Rust knows different kinds of strings. The input type S : Into<String> allows us to conveniently add any type that knows how to transform itself into a String.

Verification

We haven't changed our server and only added code. It should still be able to build our code.


# #![allow(unused_variables)]
#fn main() {
cargo build
#}

The command above should do just that. Other than given some warnings about our new code not being used, it should compile our code.

Serialize FlashCard

We have modeled flash cards on the server. We now need to turn it into JSON before we can send it to the client.

In this chapter we will use serde to automatically serialize our flash cards.

Dependency

We will need two dependencies that we need to list in our Cargo.toml. Together they allow us to serialize flash cards into JSON. Add serde and serde_json to the dependencies table in Cargo.toml.

serde = { version = "1.0.101", features = [ "derive" ] }
sserde_json = "1.0.41"

Use serde

In order to leverage the serialization capabilities of serde_json we need to create a serde serializer. There is a very cool trick that serde knows that makes this a breeze.

The first thing we need to do is to announce we our needing serde.


# #![allow(unused_variables)]
#fn main() {
use serde::Serialize;
#}

The other thing is that we need to tell serde that we would like it to derive serializers for our data types. We can do this by annotating both FlashCard and Face with a derive annotation. E.g. Face would be annotated like


# #![allow(unused_variables)]
#fn main() {
#[derive(Serialize)]
pub enum Face {
    Front,
    Back,
}
#}

Verification

With the automatic part of serde done, it is good to see if serialization works like we expect. The best way to do this is to test is. Rust integrates testing right at the place where the source is created. Add the following code at the end of src/domain/mod.rs.


# #![allow(unused_variables)]
#fn main() {
#[cfg(test)]
mod test {
    use super::*;
    use serde_json;

    #[test]
    fn flash_card_can_be_serialized_to_json() {
        let flash_card = FlashCard::new("Wettervorhersage", "Weather forecast");

        let actual = serde_json::to_string(&flash_card).unwrap();

        let expected = r#"{"front":"Wettervorhersage","back":"Weather forecast","face":"Front"}"#.to_owned();
        assert_eq!(actual, expected);
    }
}
#}

There is some boilerplate to let the test framework know about what we want to test. The actual test is


# #![allow(unused_variables)]
#fn main() {
        let flash_card = FlashCard::new("Wettervorhersage", "Weather forecast");

        let actual = serde_json::to_string(&flash_card).unwrap();

        let expected = r#"{"front":"Wettervorhersage","back":"Weather forecast","face":"Front"}"#.to_owned();
        assert_eq!(actual, expected);
#}

It creates a FlashCard, serializes it to JSON with serde_json, sets up an expectation, and checks it the expectation is met.

If you run

cargo test

it should give the all green.

Sending FlashCard

We are capable to serialize a flash card to JSON. The next step is to send it to the frontend. We will do this by making an end point where the frontend can request a flash card.

This probably will be a somewhat longer chapter because there are a lot of moving pieces. So make sure to fetch yourself a nice drink and strap in for the ride.

Dependencies

We will be using some dependencies that need to be declared in the Cargo.toml. Specifically the following dependencies

hyper = "0.12.35"
mime = "0.3.14"

Create a new route

The next step is to dream up a new route. Where should the frontend get a flash card from. We are offering an API and it is reasonable to adhere to the REST model. One suggestion is to choose /api/flashcard.

In main.rs we have configured a router. We can extend it with a route for a flash card


# #![allow(unused_variables)]
#fn main() {
route.get("/api/flashcard").to(get_flash_card);
#}

the get_flash_card handler is something that does not yet exist. But it is something we like. In order to use it we should do

  1. Announce the use of an external crate. In main.rs at the top add

# #![allow(unused_variables)]
#fn main() {
extern crate flashcard_server;
#}
  1. Use the get_flash_card function. Also in main.rs add an other use expression

# #![allow(unused_variables)]
#fn main() {
use flashcard_server::handler::flashcard::*;
#}

Create a new module

From the usage we learn that there should be a handler. So we need to announce that in out lib.rs


# #![allow(unused_variables)]
#fn main() {
pub mod handler;
#}

We will create this module by using a sub-directory handler that contains a mod.rs file. We probably are going to create a lot of handlers for this applications so the contains of the handler/mod.rs could look like this.


# #![allow(unused_variables)]
#fn main() {
pub mod flashcard;
#}

This just announces an other module. This time do, we are not creating a sub-directory. Instead we are creating a file named flashcard.rs in the handler directory. This is the other way of creating a module.

flashcard.rs should have our definition of get_flash_card.

get_flash_card

A handler has a particular signature for the gotham web framework to work with it. In goes a State, a provided by gotham. At goes a tuple of a state, and whatever you want to return. All in all this amounts to the following code.


# #![allow(unused_variables)]
#fn main() {
use gotham::state::State;
use crate::domain::FlashCard;


pub fn get_flash_card(state: State) -> (State, FlashCard) {
    let flash_card = FlashCard::new("Kodieren", "Encode");

    (state, flash_card)
}
#}

Here we create a new flash card and remove it with the state unaltered.

Response

The web is build about requests and responses. Each response can be of a certain MIME type, but it can't know everything that could be send back. So we will need to tell gotham how to create a real response from a flash card.

There is a trait that you can implement that does precisely that. It is called IntoResponse and it will allow us to specifiy how to turn a flash card into a response.

In order to use it we need to declare it, and related type.


# #![allow(unused_variables)]
#fn main() {
use gotham::handler::IntoResponse;
use gotham::helpers::http::response::create_response;
use gotham::state::State;
use hyper::{Body, Response, StatusCode};
#}

Next we should implement the IntoResponse trait.


# #![allow(unused_variables)]
#fn main() {
impl IntoResponse for FlashCard {
    fn into_response(self, state: &State) -> Response<Body> {
        create_response(
            state,
            StatusCode::OK,
            mime::APPLICATION_JSON,
            serde_json::to_string(&self).expect("serialized flash card"),
        )
    }
}
#}

Here we use the create_response function to create a valid response, which contains JSON with a body of a string representation of the flash card.

Verification

When you restart the application and open a terminal you should be able to execute the following curl command

curl -X GET http://localhost:8886/api/flashcard

and the server should respond with

{"front":"Kodieren","back":"Encode","face":"Front"}⏎

Moving out of the sandbox

Up until now we have been relying on Elm's sandbox. The sandbox allows for a user to interact with the component in a limited way. For example, it is not possible to make an HTTP call.

We would like to do just that, so it is time to move out of the sandbox.

Browser.element

The next step up on the Elm ladder is Browser.element. From the element documentation we learn

Unlike a sandbox, an element can talk to the outside world in a couple ways

We will give a brief description of those ways, paying attention which are relevant for us.

Commands

Commands are a way to request the Elm runtime to do a task. Making an HTTP call is an example.

Commands are a way to describe a certain action. This keeps your program pure, i.e. function always return the same value when called with similar arguments. The Elm runtime will respond by performing the command you specified.

The Elm runtime notifies you of the result by calling the update function.

Subscriptions

Sometimes you are interested in something your component is not responsible for. E.g. ticking of time, or mouse clicks. If you are interested in these things you can subscribe to these events.

Again, Elm runtime notifies you of an event from a subscription by calling the update function.

Flags

Let's we are creating our app and our customers want there name embossed on the flash card. The functionality would not change, besides that our model would keep track of the customer name.

One way to configure our application is by sending flags. This allows an application to change behavior right from the start. E.g. one could pass in via a flag the address one should retrieve flash cards from.

Ports

If you want even more communications with the outside world, ports allow you to send and receive messages. They are the defacto way of communicating with anything outside the control of the Elm runtime.

For example if you want to make use of a JavaScript library that does not have an native Elm counterpart one should use ports to establish a bridge

Updating update

The update function needs to be updated because it needs to be able to fire of commands. This dual responsibility is reflected in the signature. For completeness we show the current signature.

update : Message -> Model -> Model

Besides updating the model, We could potentially also create a command, to be executed by the Elm runtime. The new signature is

update : Message -> Model -> ( Model, Cmd Message )

This will break the implementation. For now it is enough to create a tuple with as second component Cmd.none.

Flip ->
    ( Maybe.map flip model, Cmd.none )

Default subscriptions

At the moment we don't want to subscribe to anything. We can tell Elm that by configuring Sub.none, similar to Cmd.none.

In order to prepare for future extension we will be creating a subscriptions definition that we will reference when we are creating a Browser.element.

subscriptions : Model -> Sub Message
subscriptions _ =
    Sub.none

Updating init

When we will introduce a Browser.element our current init field from the Browser.sandboxdoes not suffice anymore. Again. We could kick off our entire process by doing a request. Furthermore, our initial model could depend on some flags.

For now we want to ignore the flags, and don't want to send a command. It suffices to change our init to.

init = \_ -> ( model, Cmd.none )

Replace Browser.sandbox with Browser.element

Now we can replace Browser.sandbox with Browser.element. It will still complain, because it is missing a subscriptions field. But we prepared for that.

All in all our program definition looks like

Browser.element
    { init = \_ -> ( Nothing, cmd )
    , view = view
    , update = update
    , subscriptions = subscriptions
    }

Verification

Building the app and opening the application would still allow you to learn the translation of Wettervorhersage.

Prepare to receiving a FlashCard

We have transformed our sandbox into an actual element. This allows us to make HTTP calls. We could put that to good use because our server is ready to send us a flash card, we only need to ask.

In this chapter we will make a request for a flash card to our server, receive it and display it in our client.

Changing the model

Currently we assume we always have a flash card. Put since we are planning to receive one over the internet, things could go wrong. In order to be honest about this fact, we are going to change our model.

Currently we our model is an alias for FlashCard

type alias Model =
    Maybe FlashCard

Let's modify it to reflect that we maybe don't have a flash card all the time.

type alias Model =
    Maybe FlashCard

This breaks a few things that we need to fix.

view

The view accepts a model so we need to adopt it to our new definition.

Earlier we could just take the flash card and display a meaningful representation. Now we first must make sure that we actual have a flash card and show a helpful message when we don't.

We can achieve this with a pattern match. Maybe has two constructors, in our case Just card, where card is a FlashCard or Nothing. So our view functions needs to start with a pattern match like so

view : Model -> Html.Html Message
view model =
    case model of
        Just card ->
            -- details below

        Nothing ->
            -- details below

The Just card branch should do what we always did, changed to reflect the use of the card variable.

let
    text =
        case card.face of
            Front ->
                card.front

            Back ->
                card.back
in
div []
    [ span [] [ Html.text text ]
    , button [ Event.onClick Flip ] [ Html.text "flip" ]
    ]

When we don't have a flash card, i.e. in the Nothing branch we need to report this fact to the user.

div [] [ span [] [ Html.text "No flash card to show" ] ]

This wraps up our view function.

update

Our update function also accepts a model, so it needs to change as well. Before we could just call the flip function on our model when we received a Flip message.

Now, because it is wrapped in a Maybe type, this will not work. The types don't work out.

Luckily the Maybe type has a convenient function called map. The signature of map is

(a -> b) -> Maybe a -> Maybe b

which allows use to

Transform a Maybe value with a given function

I.e. allow us to apply a function to Maybe FlashCard with out unpacking it first. Our update function can therefore be

update : Message -> Model -> ( Model, Cmd Message )
update message model =
    case message of
        Flip ->
            ( Maybe.map flip model, Cmd.none )

Initial model

In our main function we our creating a model by hand. At the moment it is just a flash card, but it needs to account for the fact that maybe it isn't present. The most dramatic change would be to pick Nothing as our starting value.

    Browser.element
        { init = \_ -> ( Nothing, Cmd.none )
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

Verification

When you build the client and serve it from the server, this time you should see a message that there isn't a flash card.

Let's remedy that in the next chapter.

Receiving FlashCard

All the pieces are aligned for receiving a flash card from the server. Let's dive right into it.

New message

The Elm runtime needs a way to tell us that there is a new flash card from the server. The Elm runtime wants to do that by sending the update function a message. Let's create a message for that specific event.

We want to be notified from a result from the server. Since we will be receiving it over the network, a lot of things can go wrong. To acknowledge this we will be making use of an other Elm type: Result

A Result is the result of a computation that may fail. This is a great way to manage errors in Elm.

Since the errors that occur will be coming from the network, we will be needing types from the standard library.

import Http

Let's use it in our Message. We will be creating a message that the Elm runtime can use to inform us.

type Message
    = Flip
    | Received (Result Http.Error FlashCard)

The new addition is Received. It has a parameter of type Result Http.Error Flashcard. This tells us that we will receive the result of a "computation" that when successful will return a FlashCard.

Creating a command

At the moment we initialize our main definition with Nothing as a model. We would like to receive a flash card from the server. So we will command the Elm runtime to fetch it for use.

We do need to tell the Elm runtime how to do that. We can do that by returning an actual command instead of Cmd.none. Let's focus on the let-block in the main function.

let
    cmd =
        Http.get
            { url = "/api/flashcard"
            , expect = Http.expectJson Received decode
            }
in

Here we create a command cmd by calling Http.get. It needs a structure with an url field. You might recognize the URL from when we routed it in the server.

The structure also needs an expect field which needs to be a Http.Expect a. One way of creating such an Expect is with the Http.expectJson function.

In it's turn the expectJson function needs two arguments. The first argument is a way to turn a Result Http.Error a into a Message. Remember that we created a Recieve message constructor. This constructor is precisely the function we need. The second argument is telling the Elm runtime how to transform the JSON from the server into a type we are interested in. Remember that we created a Decoder FlashCard for that. It was bound to decode.

This wraps up the creation of the command. Let's focus on sending it.

Sending a command

Sending the command is a bit anti-climactic. It is nothing more than replacing the Cmd.none in the init field of the structure fed to the Browser.element function with our cmd.

init = \_ -> ( Nothing, cmd )

Verification

This is the moment of truth. Building the client and restarting the server should, when you open the application, show a flash card. It isn't the by now familiar Wettervorhersage one.

Instead it should show you the flash card defined on the server. It is placed there to remind you of a job well done.

Docker

We are able to ask for a flash card from the server and show it in the client. That is amazing! If you haven't already celebrated that, go ahead and do that now.

One of the improvements still ahead of us is being able to show many flashcards. At the moment the flash card shown is hard coded on the server. We would rather be able to retrieve it from a database.

We will be installing docker. Docker allows us to run applications, such as a database, inside of a container. This elevates us from keeping our machine up to date with the latest database software. Instead, we can just run a different container.

Install

Tot install docker you can visit their installation guide. It provides many option that are to numerous to reproduce here. So go ahead and follow the instructions that fits your specific situation.

Verification

You can verify if the installation was successful by running the following command

docker --version

It should respond with the version of docker that you installed. For me it responds with

Docker version 18.09.7, build 2d0083d

With docker installed, we are all set to starting containers.