A REST API with http4s and Cats IO Monad

Alan Devlin
4 min readMay 30, 2019

--

http4s is an HTTP library for Scala. http4s has good documentation including a tutorial. However, the tutorial only covers the most basic of applications — and if you are not super comfortable with the approach — it might be difficult to extend it to real-life. At least it was for me! We are going to take it one step further and implement a very simple CRUD (Create Read Update Delete) web-server application. Hopefully this should give you enough to get started with a real API.

All the code for this demo can be found on github.

Why http4s?

The latest stable version — 0.18 is built with the cats-effect IO monad in mind. If that means something to you, great -read on! If not checkout my introduction to purity in programming and the IO monad, and read on. This monadic marvel will allow us to construct a purely functional API with an absolute minimum of pain. You can use different web server implementations, but we are going to use the http4s native server: blaze.

The App.

We are going to keep things simple. The object we are are going to be using is a Hut:

case class Hut(name: String)

Our hut class has one field: name. We are also going to have a HutWithId:

case class HutWithId(id: String, hut: Hut)

For simplicity we are going to store our hut objects in memory, rather than use a database.

Setup the App.

We are going to use the template from http4s to bootstrap our app. Run:

sbt -sbt-version 1.2.8 new http4s/http4s.g8 -b 0.20

give a name for your app, and an organisation (for example com.github.[your-username]) — accept the rest of the defaults.

All going well you should be able to run your app and make a simple request like:

$ curl -i http://localhost:8080/hello/world

Build the App. Storage layer.

We are going to add CRUD functionality: The HutRepository is going to store and retrieve huts:

package io.github.spf3000.hutsapiimport java.util.UUID
import cats.effect._
import scala.collection.mutable.ListBuffer
import cats.implicits._
import io.github.spf3000.hutsapi.entities._
final case class HutRepository[F[_]](private val huts: ListBuffer[HutWithId])(
implicit e: Sync[F]
) {
val makeId: F[String] = e.delay { UUID.randomUUID().toString }
def getHut(id: String): F[Option[HutWithId]] =
e.delay { huts.find(_.id == id) }
def addHut(hut: Hut): F[String] =
for {
uuid <- makeId
_ <- e.delay { huts += HutWithId(uuid, hut) }
} yield uuid
def updateHut(hutWithId: HutWithId): F[Unit] = {
for {
_ <- e.delay { huts -= hutWithId }
_ <- e.delay { huts += hutWithId }
} yield ()
}
def deleteHut(hutId: String): F[Unit] =
e.delay { huts.find(_.id == hutId).foreach(h => huts -= h) }
}
object HutRepository {
def empty[F[_]](implicit f: Sync[F]): F[HutRepository[F]] = f.delay {
new HutRepository[F](ListBuffer())
}
}

Ok so you might notice that if you forget about all the IO’s it looks very impure — I am mutating state in place, (with the += and -=) and makeId generates a random number — also not referentially transparent. But all these functions are pure! In fact things are so pure, I don’t even have to worry about whether Scala is going to store things (memoizations), i.e. observe:

val makeId: IO[String] = IO { UUID.randomUUID().toString }

this could be a def — it actually doesn’t matter — with IO you can just use defs when you need arguments and vals when you don’t, as you have control over when things are evaluated.

Notice also that the creation of the ListBuffer is in IO as this also breaks referential transparency.

Build the App. Http layer.

In the http layer we are going to call our repository methods and return some of the pre-defined responses that http4s gives us such as OK and NotFound

package io.github.spf3000.hutsapiimport org.http4s.dsl.Http4sDsl
import cats.effect.Sync
import org.http4s.HttpRoutes
import cats.effect.Effect
import cats.implicits._
import entities._
import org.http4s._
import org.http4s.circe._
import io.circe.generic.auto._
import io.circe.syntax._
object HutRoutes {val hutsPath = "huts"def hutRoutes[F[_]](hutRepo: HutRepository[F])
(implicit F: Sync[F]) = {
val dsl = new Http4sDsl[F] {}
import dsl._
HttpRoutes.of[F] {
case GET -> Root / hutsPath / hutId => {
hutRepo.getHut(hutId)
.flatMap(_ match {
case Some(hut) => Ok(hut.asJson)
case None => NotFound(hutId)
})
}
case req @ POST -> Root / hutsPath =>
req
.decodeJson[Hut]
.flatMap(hutRepo.addHut)
.flatMap(hut => Created(hut))
case req @ PUT -> Root / hutsPath =>
req
.decodeJson[HutWithId]
.flatMap(hutRepo.updateHut)
.map(_ => Response(status = Ok))
case DELETE -> Root / hutsPath / hutId =>
hutRepo
.deleteHut(hutId)
.map(_ => Response(status = NoContent))
}
}
}

Use the App.

Open a terminal window, cd into the top directory of the app and type:

sbt run

Now you are up and running — and with a clear conscience! To create a new hut you can open another terminal and run the following command:

curl -v -H "Content-Type: application/json" -X POST http://localhost:8080/huts -d '{"name":"River Hut"}'

(this should give you back the id of your newly created hut).

You can update a hut:

curl -v -H "Content-Type: application/json" -X PUT http://localhost:8080/huts -d '{"id":"123","hut":{"name":"Mountain Hut"}}'

GET a hut:

$ curl -i http://localhost:8080/huts/123

Which will give a 200 (OK) response and the json of our hut:

and finally delete a hut:

curl -v -X DELETE http://localhost:8080/huts/123

And there you have it — You have basically done nothing but IO and yet the code is totally pure! The app above demonstrates both:

  • a simple usage of http4s
  • an example of shared state in functional programming

If you want to know more about the second point: have a look at Systemfw’s talk on the subject, it’s really great.

--

--