---
title: A REST API with Haskell and Snap
teaser: Use Haskell and Snap to build a RESTful API.
tags: web,snap,haskell
author: Sid Raval
published_on: 2015-03-04
---

Building a web application in Haskell may seem like a daunting task, but it
doesn't have to be: thanks to the authors of the [Snap Framework][snap], web
development in Haskell can be fun and eye-opening! Let's build a simple RESTful
JSON API using Snap.

Moderate familiarity with the Haskell language is assumed -- this guide will not
go through basic Haskell syntax or setup, and you're expected to look at
documentation while reading this guide.

The code snippets start with a comment that notes what file they're editing.
Each snippet only shows the added lines to that file. I recommend glancing at
each file while reading the snippet, so the previously defined values are
apparent.

Hover over any linked function to see it's type signature.

[snap]: http://www.snapframework.com

## Getting set up

We've made a [git repository] that you can clone, so we're
starting off on the same page. For reference, the author is using Cabal v1.22.0.0
and GHC v7.8.4. Run the following:

```bash
git clone git@github.com:thoughtbot/snap-api-tutorial.git
cd snap-api-tutorial
git checkout baseline
cabal sandbox init
cabal install snap
cabal install --dependencies-only
```

[git repository]: https://www.github.com/thoughtbot/snap-api-tutorial

## The API snaplet

A snaplet is a composable piece of a Snap application. Snap applications are
built by nesting snaplets. Indeed, if you take a peek at `Application.hs`
you'll see the application initializer `app` is itself the result of a
[`makeSnaplet`][makeSnaplet] function.

We're going to build our own snaplet called `Api`. This snaplet will be
responsible for our top level `/api` namespace.  We'll set a couple language
extensions, import required modules, and define our `Api` datatype. Then we'll
define the initializer for our snaplet.

```haskell
-- new file: src/api/Core.hs

{-# LANGUAGE OverloadedStrings #-}

module Api.Core where

import Snap.Snaplet

data Api = Api

apiInit :: SnapletInit b Api
apiInit = makeSnaplet "api" "Core Api" Nothing $ return Api
```

Note that the type `apiInit :: SnapletInit b Api` could have been
`apiInit :: SnapletInit App Api`. By using `b` instead of `App`, we're telling
Snap that our `Api` snaplet can be nested in *any* base application, not just
`App`. This is the root of snaplet composability.

Right now our snaplet exists in isolation -- we haven't nested it within
our top level application. We'll begin by telling our `App` datatype to expect
an `Api` snaplet:

```haskell
-- src/Application.hs

import Api.Core (Api(Api))

data App = App { _api :: Snaplet Api }
```

Then, we'll nest our `Api` snaplet within our `App` snaplet, using
[`nestSnaplet`][nestSnaplet]:

```haskell
nestSnaplet :: ByteString -> Lens v (Snaplet v1) -> SnapletInit b v1 -> Initializer b v (Snaplet v1)
```

The first argument is a root base url for the snaplet's routes, `/api` in our
case. The second argument is a `Lens` identifying our snaplet, generated by the
`makeLenses` function in `src/Application.hs`. The last argument is the snaplet
initializer function `apiInit` we defined previously. Putting it to use:

```haskell
-- src/Site.hs

import Api.Core (Api(Api), apiInit)

app :: SnapletInit App App
app = makeSnaplet "app" "An snaplet example application." Nothing $ do
    api <- nestSnaplet "api" api apiInit
    addRoutes routes
    return $ App api
```

Ok, so we've nested our `Api` snaplet. But since it has no routes, we can't
actually see it in action. We'll add a `/api/status` route that always responds
with a `200 OK`.

Snap route handlers generally return a type of `Handler b v ()`. The
[`Handler`][handler] monad is an instance of [`MonadSnap`][monadSnap], which
gives us stateful access to the HTTP request and response. All of our request
and response modification and processing will take place inside a `Handler`
monad. Thus, we'll define `respondOk :: Handler b Api ()`:

