DEV Community

Yuan Ji
Yuan Ji

Posted on • Edited on

How to Build an Elm Land Project for Production

After completing my demo project on Keycloak OAuth2 Token Exchange, I decided to expand the idea and created a new demo project called my-ai-doctor. Unlike the previous demo, which focused on backend integration, this project uses Elm for the UI and Docker Compose to run all six servers locally: the Keycloak server, API server, and the SPA web servers for both MyDoctor and MyHealth.

Fixing the Elm Compiler for Linux ARM 64

The first challenge was building the Elm Land project and deploying it to Docker containers. Initially, I used a standard Dockerfile to build my Elm UI project, but soon hit a roadblock:

 > [build 8/9] RUN npm install:                                                                                                                             
8.709 npm error code 1                                                                                                                                     
8.709 npm error path /app/node_modules/elm                                                                                                                 
8.709 npm error command failed                                                                                                                             
8.709 npm error command sh -c node install.js                                                                                                              
8.709 npm error -- ERROR -----------------------------------------------------------------------
8.709 npm error
8.709 npm error I am detecting that your computer (linux_arm64) may not be compatible with any
8.709 npm error of the official pre-built binaries.
8.709 npm error
8.709 npm error I recommend against using the npm installer for your situation. Check out the
8.709 npm error alternative installers at https://github.com/elm/compiler/releases/tag/0.19.1
8.709 npm error to see if there is something that will work better for you.
8.709 npm error
8.709 npm error From there I recommend asking for guidance on Slack or Discourse to find someone
8.709 npm error who can help with your specific situation.
8.709 npm error
8.709 npm error --------------------------------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

After some investigation, I found that the latest Elm compiler (version 0.19.1) does not support linux_arm64. This meant I could build and run Elm projects on my Apple Mac Mini Pro natively, but not inside Docker. Fortunately, Simon Lydell and Mario Rogic created a new version of the elm npm package, which supports Linux ARM 64. Based on this discussion, the solution was to override the elm package in my package.json file by adding these lines:

"overrides": {
  "elm": "npm:@lydell/elm"
}
Enter fullscreen mode Exit fullscreen mode

This replaces the Elm npm module in the locally downloaded node_modules directory with the @lydell/elm module, allowing the unofficial Elm compiler to work correctly on the Linux ARM64 platform.

Passing Environment Variables to UI Code

When developing the AI Doctor system locally, the UI SPA connects to the backend API at http://api.mydoctor:8081/api/v1/records. However, when running the entire demo inside Docker, the API is accessible via port 80 with the help of an Nginx reverse proxy: http://api.mydoctor/api/v1/records. The challenge was how to dynamically change the backend API URL in the Elm code.

The Elm Land documentation briefly mentions environment variables, but without detailed examples. I found a helpful blog, Deploying an Elm Frontend to Cloudflare Pages, which provided some hints on how to pass environment variables to an Elm Land app. After studying the Elm Land documentation again (here and here), I realized it was quite straightforward to pass different values as environment variables to Elm code.

In the interop.js file, we can pick environment variables and pass them to Elm code as Flags. For example:

export const flags = ({ env }) => {
  return {
    apiBaseUrl: env.API_BASE_URL
  };
};
Enter fullscreen mode Exit fullscreen mode

Here, we pass the backend API base URL as a field in flags, which is then saved into Shared.Model.

First, I needed to customize the Shared module:

elm-land customize shared
Enter fullscreen mode Exit fullscreen mode

This command will move three files from .elm-land into src folder: Shared.elm, Shared/Model.elm and Shared/Msg.elm.

Next, I added the field apiBaseUrl to Shared.Model.Model. Here is the code for Shared/Model.elm:

module Shared.Model exposing (Model)

type alias Model =
  { apiBaseUrl : String
  }
Enter fullscreen mode Exit fullscreen mode

In Shared.elm, I decoded the flags and passed apiBaseUrl to Shared.Model.Model:

-- FLAGS

type alias Flags =
  { apiBaseUrl : String
  }

decoder : Json.Decode.Decoder Flags
decoder =
  Json.Decode.map Flags
    (Json.Decode.field "apiBaseUrl" Json.Decode.string)

-- INIT
type alias Model =
  Shared.Model.Model

init : Result Json.Decode.Error Flags -> Route () -> ( Model, Effect Msg )
init flagsResult route =
  ( { apiBaseUrl =
        case flagsResult of
          Ok flags -> flags.apiBaseUrl
          Err _ -> ""
    }
  , Effect.none
  )
Enter fullscreen mode Exit fullscreen mode

The Shared.Model.Model (or Shared.Model) is then passed to each page, so the apiBaseUrl can be accessed whenever we need to call the backend API. See example code at Pages/Test.elm

Setting Environment Variables

To set environment variables like API_BASE_URL, you first need to declare them in the elm-land.json file under app.env, as:

{
  "app": {
    "env": [ "API_BASE_URL" ]
  }
}
Enter fullscreen mode Exit fullscreen mode

For security reasons, all environment variables are hidden from your Elm Land application by default.
When you want to share an environment variable with your app, the app.env field is the one spot check

See my elm-land.json file, and I also declared AUTH_SERVER_URL for Keycloak server address and WS_BASE_URL for backend chatbot WebSocket address.

Then, you can pass API_BASE_URL in the command line when developing locally:

API_BASE_URL=http://api.mydoctor:8081/api/v1 elm-land server
Enter fullscreen mode Exit fullscreen mode

For production builds, use the ENV directive in the Dockerfile like this:

ENV API_BASE_URL="http://api.mydoctor/api/v1"
Enter fullscreen mode Exit fullscreen mode

See my Dockerfile for mydoctor-ui app.

You can find all source code at https://github.com/jiwhiz/my-ai-doctor. This is still a work in progress. Please let me know if you find this blog is helpful.

Top comments (0)