Renamed to choonkeat/elm-webapp. It is published under that name, and new versions come out there.
Versions published as choonkeat/create-elm-server still install under that name. 2 versions of 2 come from the new location (see their notes).
Rerouted (renamed, needs approval). The GitHub user or repository was renamed. The tag is still there, at the new name. The Lamdera package server serves a copy recovered from https://github.com/choonkeat/elm-webapp.
What we checked: The repo moved to github.com/choonkeat/elm-webapp and tag 1.0.1 is there; its commit 6746cddc6551 is from 2021-04-05 00:34 UTC, after the version was published (2021-02-14 06:52 UTC); elm.json differs from package.elm-lang.org's; the exposed modules do not match docs.json; the zip's SHA-1 is not the published one and nothing else confirms its contents.
We could not verify this copy against what was published, so the Lamdera compiler asks before using it (y/n). Without a terminal, as in CI, it stops with an error unless approved:
lamdera make --auto-approve-package-rerouting approves rerouted versions for that run,LAMDERA_AUTO_APPROVE_PACKAGE_REROUTING=1 does the same for every run in that environment,LAMDERA_CACHE=off skips the Lamdera package server altogether: packages come from package.elm-lang.org and GitHub only, as with elm, and a rerouted version then fails to download.Details · package.elm-lang.org's zip: https://github.com/choonkeat/create-elm-server/zipball/1.0.1/
A setup for writing http based, client-server app in elm, inspired wholly by Lamdera
npx elm-webapp element hello-appThis will create a skeleton file directory structure
hello-app
├── Makefile
├── index.js
└── src
├── Client.elm
├── Server.elm
├── Types.elm
└── Types
└── Auto.elm
1 directory, 5 filesThe above command generates a Client of Browser.element. To generate a Browser.document or Browser.application instead, use either commands
npx elm-webapp document hello-app
npx elm-webapp application hello-appsrc/Client.elmIn this file, we see
webapp =
Webapp.Client.element
{ element =
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}☝️ This record is where we provide our standard Browser.element, Browser.document, or Browser.application
, ports =
{ websocketConnected = \_ -> Sub.none -- websocketConnected
, websocketIn = \_ -> Sub.none -- websocketIn
}☝️ Here's where you can connect a WebSocket port implementation to communicate with src/Server.elm. Uncomment to enable.
By default, elm-webapp is wired up to communicate with src/Server.elm through regular http POST /api/elm-webapp
, protocol =
{ updateFromServer = updateFromServer
, clientMsgEncoder = Types.Auto.encodeTypesMsgFromClient
, serverMsgDecoder = Types.Auto.decodeTypesMsgFromServer
, errorDecoder = Json.Decode.string
}
}☝️ This section wires up the necessary functions to coordinate with src/Server.elm
updateFromServer : MsgFromServer -> Model -> ( Model, Cmd Msg )is the entry point where we handle MsgFromServer values from src/Server.elm. We usually do a case ... of statement inside, much like how we write our standard update function
main =
webapp.elementthat gives us our main function for the client.
sendToServer : MsgFromClient -> Task Http.Error (Result String MsgFromServer)
sendToServer =
webapp.sendToServersends MsgFromClient values to our server whereby the server must respond with a MsgFromServer that we've wired to handle in updateFromServer (see above). This happens over http post by default, and over websockets if enabled (see above)
This is how we achieve a seamless and type-safe way for Client-Server communication.
src/Server.elmserves our Client frontend app by default, and can respond to values from Client.sendToServer or regular http requests.
main : Program Flags ServerState RequestContext Msg String MsgFromServer
main =
Webapp.Server.worker
{ worker =
{ init = init
, update = update
, subscriptions = subscriptions
}☝️ This record is where we provide our standard Platform.worker
, ports =
{ writeResponse = writeResponse
, onHttpRequest = onHttpRequest
, onWebsocketEvent = \_ -> Sub.none -- onWebsocketEvent
, writeWebsocketMessage = \_ _ _ -> Cmd.none -- writeWebsocketMessage
}☝️ Here's where we've connected our httpserver with Elm ports. You can connect a WebSocket server Elm port too; uncomment to enable.
, protocol =
{ routeDecoder = routeDecoder
, updateFromRoute = updateFromRoute
, updateFromClient = updateFromClient
, serverMsgEncoder = Types.Auto.encodeTypesMsgFromServer
, clientMsgDecoder = Types.Auto.decodeTypesMsgFromClient
, headerDecoder = headerDecoder
, errorEncoder = Json.Encode.string
}
}☝️ This section wires up the necessary functions to coordinate with src/Client.elm
updateFromClient : RequestContext -> Time.Posix -> MsgFromClient -> ServerState -> ( ServerState, Task String MsgFromServer )is called whenever the Client sends a value over with its sendToServer. We usually do a case ... of statement inside, much like how we write our standard update function
updateFromRoute : ( Method, RequestContext, Maybe Route ) -> Time.Posix -> Request -> ServerState -> ( ServerState, Cmd Msg )is the catch-all handler for http request; called whenever Server has to handle a http request that isn't handled by updateFromClient. e.g. oauth redirect path.
Note that ServerState is simply Model you see in standard Elm apps; named differently.
headerDecoder : ServerState -> Json.Decode.Decoder RequestContextis applied to http request headers and gives us a more meaningfully categorised RequestContext . e.g. we can decode the Authorization header and determine if the JWT value gives us a valid LoggedInUser Email or an AnonymousUser
This difference can be put into good use when we handle updateFromClient or updateFromRoute
index.js boots up our src/Server.elm
- by default, node.js runs http.createServer and let Elm handles http request and write responses via Elm ports
- if env LAMBDA is set, lambda.js will instead setup a callback so we can handle http request inside AWS Lambda behind an API Gateway.
- other possible integrations are cloudflare-workers.js or even deno-deploy.js
- PRs are extremely welcome to improve the robustness of these integrations 🙇♂️src/Types.elm holds the types shared between Server and Client.src/Types/Auto.elm ; also see gotchas regarding imported typeselm-auto-encoder-decoder in elm-webapp only for convenience; you can switch it out for your own encoders & decoders. BUT if you continue using elm-auto-encoder-decoder, don't use them anywhere else (e.g. as encoder to save in db, exposed as part of your external api, etc...). Main reason being that the serialized format could change future releases of elm-auto-encoder-decoder and thus MUST NOT be relied on.Copyright © 2021 Chew Choon Keat
Distributed under the MIT license.