An API Server in Haskell. Written as a demonstration of how to use hasql and scotty together in a realistic (we hope) api server application.
There's a User
and a minimal Resource
(which can easily be extended
or copied). Even these two resources interact in a nice way to
demonstrate things like required or optional parameters and token-based
authentication.
This application is "cabalized" and so it should be the same as running any other cabal-managed application. Make sure that you have cabal and cabal-install installed and working. For the majority of users, installing the Haskell Platform will be all you need.
Next clone the repo and then initialize a new project. This project uses cabal freeze to lock-in the versions of the necessary dependencies.
git clone https://github.com/bendyworks/api_server.git
cd api_server
It is good practice to create a sandbox for installing packages into:
cabal sandbox init
Next install the project's dependencies. We'll also install the dependencies for tests:
cabal install --only-dependencies --enable-tests
At this point, go get a snack, it's going to build for a while. Next we
need to initialize the database that the app depends upon. There's a script
which will set up the database (you can also call db drop
to drop the
databases) This will create api_dev
and api_test
databases using the
psql command:
./bin/db create
Lastly, we'll set some environment variables. I usually create a .env
file with some details like this:
export DATABASE_URL="postgresql://postgres:@localhost:5432/api_dev"
export SMTP_SERVER=localhost
export SMTP_PORT=25
export SMTP_LOGIN=""
export SMTP_PASSWORD=""
export HASK_ENV=DEVELOPMENT
export PORT=3000
Just to make sure everything worked, we'll build the tests and run them:
cabal test --show-details=always --test-options="--color"
If you look in the cabal file (api-server.cabal
) you can see that the
project is split into three pieces:
- A library that contains the vast majority of the application logic.
- An executable that uses the library and is basically there to gather some environment variables to run the server.
- A test suite that uses the library.
Undoubtedly, I will have missed something in these docs. Please file an issue and we'll get the setup corrected.
The basic flow for the api is as follows:
REQ> POST /users
RSP> user_id: x, api_token: t
REQ> POST /users/$x
{ "resource_email": "[email protected]" }
RSP> "ok" (also sends an email to given address, with uuid)
REQ> GET /verify/$uuid
RSP> {resource_id: r, user_id: x}
REQ> POST /resource
Header: "Authorization: Token $t"
{ "resource_email": "[email protected]"
, "resource_name": "some name"
, "resource_optional": "optional text"
, "user_id": $x
}
RSP> { "resource_email": "[email protected]"
, "resource_name": "some name"
, "resource_optional": "optional text"
, "resource_id": $r
}
There's a series of blog posts that describes how we built this application and the kinds of things that we were hoping to achieve:
- Part 1 - db concerns
- Part 2 - encoding domain logic in types
- Part 3 - type system as an aid to authentication
- Bendyworks for professional development time & the room to try new stuff
- Jon for putting in all the time that actually makes a project like this work. He’s always been the one that fixes the half-baked stuff that I write. Also, it has been immensely rewarding to pair-program on Haskell professionally.
- All of the various excellent Haskell libraries and tooling.
- And last, but certainly not least, Joe Nelson for his utterly indefatigable work on everything Haskell, all of which I depend on constantly: haskell-vim-now, heroku-buildpack-ghc, postgrest and more. Also I want to thank Joe for hours of deep conversations during BayHac2014 about what’s possible using databases, Haskell, and so much more. My understanding about how to design applications like this wouldn’t have been possible without his insights. His urging (even as a voice in the back of my head) has caused me to finish more projects than anything else. Thanks Joe!