Web App with Elm
A minimal web app with an Elm frontend and Roc backend. The Roc backend uses the basic-webserver platform.
Why Elm + Roc?
Roc was inspired by Elm, so it's nice to be able to use a similar language for the frontend. Elm also has a mature collection of re-usable packages.
Alternatives
We've also enjoyed using htmx with Roc. It allows you to use Roc for the frontend and the backend.
Full Code
src/Main.elm:
module Main exposing (..) -- Importing necessary modules import Browser import Html exposing (Html, div, text, h1) import Html.Attributes exposing (..) import Http -- MAIN -- The main function is the entry point of an Elm application main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view } -- MODEL -- Model represents the state of our application type Model = Failure String | Loading | Success String -- init function sets up the initial state and any commands to run on startup init : () -> (Model, Cmd Msg) init _ = ( Loading , fetchData ) -- UPDATE -- Msg represents the different types of messages our app can receive type Msg = GotResponse (Result Http.Error String) -- update function handles how the model changes in response to messages update : Msg -> Model -> (Model, Cmd Msg) update msg model = case msg of GotResponse result -> case result of Ok body -> (Success body, Cmd.none) Err error -> (Failure (Debug.toString error), Cmd.none) -- VIEW -- view function determines what to display in the browser based on the current model view : Model -> Html Msg view model = case model of Failure errorMsg -> text ("Is the Roc webserver running? I hit an error: " ++ errorMsg) Loading -> text "Loading..." Success body -> div [ id "app" ] [ h1 [] [ text body ] ] -- HTTP -- fetchData sends an HTTP GET request to the Roc backend fetchData : Cmd Msg fetchData = Http.get { url = "http://localhost:8000/" , expect = Http.expectString GotResponse } -- SUBSCRIPTIONS -- subscriptions allow the app to listen for external input (e.g., time, websockets) -- In this case, we're not using any subscriptions subscriptions : Model -> Sub Msg subscriptions _ = Sub.none
elm.json:
index.html:
<!-- app div is used by elm -->
<!-- elm is compiled to js -->
backend.roc:
app [Model, server] { pf: platform "https://github.com/roc-lang/basic-webserver/releases/download/0.8.0/jz2EfGAtz_y06nN7f8tU9AvmzhKK-jnluXQQGa9rZoQ.tar.br", } import pf.Stdout import pf.Http exposing [Request, Response] import pf.Utc # [backend](https://chatgpt.com/share/7ac35a32-dab5-46d0-bb17-9d584469556f) Roc server # Model is produced by `init`. Model : {} # With `init` you can set up a database connection once at server startup, # generate css by running `tailwindcss`,... # In this case we don't have anything to initialize, so it is just `Task.ok {}`. server = { init: Task.ok {}, respond } respond : Request, Model -> Task Response [ServerErr Str]_ respond = \req, _ -> # Log request datetime, method and url datetime = Utc.now! |> Utc.toIso8601Str Stdout.line! "$(datetime) $(Http.methodToStr req.method) $(req.url)" Task.ok { status: 200, headers: [ # !! # Change http://localhost:8001 to your domain for production usage # !! { name: "Access-Control-Allow-Origin", value: "http://localhost:8001" }, ], body: Str.toUtf8 "Hi, Elm! This is from Roc: 🎁\n", }
Running
Roc
You can change the port on which the Roc server runs with ROC_BASIC_WEBSERVER_PORT.
cd examples/ElmWebApp/ # development roc backend.roc --linker=legacy # production roc build backend.roc --optimize --linker=legacy ./backend
Elm
Note: for non-trivial Elm development we recommend using elm-watch.
Compile elm code to javascript:
cd examples/ElmWebApp/frontend # development elm make src/Main.elm --output elm.js # production elm make src/Main.elm --output elm.js --optimize
Serve the frontend:
elm reactor --port 8001 # Roc backend will be on 8000
For production; use a battle-tested HTTP server instead of elm reactor.
Open localhost:8001/index.html in your browser.