```haskell
-- src/api/Core.hs

import           Snap.Core
import qualified Data.ByteString.Char8 as B

apiRoutes :: [(B.ByteString, Handler b Api ())]
apiRoutes = [("status", method GET respondOk)]

respondOk :: Handler b Api ()
respondOk = modifyResponse $ setResponseCode 200

apiInit :: SnapletInit b Api
apiInit = makeSnaplet "api" "Core Api" Nothing $ do
        addRoutes apiRoutes
        return Api
```

Now, let's look at the type signatures for [`modifyResponse`][modifyResponse]
and [`setResponseCode`][setResponseCode]:

```haskell
modifyResponse :: (MonadSnap m) => (Response -> Response) -> m ()
setResponseCode :: Int -> Response -> Response
```

That is, `setResponseCode` takes an integer and returns a `Response`
modifying function that we can then pass into `modifyResponse`. `modifyResponse`
will perform the response modification within our `Handler` monad.

Now try the following:

```bash
$ cabal run -- -p 9000
$ curl -I -XGET "localhost:9000/api/status"

HTTP/1.1 200 OK
Server: Snap 0.9.4.6
Date: ...
Transfer-Encoding: chunked
```

Yay! Our first response.

## The todo data type

Next we'll create a `Todo` data type, and provide instances that allow
it to be deserialized out of a row in PostgreSQL, and serialized into JSON for
our response. First, our data type and instances:

The [`FromRow`][fromRow] typeclass will allow us to define [`fromRow`][fromRowF], which
deserializes rows from PostgreSQL into `Todo` data structures.

The [`ToJSON`][toJson] typeclass will allow us to define [`toJSON`][toJsonF], which
serializes our `Todo` data structure into a [`Value`][value] type. This type can
then be converted to JSON via the [`encode`][encode] function.

```haskell
-- new file: src/api/Types.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}

module Api.Types where

import           Control.Applicative
import qualified Data.Text as T
import           Data.Aeson (ToJSON(toJSON), object, (.=))
import           Snap.Snaplet.PostgresqlSimple

data Todo = Todo
  { todoId   :: Int
  , todoText :: T.Text
  }

instance FromRow Todo where
  fromRow = Todo <$> field
                 <*> field

instance ToJSON Todo where
   toJSON (Todo id text) = object [ "id" .= id, "text" .= text ]
```

In `fromRow`, the number of calls to `field` should match the number of columns
returned in the SQL query you intend to call to retrieve the data.

## The todo snaplet

Next, we'll be nesting a `Todo` snaplet inside our `Api` snaplet. We'll then
establish a database connection, and write `GET` and `POST` handlers for
`/api/todos`, allowing us to create a new todo item, and fetch all todo items.

We'll start with the boilerplate, as before -- defining our snaplet, then
nesting it inside our `Api` snaplet:

```haskell
-- new file: src/api/services/TodoService.hs

{-# LANGUAGE OverloadedStrings #-}

module Api.Services.TodoService where

import Api.Types (Todo(Todo))
import Control.Lens (makeLenses)
import Snap.Core
import Snap.Snaplet

data TodoService = TodoService

todoServiceInit :: SnapletInit b TodoService
todoServiceInit = makeSnaplet "todos" "Todo Service" Nothing $ return TodoService
```

```haskell
-- src/api/Core.hs

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens (makeLenses)
import Api.Services.TodoService(TodoService(TodoService), todoServiceInit)
-- ...

data Api = Api { _todoService :: Snaplet TodoService }

makeLenses ''Api
-- ...

apiInit :: SnapletInit b Api
apiInit = makeSnaplet "api" "Core Api" Nothing $ do
  ts <- nestSnaplet "todos" todoService todoServiceInit
  addRoutes apiRoutes
  return $ Api ts
```

Nothing new here. Now we'll nest a PostgreSQL snaplet, provided by
[`snaplet-postgresql-simple`][postgres-snaplet], into our `TodoService`. This will
provide the `TodoService` with a connection to a database, and allow us to query
it. We'll also import Aeson, so we can encode our responses as JSON using the
`ToJSON` instance we defined before.

