< Back
July 10, 2020

Three Scala Solutions for REST APIs

A comparison between AkkaHTTP, Play, and Http4s

Written by:

Ivo Saraiva

This blog post is based on a talk I presented at the first Scala meetup in Porto (Portugal). Standing in front of an audience of developers, my intent was to show how these tools tackle a simple Scala HTTP REST API. This blog post aims to explore which approach may better suit your needs and style.

What is the problem?

Of the many definitions one might find of a software developer, a common trait among them would be someone that solves problems. So we need a problem to solve. Let’s pick a common one: how to build an HTTP REST API in Scala.

trait Controller[R] {

def get(id: Id): R

def post(resource: JSON): R

def put(id: Id, resource: JSON): R

def delete(id: Id): R


trait Service[R] {

def create(resource: JSON): Future[Id]

def read(id: Id): Future[R]

def update(id: Id, resource: JSON): Future[Unit]

def delete(id: Id): Future[Unit]


trait Repository[R] {

def create(resource: R): Future[Id]

def read(id: Id): Future[R]

def update(id: Id, resource: R): Future[Unit]

def delete(id: Id): Future[Unit]


I picked a traditional architecture with a Controller to handle requests, a Service to handle business logic, and a Repository to handle data storage. I will only focus on the Controller part of our problem. Our Controller should handle the common HTTP requests and return HTTP responses, here represented by the type parameter R.

The first question is “why Scala?” The answer is because this is a blog post about Scala. I’m assuming the language was previously chosen and we are at the “what tools to use?” stage. Scala has many ways and styles. It can be more OO more FP or a hybrid. I will try to solve our problem with three different tools. I won’t compare performance, I just want to give you the notion of how these tools tackle our problem and how it feels to work with them. Hopefully, you will get some insight on which one better matches your needs and style.


AkkaHTTP belongs to the Akka toolkit. It treats an HTTP server as a stream of HTTP requests that are transformed into HTTP responses. As such, it uses AkkaStreams to implement HTTP servers. What is AkkaStreams? It is the Akka implementation of Reactive Streams. What is a Reactive Stream? You can get more detailed information on that elsewhere, but in a very succinct way, it is a flow of data that is resilient to failure, i.e. it knows what to do when stuff happens without crashing, and it has backpressure, i.e. there is an attempt to control the flow of data when, for instance, a consumer of data is slower than a provider of data.

Akka implements Reactive Streams using the Actor Model. What is the Actor Model? Again, this isn’t the place to study it, but in a very succinct way, it is a concurrency model based on actors. An actor is a programming entity that can do just a few things:

