Lars Lillo Ulvestad
Lars Lillo Ulvestad
Developer and digital storyteller. Works as a front-end developer at Kantega.

Part 2: How to initialize data from IHP directly to Elm

Generate types, encoders and decoders for Elm automatically in IHP.
Published: Saturday, 19 December 2020
13 minute read
Article cover photo

This is part 2 of the series IHP with Elm

When initializing Elm, you can set initial values through something called flags.

For this part of the series, I want to show you a technique for loading these values called flags directly from IHP into Elm in a type-safe way.

And what's even cooler, we will also enable you to generate Haskell types to Elm without writing any decoders or encoders manually 😍

Starting out simple

IHP is a full-stack web framework, and Elm should as mentioned in the previous post not be used for absolutely everything view-related in an IHP app.

In IHP, use Elm only when you start to think "I actually need Elm". That will keep the complexity down and let you use Elm for what it's great for.

All this being said, the examples in this tutorial are made extremely simple to make the process easier to follow.

Continue from part one

If you haven't done part 1 of this series, do so first.

If you don't want to, you could clone the project source and checkout to this tag to follow along:

Create an IHP database type

To demonstrate how we can insert different datatypes into Elm, let's create a relatively complex database table.

Run the app with npm start and go to localhost:8001/Tables.

Select the Code Edit toggle in the top left corner and paste this snippet into the code area:

Remember to press Save down in the bottom. It's a bit hidden, so easy to miss.

Pasting in schema

After saving, press Update DB in the IHP dashboard. This should update the database with the new table.

This will have automatically created the type Book. Let's create a controller for it next.

Generate Controller and Views

Stay in the localhost:8001 admin dashboard and select Codegen in the menu to the left.

Select Controller, name it Books and click Preview and click Generate.

Like this:

Create Controller named Books

You will now have generated all you need to view, update, create and delete books. Pretty cool!

Just a couple of small adjustments before we proceeed:

Let's just use a checkbox field for the hasRead value and a datepicker for the publishedAt value.

In both New.hs and Edit.hs in the /Web/View/Books/ folder, replace these two fields:

to

Let's also take a short visit to the Books Controller /Web/Controller/Books.hs and the buildBook function at the bottom. Make sure the nullable value review turns into Nothing if empty instead of Just "".

Then go to http://localhost:8000/Books to create just a couple of Books with some varying values.

Some small changes in the hsx templates

We are adding the Elm widget application globally because we are going to use it as a general purpose widget engine.

Navigate to Web/View/Layout.hs and add the elm script <script src="/elm/index.js"></script> to the scripts in development and remove the unused scripts for development as well.

Note that we are using defer on prod.js and the elm script for Elm to load properly.

Secondly, let's replace what we wrote in the previous part of the series in /Web/View/Static/Welcome.hs, just to have a practical link to the Books.

Install haskell-to-elm

haskell-to-elm will let us generate Elm types from Haskell types, including encoders and decoders.

In order to generate Elm types from IHP, add the haskell-to-elm package to haskellDeps in default.nix

To update your local environment, close the server (ctrl+c) and run

If you are on vscode, you might need to reload your text editor to catch the updates in .envrc.

Also add the required Elm packages required by haskell-to-elm. I i highly recommend the cli-tool elm-json to install elm packages.

Reduce boilerplate for Haskell-to-Elm types

Following these instructions will make it easier to add haskell-to-elm types later on.

Create a folder named Application/Lib and create a new Haskell module:

Paste this code into Application/Lib/DerivingViaElm.hs.

You probably won't ever do any changes in this script, but it saves us from lots of boilerplate when creating Haskell to Elm types.

Turn IHP types into JSON serializable types

Create the file where the elm-compatible types will live.

In Web/JsonTypes.hs we will create types that can be directly serialized into both JSON and Elm decoders. For starters, we will make a BookJSON type and a function for creating it from the IHP generated Book.

This is some extra work, but you also get to control what fields that will be exposed to the outside world here.

Make a widget entry-point

A logical place to write the entrypoints for this Elm widget is Application/Helper/View.hs as functions exposed here are accessible in all view modules.

We will also define a Widget type that will be like a register for all new widgets.

bookWidget takes in the IHP Book type as an argument, converts to the BookJSON type and wraps it inside a Widget.

Now we need to jump to the elm/index.js file and pass in the data-flags attribute from the widget. While we are at it we are also enabling the possibility to have several elm widgets present at one single page.

The value passed into the data-flags attribute is serialized and ready to be sent right through JavaScript and directly into Elm.

Let's put this bookWidget into /Web/View/Books/Show.hs:

Autogenerate types

Now it's time for the fun stuff. We need to go back to localhost:8001 and generate a script and select Codegen in the left menu and then Script. Type GenerateElmTypes, select Preview and then Generate.

Like this:

Generate Elm Script

IHP will have created an boilerplate for an executable for you.

Fill in the export logic for generating Elm types in /Application/Script/GenerateElmTypes.hs:

Let's test it. Run:

Voila! If everything has gone well so far, you should have a file named elm/Api/Generated.elm. Inspect it with great joy. You didn't need to write any of this manually in Elm.

What a beauty!

Generated Elm types

Let's make a npm run gen-types script for it in package.json and we might as well run it at the npm start command to update it regularly.

Write some Elm

Let's finish up this tutorial by rewriting the Main.elm to decode the flags and use the Haskell model.

Go to localhost:8000/Books and press Show on any book you have created. You should see where Elm starts and begins with the <elm🌳> tag.

A sample of the Elm component

The Elm logic is handling every type as it was defined in Haskell, from Bool to even Maybe String.

To get a complete overview of the changes, see the diff compared what we did in the previous post

Next up

We have created only one widget, but in the next post we are adding another one.

We are also structuring the widgets into separate modules, inpired by Richard Feldman's RealWorld SPA archtecture, but a simpler version.

Got a comment? Drop me a tweet