Awesome
Servant-Elm Example App
This is an example application making use of the servant-elm library.
To build and run this application, you need to install stack and Elm.
Assuming you have both tools installed, you can run the example with:
make && make serve
App layout
If you study servant-elm-example-app.cabal, you will see that the Haskell part of this project comprises four build targets:
-
an
api
library, which will contain our Servant API type and server implementation; -
a
backend
executable, which serves a home page and our API under/api
, as well as our static assets; -
a
code-generator
executable, which uses servant-elm to generate Elm code for interacting with our API; and -
a test-suite (not yet implemented).
Our Elm code is in the frontend/src directory.
Build process
The Makefile ties everything together.
First we build the api
library and the backend
and code-generator
executables by running stack build
.
Then we run stack exec code-generator
, directing the output Elm code to
frontend/src/Generated/Api.elm.
Finally, we run elm-make
to build frontend/dist/app.js
.
Components in detail
The API
The definition of our API lives in api/Api/Types.hs. It's pretty simple. You can:
POST /books
GET /books
The first returns a JSON representation of a book, and the second returns a list of such representations.
The implementation lives in api/Api/Server.hs. The book database
state lives in a TVar BookDB
, which must be supplied by the user.
The backend
backend/Main.hs defines a type called SiteApi
, which wraps
our book API under /api
, provides an index route and serves assets under
/assets
.
The server
function implements this SiteApi
. It wraps the API server
implementation, serves the frontend/dist
directory as /assets
, and serves
a home page.
The home page simply sets a page title and bootstraps our Elm app (which will be
built to frontend/dist/app.js
).
The main
function creates a new TVar
for our book database API and starts the app
on port 8000.
The code generator
code-generator/Main.hs imports our Api type and calls
servant-elm's elmJSWith
with some options:
-
moduleName
configures the name of the Elm module that will be generated. This must match the filepath that the Elm code will be written to, and any import statements in your own Elm code using the generated code. In our case we have usedGenerated.Api
. -
urlPrefix
specifies where the frontend code can connect to the API. In our case it ishttp://localhost:8000/api
.
The frontend
In frontend/src/Main.elm we import the Generated.Api
module. Our Elm app uses the Html
module (see
The Elm Architecture for
details).
The functions fetchBooks
and the use of postBooks
in update
demonstrate
how the generated Elm functions can be used. The generated functions getBooks
and postBooks
have types Http.Request (List (Book))
and Book -> Http.Request (Book)
respectively. The Http.Request a
type is passed to the
Http.send
function to actually perform the requests.
TODO
- Demonstrate API endpoints that take parameters and captures.
- Try to make coupling between components more explicit:
- port and API prefix must match between
backend
andcode-generator
, - Elm module name must match between
Makefile
andcode-generator
andfrontend
, - frontend build directory must match between
Makefile
andbackend
.
- port and API prefix must match between