http4s is Scala’s answer to Java’s servlets. Apart from being a minimal, idiomatic Scala interface for HTTP services, it has a range of impressive features as well:
- Typeful : Everything in http4s is a type. Standard headers are semantic types, codecs are typeclass, and even requests and responses are immutable models
- Functional : Cats is one of the supporting pillar to http4s, and cats-effect manages I/O in this library making the library functional in nature
- Streaming : FS2, a streaming library, is another pillar of http4s. It provides request and response body in a streaming way, thus processing large payloads in constant space.
- Cross-Platform : http4s cross-builds for Scala.js and Scala Native. We can deploy the code to browsers and the JVM.
HttpRoutes[F] – Heart of http4s
The http4s library is based on the concepts of Request and Response. A request is mapped to a response through a set of function of type Request => Response
. These functions are routes, and a server is set of such routes. Without getting into a a lot of details, consider the following points:
- Producing a response from a request requires some side-effects, like interaction with database, with external services, etc. This transforms our original route function to
Request => F[Reponse]
- Not every request has a response, hence the type of the function can be tranlsated to
Request => F[Option[Response] => OptionT[F, Response]
(monad transformer) - Cats Kleisli monad transformer
Kleisli[F[_], A, B]
is a wrapper around functionA => F[B]
, thus transforming our route function toKleisli[OptionT[F, *], Request, Response]
Finally, the http4s library defines a type alias for the Kleisli monad transformer that is easier to understand for human beings: HttpRoutes[F]

Creating endpoint
We can create a simple route type, its endpoint and behaviour using http4s as follows:
val route: HttpRoutes[IO] = HttpRoutes.of[IO] {
case GET -> Root / "hello" / name => Ok(s"""Hello, ${name}!""")
}
This can be represented diagrammatically as:



Testing the endpoint
One beautiful thing about the HttpRoutes[F]
model is that we don’t need a server to test our route. We can construct our own request and experiment directly in the REPL. For the above endpoint, we need to create a Request and a Response wrapped in IO for testing.
//Create Request
val helloRequest: Request[IO] = Request[IO](Method.GET, uri"/hello/John")
// Output
helloRequest: Request[IO] = Request(method=GET, uri=/hello/John,
httpVersion=HTTP/1.1, headers=Headers(), entity=Entity.Empty)
//Create Response
val response: IO[Response[IO]] = route.orNotFound.run(helloRequest)
// Output
response: IO[Response[[+A]cats.effect.IO[A]]] = IO(...)
// To run the effect
response.unsafeRunSync()
//Output
Response(
status=200,
httpVersion=HTTP/1.1,
headers=Headers(Content-Type: text/plain; charset=UTF-8,
Content-Length: 12), entity=Entity.Strict(12 bytes total)
)
// To read the reponse
response.flatMap(_.as[String]).unsafeRunSync()
//Output
res0: String = Hello, John!
Creating Server and Client using http4s
http4s supports multiple server backends that we can use in production. We will be using the library native backend support for Ember to create server as well as client.
Ember Server
We can use the following code to create an ember server using configurable host-port values and route(s) creating the HttpApp:
EmberServerBuilder
.default[IO]
.withHost(Host.fromString(host).get)
.withPort(Port.fromInt(port).get)
.withHttpApp(route.orNotFound)
.use(_ => IO.never)
.as(ExitCode.Success)
The above piece of code can be written inside the cats-effect provided IOApp
trait that has an abstract run
method returning IO[ExitCode]
. An IOApp
runs the process and adds a JVM shutdown hook to interrupt the infinite process and gracefully shut down your server when a SIGTERM is received. The orNotFound
API takes care of handling any route that is not defined in the server.
Ember Client
Similarly, Blaze can be used to create Http client as well using the following code inside IOApp
:
EmberClientBuilder.default[IO].build
.use { client =>
val response = client.expect[String]("http://localhost/hello/John")
println(s"Response: ${response.unsafeRunSync()") //For testing
}
//Output
Response: Hello, John
Conclusion
The http4s ecosystem helps us in creating server API over HTTP. The library fully embraces the functional programming paradigm, using Cats and Cats Effect as building blocks. It is easy to define routes and behaviours, tests these routes without a server, create a server and a client. In future blogs, we will look into how we can use the same library to extract query parameters from the endpoints, create middlewares, and provide authentication and streaming behaviour.
References:
- https://blog.rockthejvm.com/http4s-tutorial/
- https://http4s.org/
- https://medium.com/@alandevlin7/http4s-v0-2-1d2d859d86c4