How to write a Haskell web service (from scratch) - Part 3
Tags: haskell, rest, scotty, cabal
Published: 2018-10-22 19:48:50

---
title: How to write a Haskell web service (from scratch) - Part 3
published: true
description: This is a hands-on guide on creating a Haskell based web service. No previous knowledge of haskell is required. However, basic understanding of RESTful web services is assumed.
tags: haskell, rest, scotty, cabal
cover_image: https://thepracticaldev.s3.amazonaws.com/i/i1367p6esagi4mmwljp1.jpg
---

_This is the third part of a hands-on guide on creating a Haskell based web service. No previous knowledge of haskell is required. However, basic understanding of RESTful web services is assumed. Other posts in this series: [part 1](https://dev.to/parambirs/how-to-write-a-dockerized-haskell-web-servicefrom-scratch---part-1-3m7c), [part 2](https://dev.to/parambirs/how-to-write-a-haskell-web-servicefrom-scratch---part-2-6pi)._

# 3. Run a basic Haskell web app using Scotty

## Introducing [Scotty](https://github.com/scotty-web/scotty)

There are many web frameworks available for Haskell: Yesod, Snap, Scotty, etc. I chose Scotty over others as it seemed easier to get started with.

We’ll write a simple web service that responds to various HTTP request types (GET, PUT, POST, DELETE). We’ll see how to get request headers, path parameters and form fields and how to respond with plain-text, html or JSON response.

## Initialise Cabal

Let’s initialise a cabal app for our web service. I’ve mostly chosen the default options. Two notable exceptions include:
license: 9) MIT
source directory: 2) server

```
% mkdir scotty-webapp-example
% cd scotty-webapp-example
% cabal sandbox init
Writing a default package environment file to
/Users/psingh/tmp/haskell/eac-articles/scotty-webapp-example/cabal.sandbox.config
Using an existing sandbox located at
/Users/psingh/tmp/haskell/eac-articles/scotty-webapp-example/.cabal-sandbox
% cabal init
Package name? [default: scotty-webapp-example]
Package version? [default: 0.1.0.0]
...
...
```

## Write the server code

Since we told `cabal` earlier that our main module for the executable will be `Main.hs`, and that it will live inside the `server` folder, let’s add `server/Main.hs` file to our source.

```haskell
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Network.HTTP.Types
 
main = scotty 3000 $ do
  get "/" $ do                         -- handle GET request on "/" URL
    text "This was a GET request!"     -- send 'text/plain' response
  delete "/" $ do
    html "This was a DELETE request!"  -- send 'text/html' response
  post "/" $ do
    text "This was a POST request!"
  put "/" $ do
    text "This was a PUT request!"
```

As you can probably figure out, this code will start a server on port 3000 and:

- For a `GET` request on the `/` path, the server will respond with an HTML response with the content “This was a GET request!”
- For a `DELETE` request on the `/` path, the server will respond with a ‘plain-text’ response with the content “This was a DELETE request!”

## Add dependencies

Let’s add a dependency for `scotty` and `http-types` libraries in our cabal file.

This is how the build-depends segment of the scotty-webapp-example.cabal files looks right now:

```haskell
build-depends: base >=4.8 && <4.9
```

Change it to:

```haskell
build-depends: base >=4.8 && <4.9
             , scotty
             , http-types
```

Next, run `cabal install` to add the dependencies into your sandbox followed by `cabal run` to run the server.

```
% cabal install
Resolving dependencies…
Notice: installing into a sandbox located at
/Users/psingh/tmp/haskell/eac-articles/scotty-webapp-example/.cabal-sandbox
Configuring ansi-terminal-0.6.2.3…
Configuring appar-0.1.4…
…
…
% cabal run
Package has never been configured. Configuring with default flags. If this fails, please run configure manually.
Resolving dependencies…
Configuring scotty-webapp-example-0.1.0.0…
Preprocessing executable ‘scotty-webapp-example’ for
scotty-webapp-example-0.1.0.0…
[1 of 1] Compiling Main ( server/Main.hs, dist/build/scotty-webapp-example/scotty-webapp-example-tmp/Main.o )
Linking dist/build/scotty-webapp-example/scotty-webapp-example …
Running scotty-webapp-example…
Setting phasers to stun… (port 3000) (ctrl-c to quit)
```