```haskell
-- src/api/services/TodoService.hs

{-# LANGUAGE TemplateHaskell -#}
{-# LANGUAGE FlexibleInstances -#}

import Control.Lens (makeLenses)
import Control.Monad.State.Class (get)
import Data.Aeson (encode)
import Snap.Snaplet.PostgresqlSimple
import qualified Data.ByteString.Char8 as B
-- ...

data TodoService = TodoService { _pg :: Snaplet Postgres }

makeLenses ''TodoService
-- ...

todoServiceInit :: SnapletInit b TodoService
todoServiceInit = makeSnaplet "todos" "Todo Service" Nothing $ do
  pg <- nestSnaplet "pg" pg pgsInit
  return $ TodoService pg

instance HasPostgres (Handler b TodoService) where
  getPostgresState = with pg get
```

The `HasPostgres` instance just gives our code some brevity, you can read about
it [here][postgres-snaplet].

A bit of SQL to set up our database and insert a couple rows of test data:

```SQL
CREATE DATABASE snaptutorial;
CREATE TABLE todos (id SERIAL, text TEXT);
INSERT INTO todos (text) VALUES ('First todo');
INSERT INTO todos (text) VALUES ('Second todo');
```

Then, provide configuration to the postgres snaplet by editing the following
file appropiately:
`snaplets/api/snaplets/todos/snaplets/postgresql-simple/devel.cfg`.

We're now ready for our first `GET` to `/api/todos`. We'll fetch all the rows of
the todos table, convert them into `Todo` data, and then serialize them as JSON
for our response.

To retreive the data, we use the [`query_`][query] function, which takes a SQL
string and returns a (monadic) array of data that implement the `FromRow`
typeclass:

```haskell
query_ :: (HasPostgres m, FromRow r) => Query -> m [r]
```

Then, we'll use [`writeLBS`][writeLBS] in conjunction with the previously
mentioned [`encode`][encode] function to write a JSON string to the response
body:

```haskell
writeLBS :: MonadSnap m => ByteString -> m ()
```

[Under the hood][writelbs-source], this function calls out to a function that
calls the [`modifyResponse`][modifyResponse] function we saw earlier.

Put together, it looks like this:

```haskell
-- src/api/services/TodoService.hs

-- ...
todoRoutes :: [(B.ByteString, Handler b TodoService ())]
todoRoutes = [("/", method GET getTodos)]

getTodos :: Handler b TodoService ()
getTodos = do
  todos <- query_ "SELECT * FROM todos"
  modifyResponse $ setHeader "Content-Type" "application/json"
  writeLBS . encode $ (todos :: [Todo])

todoServiceInit :: SnapletInit b TodoService
todoServiceInit = makeSnaplet "todos" "Todo Service" Nothing $ do
  pg <- nestSnaplet "pg" pg pgsInit
  addRoutes todoRoutes
  return $ TodoService pg
-- ...
```

Additionally, we set  the `Content-Type` header so browsers know we are sending
back JSON. Voila:

```bash
$ cabal run -- -p 9000
$ curl -XGET localhost:9000/api/todos

[{"text":"First todo","id":1}, {"text":"Second todo","id":2}]
```

Now, to create data, we'll write a handler for a `POST` to `/api/todos`. This
time, we'll get parameters from our request body, and insert them into our
database. Then we'll respond with a `201 CREATED`.

This time, we'll get data from the POST request body via [`getPostParam`][getPostParam]:

```haskell
getPostParam :: MonadSnap m => ByteString -> m (Maybe ByteString)
```

Then use [`execute`][execute] (which is the database modifying counterpart to
`query`) to insert the data acquired via `getPostParam` into the database:

```haskell
todoRoutes :: [(B.ByteString, Handler b TodoService ())]
todoRoutes = [("/", method GET getTodos)
             ,("/", method POST createTodo)]

createTodo :: Handler b TodoService ()
createTodo = do
  todoTextParam <- getPostParam "text"
  newTodo <- execute "INSERT INTO todos (text) VALUES (?)" (Only todoTextParam)
  modifyResponse $ setResponseCode 201
```

Here, the [`Only`][only] is postgresql-simple's version of single value collections.

Voila:

