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, init!, respond!] { web: platform "https://github.com/roc-lang/basic-webserver/releases/download/0.13.0/fSNqJj3-twTrb0jJKHreMimVWD7mebDOj0mnslMm2GM.tar.br", } import web.Stdout import web.Http exposing [Request, Response] import web.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 `Ok({})`. init! = |{}| Ok({}) respond! : Request, Model => Result Response [ServerErr Str]_ respond! = |req, _| # Log request datetime, method and url datetime = Utc.to_iso_8601(Utc.now!({})) Stdout.line!("${datetime} ${Inspect.to_str(req.method)} ${req.uri}")? 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.to_utf8("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.