The server will be running now on port 3000. Let’s verify that (using the excellent [http](https://github.com/jkbrzt/httpie) tool:

```
% http delete :3000
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Date: Mon, 14 Sep 2015 05:44:57 GMT
Server: Warp/3.1.3
Transfer-Encoding: chunked
This was a DELETE request!
```

Great!

## Handling more complex requests

Let’s add a few more handlers to handle different kinds of requests. Add the following to `server/Main.hs`:

```haskell
-- set a header:
post "/set-headers" $ do
 status status302  -- Respond with HTTP 302 status code
 setHeader "Location" "http://www.google.com.au"
-- named parameters:
get "/askfor/:word" $ do
  w <- param "word"
  html $ mconcat ["<h1>You asked for ", w, ", you got it!</h1>" ]
-- unnamed parameters from a query string or a form:
post "/submit" $ do  -- e.g. http://server.com/submit?name=somename
 name <- param "name"
 text name
-- match a route regardless of the method
matchAny "/all" $ do
 text "matches all methods"
-- handler for when there is no matched route
-- (this should be the last handler because it matches all routes)
notFound $ do
 text "there is no such route."
```

## Encode/Decode JSON

Most web services these days interact via JSON. Haskell provides a type safe way to encode/decode JSON strings using the [Aeson]() library.

### Defining the model

Let’s create an `Article` data type that could represent a news article for example. An article consists of 3 fields: anInteger id, a Text title and a Text bodyText. By making Article an instance of `FromJSON` and `ToJSON` typeclasses, we can use Aeson library for converting between JSON strings and Article objects. Add the following code to the file `server/Article.hs`:

```haskell
{-# LANGUAGE OverloadedStrings #-}
module Article where
import Data.Text.Lazy
import Data.Text.Lazy.Encoding
import Data.Aeson
import Control.Applicative
 
-- Define the Article constructor
-- e.g. Article 12 "some title" "some body text"
data Article = Article Integer Text Text -- id title bodyText
     deriving (Show)
 
 
-- Tell Aeson how to create an Article object from JSON string.
instance FromJSON Article where
     parseJSON (Object v) = Article <$>
                            v .:? "id" .!= 0 <*> -- the field "id" is optional
                            v .:  "title"    <*>
                            v .:  "bodyText"
 
 
-- Tell Aeson how to convert an Article object to a JSON string.
instance ToJSON Article where
     toJSON (Article id title bodyText) =
         object ["id" .= id,
                 "title" .= title,
                 "bodyText" .= bodyText]
```

We’ll need to add a couple of routes to our Scotty router function to handle encoding and decoding Article types:

```haskell
main = scotty 3000 $ do
 
  -- get article (json)
  get "/article" $ do
    json $ Article 13 "caption" "content" -- Call Article constructor and encode the result as JSON
 
  -- post article (json)
  post "/article" $ do
    article <- jsonData :: ActionM Article -- Decode body of the POST request as an Article object
    json article                           -- Send the encoded object back as JSON
```

We’ll also need to add a couple of dependencies to `scotty-webapp-example.cabal` file:

```haskell
build-depends: base >=4.8 && <4.9
             , scotty
             , http-types
             , text
             , aeson
```

### Test JSON encoding/decoding

Let’s fire up Scotty and see if it can handle JSON properly:

#### GET /article

```
% http get :3000/article
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 14 Sep 2015 06:51:17 GMT
Server: Warp/3.1.3
Transfer-Encoding: chunked
{
  “bodyText”: “content”,
  “id”: 13,
  “title”: “caption”
}
```

#### POST /article

```
% http post :3000/article id:=23 title=”new caption” bodyText=”some content”
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 14 Sep 2015 06:56:57 GMT
Server: Warp/3.1.3
Transfer-Encoding: chunked
{
  “bodyText”: “some content”,
  “id”: 23,
  “title”: “new caption”
}
```

## Source

The complete source for this section is available on [github](https://github.com/parambirs/hello-scotty/tree/with-json-types)

# Finally

I hope you found this tutorial useful. Please let me know if something didn’t work for you or if I missed documenting any step.