How to write a Haskell web service (from scratch) - Part 3

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, part 2.

3. Run a basic Haskell web app using 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.

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.

{-# LANGUAGE OverloadedStrings #-}importWeb.ScottyimportNetwork.HTTP.Typesmain=scotty3000$doget"/"$do-- handle GET request on "/" URLtext"This was a GET request!"-- send 'text/plain' responsedelete"/"$dohtml"This was a DELETE request!"-- send 'text/html' responsepost"/"$dotext"This was a POST request!"put"/"$dotext"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:

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

Change it to:

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.

Handling more complex requests

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

-- set a header:post"/set-headers"$dostatusstatus302-- Respond with HTTP 302 status codesetHeader"Location""http://www.google.com.au"-- named parameters:get"/askfor/:word"$dow<-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=somenamename<-param"name"textname-- match a route regardless of the methodmatchAny"/all"$dotext"matches all methods"-- handler for when there is no matched route-- (this should be the last handler because it matches all routes)notFound$dotext"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:

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

main=scotty3000$do-- get article (json)get"/article"$dojson$Article13"caption""content"-- Call Article constructor and encode the result as JSON-- post article (json)post"/article"$doarticle<-jsonData::ActionMArticle-- Decode body of the POST request as an Article objectjsonarticle-- Send the encoded object back as JSON

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