A very simple JSON API in Haskell
Tags: haskell, scotty, ioref, api
Published: 2021-01-11 18:28:53

---
title: A very simple JSON API in Haskell
published: true
description: Using scotty and an in-memory DB using IORef to implement a very simple JSON API in Haskell
tags: haskell, scotty, IORef, API
//cover_image: https://direct_url_to_image.jpg
---

_Note: This post is also available on my personal blog. If you prefer to read it there, please [click here](https://blog.cdimitroulas.com/posts/2021-01-11-simple-json-api-haskell.html)_

# The simplest Haskell JSON API tutorial

I was following along on [this blog post](https://felixmulder.com/writing/2019/10/05/Designing-testable-components.html) by Felix Mulder and I found it quite challenging to grasp the main concepts as a newcomer to Haskell for a variety of reasons.

This is my attempt to recreate the JSON API from that blog post using a much more simplified approach which doesn't require as much Haskell knowledge to understand the code. I hope this is useful to others and I'm sure it will be a useful learning experience for myself!

Credit goes to Sean Hess for [this blog post](https://seanhess.github.io/2015/08/19/practical-haskell-json-api.html) which also helped me along the way

## Let's build an JSON API!

In this tutorial we're going to be building a very simple API with two endpoints:

- Create a user `POST /user`, receive the new user ID (String) in the response

- Delete a user `DELETE /user/:userId`

## Getting started

This tutorial assumes that you will be using `stack` to build your Haskell project.

A `package.yaml` file you can copy to ensure you have the right dependencies and language extensions can be found here: https://github.com/cdimitroulas/simple-json-api-haskell/blob/main/package.yaml

All the code we write in this post can also be found at:
https://github.com/cdimitroulas/simple-json-api-haskell

## Our domain types

Lets begin by defining some simple types for our domain and the necessary FromJSON instances for them.

```haskell
-- User.hs
module User where

import Data.Aeson
import Data.Text
import GHC.Generics

data User = User
  { userId :: Text,
    userName :: Text
  }

-- Data type which describes the request which
-- will be received to create a user
data CreateUserRequest = CreateUserRequest
  { name :: Text,
    password :: Text
  }
  deriving (Generic)

-- We define a FromJSON instance for CreateUserRequest
-- because we will want to parse it from a HTTP request
-- body (JSON).
instance FromJSON CreateUserRequest
```

## Faking a database

To keep this tutorial simple and avoid us having to set up a database we will be using a mutable variable to store our users. Mutable variables in Haskell I hear you cry?! Is that even a thing?!

Well, it's probably not exactly what you are imagining if you are coming from another language where mutating variables is easily achievable but in Haskell `IORef` provides a way to mutate a variable, so we are going to use this! (This approach is obviously just for educational purposes and is in no way recommended for a real application :D)

```haskell
-- Db.hs
module Db
  ( DbUsr (..),
    getUserStore,
    insertUser,
    deleteUser,
    mkDb,
    UserStore (..),
  )
where


import Data.IORef
import Data.List (sort)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Text (Text)

data DbUsr = DbUsr
  { dbUsrName :: Text,
    dbUsrPassword :: Text
  }
  deriving (Show)

newtype UserStore = UserStore {unUsrStore :: IORef (Map Int DbUsr)}

-- Creates our initial empty database
mkDb :: IO UserStore
mkDb = do
  ref <- newIORef (Map.empty :: Map Int DbUsr)
  pure $ UserStore ref

-- Accepts a default value to return in case the list is empty
safeLast :: a -> [a] -> a
safeLast x [] = x
safeLast _ (x : xs) = safeLast x xs

-- Utility to allow us to read the data in our "database"
getUserStore :: UserStore -> IO (Map Int DbUsr)
getUserStore (UserStore store) = readIORef store

-- VERY naive utility to get the next unique user ID
getNextId :: UserStore -> IO Int
getNextId x = (+ 1) . safeLast 0 . sort . Map.keys <$> getUserStore x

-- insertUser uses getNextId to get the next ID and then updates our database using
-- modifyIORef from the Data.IORef library. It returns the new ID as a result.
insertUser :: UserStore -> DbUsr -> IO Int
insertUser userStore usr = do
  nextId <- getNextId userStore
  modifyIORef (unUsrStore userStore) (Map.insert nextId usr)
  pure nextId

-- deleteUser updates our database by deleting the relevant user data 
deleteUser :: UserStore -> Int -> IO ()
deleteUser usrStore uid = modifyIORef' (unUsrStore usrStore) (Map.delete uid)
```

## Putting together our JSON API

Now we have all of the pieces of our application that we need, it's time to put them together and expose them via a JSON API!  In order to do this we will be using the [`scotty`](https://github.com/scotty-web/scotty) library (very similar to `Sinatra` from Ruby!).

```haskell
-- Main.hs
module Main where

import Control.Monad.IO.Class
import qualified Db
import User (CreateUserRequest (..))
import Web.Scotty

main :: IO ()
main = do
  -- Initialize our fake DB
  db <- Db.mkDb

  -- Run the scotty web app on port 8080
  scotty 8080 $ do
    -- Listen for POST requests on the "/users" endpoint
    post "/users" $
      do
        -- parse the request body into our CreateUserRequest type
        createUserReq <- jsonData

        -- Create our new user.
        -- In order for this compile we need to use liftIO here to lift the IO from our
        -- createUser function. This is because the `post` function from scotty expects an
        -- ActionM action instead of an IO action
        newUserId <- liftIO $ createUser db createUserReq

        -- Return the user ID of the new user in the HTTP response
        json newUserId

    -- Listen for DELETE requests on the "/users/:userId" endpoint
    delete "/users/:userId" $ do
      -- Get the value of the userId from the URL
      userId <- param "userId"

      -- Delete the relevant user
      -- Same as with the user creation, we need to use liftIO here.
      liftIO $ Db.deleteUser db userId

-- Our createUser function simply deals with constructing a DbUsr value and passes it
-- to the Db.insertUser function
createUser :: Db.UserStore -> CreateUserRequest -> IO Int
createUser db CreateUserRequest {name = uname, password = pwd} = Db.insertUser db dbusr
  where
    dbusr = Db.DbUsr {Db.dbUsrName = uname, Db.dbUsrPassword = pwd}
```

## Conclusion

We now have our completed JSON API! We can run it using `stack run`.

You should see this output:
```
Setting phasers to stun... (port 8080) (ctrl-c to quit)
```

We can then make some http requests to check it's all working. I like to use the [httpie](https://httpie.io/) command line tool for this:
![Screenshot of the results of running some POST and DELETE requests against our API](https://dev-to-uploads.s3.amazonaws.com/i/445g4yqbeot81rk92b4r.png)