```bash
$ cabal run -- -p 9000
$ curl -i -XPOST --data "text=Third todo" "localhost:9000/api/todos"

HTTP/1.1 201 Created
Server: Snap 0.9.4.6
Date: ...
Transfer-Encoding: chunked

$ psql snaptutorial
$ SELECT * FROM todos;

 id |     text
----+--------------
  1 | First todo
  2 | Second todo
  3 | Third todo
```

And so we have a simple REST API that can fetch and create resources! For more
on Snap, check out the [Snap documentation], or visit `#snapframework` on
[freenode].

[Snap documentation]: http://snapframework.com/docs
[freenode]: http://freenode.net
[makeSnaplet]:
http://hackage.haskell.org/package/snap-0.6.0/docs/Snap-Snaplet.html#v:makeSnaplet
"makeSnaplet :: Text -> Text -> Maybe (IO FilePath) -> Initializer b v v -> SnapletInit b v"
[nestSnaplet]:
http://hackage.haskell.org/package/snap-0.6.0/docs/Snap-Snaplet.html#v:nestSnaplet
"nestSnaplet :: ByteString -> Lens v (Snaplet v1) -> SnapletInit b v1 -> Initializer b v (Snaplet v1)"
[modifyResponse]:
https://hackage.haskell.org/package/snap-core-0.9.4.0/docs/Snap-Core.html#v:modifyResponse
"modifyResponse :: (MonadSnap m) => (Response -> Response) -> m ()"
[setResponseCode]:
https://hackage.haskell.org/package/snap-core-0.9.4.0/docs/Snap-Core.html#v:setResponseCode
"setResponseCode :: Int -> Response -> Response"
[handler]:
http://hackage.haskell.org/package/snap-0.6.0/docs/Snap-Snaplet.html#t:Handler
[monadSnap]:
http://hackage.haskell.org/package/snap-core-0.9.4.0/docs/Snap-Core.html#t:MonadSnap
[fromRow]:
https://hackage.haskell.org/package/postgresql-simple-0.4.10.0/docs/Database-PostgreSQL-Simple-FromRow.html
[fromRowF]:
https://hackage.haskell.org/package/postgresql-simple-0.4.10.0/docs/Database-PostgreSQL-Simple-FromRow.html#v:field
"fromRow :: RowParser a"
[toJson]:
https://hackage.haskell.org/package/aeson-0.6.1.0/docs/Data-Aeson.html#t:ToJSON
[value]:
https://hackage.haskell.org/package/aeson-0.6.1.0/docs/Data-Aeson.html#t:Value
[encode]:
https://hackage.haskell.org/package/aeson-0.6.1.0/docs/Data-Aeson-Generic.html#t:encode
"encode :: Data a => a -> ByteString"
[postgres-snaplet]:
https://hackage.haskell.org/package/snaplet-postgresql-simple-0.6/docs/Snap-Snaplet-PostgresqlSimple.html
[query]:
https://hackage.haskell.org/package/snaplet-postgresql-simple-0.6/docs/Snap-Snaplet-PostgresqlSimple.html#v:query_
"query_ :: (HasPostgres m, FromRow r) => Query -> m [r]"
[execute]:
https://hackage.haskell.org/package/snaplet-postgresql-simple-0.6/docs/Snap-Snaplet-PostgresqlSimple.html#v:execute
"execute :: (HasPostgres m, ToRow q) => Query -> q -> m Int64"
[writeLBS]:
http://hackage.haskell.org/package/snap-core-0.9.4.0/docs/Snap-Core.html#v:writeLBS
"writeLBS :: MonadSnap m => ByteString -> m ()"
[writelbs-source]:
http://hackage.haskell.org/package/snap-core-0.9.4.0/docs/src/Snap-Internal-Types.html#writeLBS
[getPostParam]:
https://hackage.haskell.org/package/snap-core-0.9.4.0/docs/Snap-Core.html#v:getPostParam
"getPostParam :: MonadSnap m => ByteString -> m (Maybe ByteString)"
[only]:
https://hackage.haskell.org/package/postgresql-simple-0.4.9.0/docs/Database-PostgreSQL-Simple.html#t:Only
[toJsonF]:
https://hackage.haskell.org/package/aeson-0.6.1.0/docs/Data-Aeson.html#v:toJSON
"toJSON :: a -> Value"