  • Receive messages in a mailbox.
  • Send messages.
  • Create other actors.
  • Execute a behavior based on its state and messages received.

The only way to communicate with an actor is through messages, and messages in the mailbox are processed one by one. These characteristics make actors suitable to handle concurrency and to keep the shared state safe.

I believe it is easy to think of AkkaStreams as pipes that can transform whatever goes through them and that can be connected as we like. So we need to build a pipe that takes HTTP requests on one end, makes some magic happen in the middle, and spits out an HTTP response on the other end.

Setup Akka

The first step is to setup akka. We will need something like the following in the code:

implicit val system: ActorSystem = ActorSystem(“amazingActorSystem")

implicit val materializer: ActorMaterializer = ActorMaterializer()

implicit val executionContext: ExecutionContext = system.dispatcher

Since we are dealing with the actor model, we will need an ActorSystem. This handles the actor stuff behind the scenes. Think of it as one Actor to rule them all. We need an ActorMaterializer. A stream in Akka is just an object that can be passed around as needed. To actually start the flow of data, the stream needs to be “materialized.” That is what the ActorMaterializer does. Finally we need an execution context to handle Futures. These three lines of code are the minimum setup we need to start our server. Additionally, placing an “application.conf” file in the right place in our project allows for tweaking other configurations like parallelism, timeouts, connection pools, and many other details.

Handle the Requests

We need to define how to handle the incoming requests, so let’s implement our Controller:

override def get(id: Id): Future[HttpResponse] =



.map(resource => HttpResponse(StatusCodes.OK, entity =

HttpEntity(ContentTypes.application/json, resource)))

override def post(resource: JSON): Future[HttpResponse] =


.map(id =>


.withHeaders(RawHeader(“Location", s"/$id)))

override def delete(id: Id): Future[HttpResponse] =


.map(_ => HttpResponse(StatusCodes.NoContent))

We define our type parameter R to be Future[HttpResponse]. We have Future because everything is asynchronous and HttpResponse is, surprisingly, the Akka representation of an HTTP response. In each method implementation, we call a service to execute some logic or to get some data, make the desired processing, and construct an HttpResponse. The constructor of this class allows the definition of the many aspects of a response like status code, content types, or headers. It uses implicit Mashallers. These are Akka serializers that send our Scala data structures through the wire. Mashallers for the common content types already exist, like raw string or JSON. Others can easily be implemented.

Now that we have a way of building responses, we need a way of directing incoming requests to the right place. AkkaHTTP has a powerful RoutingDSL. There are two main concepts in this DSL: Route and Directive. A Directive matches an HTTP request based on path, HTTP verb, body, and query and it processes those matched requests. A Route is a composition of Directives forming a road with many paths for an HTTP request to follow. It works like this:

val routes: Route =

path(“resource" / Segment) { id =>

get {


case Success(response) => complete(response)

case Failure() => complete(StatusCodes.ServiceUnavailable)


} ~

put {
entity(as[JsObject]) { json =>

onComplete(put(id, json.toString)) {

case Success(_) => complete(StatusCodes.NoContent)

case Failure(_) => complete(StatusCodes.ServiceUnavailable)



} ~ ???


This Route begins with the “path” Directive. If the HttpRequest has a path “resource” followed by some other string, there is a match and we enter the Directive with this string assigned to the “id” parameter. Next, we find the “get” Directive. Not surprisingly, if the HttpRequest is a GET, then we enter this Directive. The next one is “onComplete.” This Directive takes a Future. In this case it is the “get” method from our Controller called with the “id” parameter. According to the result of this Future, we send the appropriate response back with the “complete” method. What if our HttpRequest is a PUT not a GET? For this, we use the “~” operator. This just means “if the previous Directive does not match, try the next one.” With a PUT we would skip the “get” Directive and enter the “put” one. The following Directive is “entity.” In this case, this Directive tries to parse the request body as a JSON. If successful, we enter the Directive and the parsed JSON is assigned to the “json” parameter. We then complete it appropriately by calling our Controller as before. Akka allows parsing the contents of a request as any data structure we like. We just need to implement the right Unmarshaller, which is just Akka’s name for a deserializer. Similar to Marshallers, Unmashallers for the common content types already exist, like raw string or JSON. Others can easily be implemented.

Because I’m lazy and laziness is a requirement in FP, I left the other two methods of our Controller out of the Route, but check if you can do it yourself.

I’ve shown here just a handful of Directives, but AkkaHTTP comes with tens of them that handle pretty much everything related to HTTP requests, like path segments, query parameters, headers, authentication, cookies, logging, and more. If you can think of anything else you need, implementing new Directives is also possible.

Start the server

Finally, we just need to start the HTTP server. The HTTP class in AkkaHTTP has a few methods to start a server, like this one:

val serverBinding: Future[ServerBinding] = Http().bindAndHandle(routes, “localhost", 8000)

You just need to provide the “routes” and define the host and port to listen to. The created ServerBinding can later be used to unbind the used port and you should then terminate the actor system.

As you will find repeated in the Akka documentation, AkkaHTTP is not a framework, it is a toolkit. This means they aim to provide an un-opinionated set of tools that we can use as we see fit. They don’t really care how we used them. Either way, AkkaHTTP, in my humble opinion, allows the pretty fast creation of an HTTP server with a considerable set of options to be tweaked to our heart’s desire. It has a powerful RoutingDSL that can easily process requests. Even though we did not need it to solve our problem, it also allows the creation of an HTTP client.


Play has a single purpose, to build web applications using Model-View-controller architecture. As a framework, it has a default way to handle pretty much every aspect of a web application. If you don’t like Play’s opinions on something, you are always free to try your way.

Setup Play

Play follows the convention when it comes to configuration, so using the default configuration doesn’t require you to do anything. If you want to tweak stuff, placing an application.conf file in the resources folder with your configuration overrides is enough.

Taking Action

There are three important concepts in Play: Controller, Action, and Result. A Controller is a class we need to implement that has methods that return Actions. An Action just represents a function that takes an HTTP request and returns a Result. A Result is just what Play calls an HTTP response.

When we implement our Controller trait, we need to also implement a Play Controller.

abstract class ControllerImpl(cc: ControllerComponents, res: Resource)

(implicit ec: ExecutionContext)

extends AbstractController(cc)

withController[Future[Result]] {

override def get(id: Id): Future[Result] =

ResourceService(res.name).read(id).map(resource => Ok(resource.name))

override def post(resource: JSON): Future[Result] =


.map(id => Created(“").withHeaders(“Location" ->s"/${res.name}/$id))

override def put(id: Id, resource: JSON): Future[Result] =

ResourceService(res.name).update(id, resource).map(_ => NoContent)

override def delete(id: Id): Future[Result] =

ResourceService(res.name).delete(id).map(_ => NoContent)

def getAction(id: Id): Action[AnyContent] = Action.async(get(id))

def postAction: Action[JsValue] = Action.async(parse.json){
request => post(request.body)

def putAction(id: Id): Action[JsValue] = Action.async(parse.json){
request => put(id, request.body.toString)

Let’s look at the constructor of our class. It implements our Controller with the type parameter R being a Future of Result. This is quite similar to AkkaHTTP, it just has different names. Our class also implements Play’s AbstractController, which takes a ControllerComponents instance. This is just something Play uses to handle requests and there is a default one that Play injects into our classes without any effort on our part except for the use of an @Inject annotation. It is also possible to implement our own ControllerComponents if we need custom request processing. Finally, the Resource parameter just identifies the type of resource we want to handle.

To implement our method, we follow the same approach as for AkkaHTTP. We asynchronously get some data from a service and process them into a Result. There is a collection of methods that can create these, like “Ok” and “NoContent” shown above. As with AkkaHTTP, there are implicit serializers that convert our Scala data structures into something that can go through the wire. Here they are called Writables and it is possible to expand the default ones to handle other specific cases.

Because this is a Play Controller, we need to provide Actions. We use “Action.async” to keep things asynchronous. This method takes a function from Request to Future of Result. This function should process and handle the incoming requests. In case we need to process a request’s body, we need to pass a deserializer. We are using the “parse.json” “BodyParser” that is the default deserializer for JSON bodies. This instance comes from the ControllerComponents instance that a Play Controller requires. Next, we use our Controller methods to create the results in the created Actions.

Let’s create two Play Controllers, one to handle ResourceA and one to handle ResourceB.

class ResourceControllerA @Inject() (cc: ControllerComponents)
(implicit ec: ExecutionContext) extends ControllerImpl(cc, ResourceA)

class ResourceControllerB @Inject() (cc: ControllerComponents)
(implicit ec: ExecutionContext) extends ControllerImpl(cc, ResourceB)

GET /resourcea/:id scalameetup.controllers.ResourceControllerA.getAction(id: String)

PUT /resourcea/:id scalameetup.controllers.ResourceControllerA.putAction(id: String)

DELETE /resourcea/:id scalameetup.controllers.ResourceControllerA.deleteAction(id: String)

POST /resourcea scalameetup.controllers.ResourceControllerA.postAction

GET /resourceb/:id scalameetup.controllers.ResourceControllerB.getAction(id: String)

PUT /resourceb/:id scalameetup.controllers.ResourceControllerB.putAction(id: String)

DELETE /resourceb/:id scalameetup.controllers.ResourceControllerB.deleteAction(id: String)

POST /resourceb scalameetup.controllers.ResourceControllerB.postAction

The Router syntax is pretty straight forward. You write the HTTP verb followed by the path and the respective Action that handles the request. Each HTTP path segment starting with “:” can be used as a parameter in the Action method. Some other details exist to handle query parameters or default input.
Contrary to Akka, Play is an opinionated framework; It has a default way of handling each aspect of a web application. For instance, AkkaHTTP is its default server, but others can be used. Guice is the default dependency injection solution, but others can be used. There is a default way of handling JSON or XML documents. It also has its own template engine to respond with pretty web pages, which we didn’t need to solve our current problem. If a big web application with lots of endpoints is required, Play is probably a more focused and better solution than AkkaHTTP.


Http4s has a different approach than the two previous tools. It is a tool in the Cats environment, so it follows a more functional approach based on this functional programming library. So what is Functional Programming?
This is not the time and place for a detailed answer to this question, but let’s try a very simple one with bullet points:

  1. Program with functions
  2. Functions must be total
  3. Functions must be deterministic
  4. Functions must be pure
  5. Code must have referential transparency

Point 1 seems pretty self-explanatory. Point 2 means for every input, we have an output, not a catastrophic event like an Exception. Point 3 means that for the same input we always get the same output; there is no influence from some other code somewhere or from the position of the planets in the sky. Point 4 means that side effects are forbidden; a function only calculates and returns the output without updating some field somewhere, for instance. If these points 1 to 4 are followed, we have Point 5. Referential transparency means that any piece of code can be replaced with its result without changing the meaning of a program.

The Future is no good

The two tools presented so far use Scala’s Future to achieve asynchronicity. Is the Future any good? Does it follow the rules above? Let’s test referential transparency:

for {

_ <- Future { println(“Is the Future Pure?") }

_ <- Future { println(“Is the Future Pure?") }

} yield ()

val future = Future { println(“Is the Future Pure?") }

for {

_ <- future _ <- future } yield ()

How many times do we print in the first for-comprehension? How many times do we do it in the second? I’m sure you know it is twice first and once second. However, we just replaced an expression by its result. There is no referential transparency. The reason is that the Future is eager. As soon as it is created, the code inside starts running in some thread.

When following a more functional approach, as the Cats library does, the Future is no good and something else is needed; we require something that is lazy and composable. Let’s make up a completely original name for it: “Monad.”
After this boring introduction to FP, we will solve our problem once again.

F[_] our Controller

Which Monad should we use? Laziness and procrastination are good qualities to have in FP. We will delay this decision as much as possible. Hopefully, someone else will handle it.

class Http4sController[F[]: Effect](res: Resource)

extends Controller[F[]]

with Http4sDsl[F]

override def get(id: Id):

F[Resource] =


override def post(resource: JSON): F[Id] =


override def put(id: Id, resource: JSON): F[Unit] =

BetterResourceService[F].update(id, resource)

override def delete(id: Id): F[Unit] =


Since we are not picking a Monad for our Controller to return yet, we implement it with a Higher Order Kind. This is a fancy name for a type constructor, something that takes a type and returns a type. For instance, List is a Higher Order Kind. It can receive the type Int and return the type List[Int]. Option is a Higher Order King. It can receive the type String and return the type Option[String]. Future is a Higher Order Kind and so on. F[_] is Scala’s syntax for this. Remember, we have some rules to follow so that everything is nice and functional. To guarantee that rules are followed, we use the :Effect syntax. This makes sure that whatever Monad we pick, it follows the rules in the Cats trait Effect. Now each implemented method returns an F of something. We are no longer using the previous service that would return Futures. This new service also uses a Higher Order Kind, F. Since we made sure that F follows the rules, these return values can be composed nicely.

Finally, the Http4sDsl provides the tools to handle requests, like the HttpRoutes.of method:

val routes: HttpRoutes[F] = HttpRoutes.of[F] {

case GET -> Root / Name / id =>

for {

res <- get(id) response <- Ok(res)

} yield response

case req @ POST -> Root / Name =>

for {

body <- req.as[JSON]

id <- post(body) response <- Created(Header(“Location", s"$Name/$id))

} yield response

case req @ PUT -> Root / Name / id =>

for { body <- req.as[JSON]

_ <- put(id, body) response <- NoContent()

} yield response

case DELETE -> Root / Name / id =>

for { _ <- delete(id) response <- NoContent()

} yield response


This method takes our F and a partial function in which each case clause matches a request and uses the implemented methods to handle it.
It is interesting to notice that every request is handled by a for-comprehension. We guarantee that our F is composable. This makes it easy to reason and work with. Imagine building a new endpoint. What might we need?

  1. Authenticate the user
  2. Authorize the user
  3. Get resource A
  4. Get resource B
  5. Build response with resources

This list of logical steps might easily be replaced by:

for {

user <- authenticate(req) _ <- authorize(user) resourceA <- getA(id) resourceB <- getB(id) response <- Ok(resourceA + resourceB)

} yield response

This nice one to one match only requires that each method returns an F and that F follows our rules.

F[_] a server

Next, we need a server. Http4s uses Blaze. There are a few builder methods to get a server:

def routes[F[_]: Effect]: HttpRoutes[F] =

Http4sController(ResourceA).routes <+> Http4sController(ResourceB).routes

def server[F[_]: ConcurrentEffect: Timer]: Stream[F, ExitCode] =


.bindHttp(8080, “localhost")



The routes method gets the created routes for a resource of type A, the routes for a resource of type B and concatenates them. The server method builds a server with a given port, host, and the created routes. This method returns a Stream. This is not an Akka stream but an fs2 stream. fs2 is another library in the Cats environment to handle streams. Here we have a stream of our Fs that will eventually complete with an ExitCode, our application’s exit code. Blaze has two additional requirements for our F: ConcurrentEffect and Timer. These are just some additional rules to be followed but we keep procrastinating and we don’t pick a Monad yet.

Pick a Monad

If we were writing a software library, we could leave this last step for the user to handle and that would be done. Here it is not the case, and, instead, we reached the end of the world: the main method. We can’t procrastinate anymore and have to pick a Monad:

object Main extends IOApp {

def run(args: List[String]): IO[ExitCode] = server[IO].compile.drain.as(ExitCode.Success)


We are using the IO Monad from CatsEffect. This Monad follows all the rules that we require as it was designed to do so.

Is F[_] worth it?

In this post, I tried to compare three tools in the Scala environment and their approach to solve a simple problem. The first two are based on the Actor model since Play uses AkkaHTTP as the default server, and on Scala’s Future. The third follows a more functional approach and uses Effect monads to keep the code pure. This latter approach has a steeper learning curve.

Functional programming introduces quite a few new concepts like monad, functor, traverse, and others that might take some time to fully grasp. If this is the chosen approach, a bright developer team is required. However, FP allows using small composable functions pieced together to get what you want, and you can do it with intuitive for-comprehensions, as in the new endpoint example above. This makes pure functional code safer and faster to work with.
To answer the question, FP is definitely worth it, but a bright developer team should also know that if we need a small service with one endpoint, AkkaHTTP might be a quicker way to get there.

Did you find this blog post useful? What topics would you like to read about? Please leave your feedback in the comments section below and help us bring you the most relevant content.

About the Author

Ivo Saraiva

A little more than two and a half years ago I was working on a biochemistry lab doing research and decided to get a job as a software developer. Since then, I’ve been learning and working on Scala and functional programming. I keep learning and improving my abilities to solve problems in software development.

– – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

Leave a Reply

Your email address will not be published. Required fields are marked *



Click below to speak to one of our team members.

× How can we help?