Overview of HTTP frameworks for Scala
_Part 1
Which one to choose for a new project: Http4s, ZIO-http, Tapir, or Caliban?
Introduction
Scala, a powerful language blending functional and object-oriented paradigms, offers a rich ecosystem of HTTP frameworks. Each framework brings unique strengths and weaknesses, making the choice a crucial decision for Scala developers. This paper delves into a comparative analysis of four prominent frameworks: Http4s, ZIO-http, Tapir, and Caliban.
By conducting in-depth research and analysis, this paper aims to provide valuable insights for Scala developers and contribute to the ongoing discussion on the best practices for building scalable and efficient HTTP applications in Scala.
Framework Overview
Http4s: A modular and extensible library built on top of Cats-Effect for building HTTP servers and clients, offering flexibility and control over the HTTP layer.
ZIO-http: A high-performance, fully asynchronous HTTP server and client library built on top of the ZIO ecosystem.
Tapir: A type-safe, end-to-end API description framework, generating both server and client code from a single API definition, as well as Open API documentation
Caliban: A GraphQL server library built on top of ZIO, providing a type-safe and performant approach to building GraphQL APIs. Also, it provides a GraphiQL playground for testing the different endpoints.
Comparative Analysis
Choosing the Right Framework for Your Project
The choice of a Scala HTTP framework depends on various factors, including project requirements, team expertise, and performance needs. Caliban is ideal for GraphQL-focused applications, while Http4s offers flexibility and control over the HTTP layer. Tapir is well-suited for API-first development, and ZIO-http provides a high-performance, asynchronous solution. By carefully considering these factors, developers can select the most appropriate framework for their specific needs.
Potential Research Directions
• Benchmarking performance: A detailed performance comparison of these frameworks under various workloads.
• Ecosystem analysis: A deep dive into the available libraries and tools for each framework.
• Real-world case studies: Analysing how these frameworks are used in production environments.
• Future trends: Exploring emerging trends and potential advancements in Scala HTTP frameworks
A Comparative Analysis: My Own Experience
Introduction
In this section we will examine a simple HTTP API developed with each one of the libraries and try to evaluate the good aspects and the drawbacks of each choice. There is not a single answer for all the problems. Every framework could be a good choice for a given project and not a very good one for another.
I used the latest versions available for these four frameworks by the time this article was written (November, 2024). The versions in the build.sbt file are, respectively:
The API to develop is a toy one, is just one “Hello World” endpoint and another one returning a list of books in JSON format. But this exercise will help us to dive a little deeper in each framework and spot the differences and strengths of each framework.
Another point worth mentioning (common to all four frameworks) is that it is difficult sometimes to get the server and API endpoints code compiling because the open source libraries evolve very rapidly and there is not a lot of documentation available or it is not up to date. This article tries to contribute to this aspect, helping you to decide which framework to use and providing simple code examples to get started with a new API.
In the next sections we will examine each framework and see how to create applications, servers and endpoints for each one.
Http4s
This Http4s library is, I guess, the first one of these frameworks to be developed on the Scala ecosystem, as of March of 2014 approximately. It is an open source project that has a strong community behind and many adopters (see https://http4s.org).
Http4s has been improved a lot over the years, in terms of usability, efficiency and support. If you are using (or planning to use) Cats Effect as your effects library, this is the best choice, because it integrates perfectly with Cats as it is built on top of it.
The first time I used it I struggled a little bit with the server configuration, but I was not so experienced with Scala. This time it was very easy to make it compile (the Tapir example was harder for me at this time), with a good help from the official documentation, some code examples I found and of course, generative AI chatbots (that are not always correct in the code, but sometimes are very helpful).
But, let’s dive into the code! I will omit the imports and other parts of the code, but if you want to browse the full example you can go to the repository references, that are in the references section.
The main program file extends the IOApp from Cats-Effect and defines the variable “run” of type “IO[Nothing]”. That means that the program returns a side effect that never finishes, because it is a server that hangs on a loop, waiting for requests to come in.
The implicit val in the first line of the object is for defining the logger for the server.
Then, two GET endpoints are defined, one for the “Hello World” and other one for the JSON endpoint. The BookRepository.getBooks method returns an IO effect that succeeds with a List[Book].
After that, the two routes are combined into an HttpApp and the host and port are defined using some very handy interpolators that return a special data type (Host and Port).
As you can see, the code is very clean, clear and concise. The server configuration is just one line, using the previously defined parameters.
For completeness, this is the BookRepository object:
The getBooks operation is hard-coded here, but it could be a Cats-Effect side-effectful expression for going to a database and selecting the list of books.
ZIO-http
The main competitor of Http4s is ZIO-http, the ZIO effect system counterpart for building high performance HTTP APIs. As its name implies, it is based on the ZIO effect system.
Here we jump directly to the code to compare and contrast with the previous option (Http4s).
The main difference in the code is that we are using ZLayers to create the server. ZLayers is a ZIO solution to the problem of dependency injection, that is used to create, organise, connect object instances and build the application structure, according to its dependencies. For example, a determined “book service” object instance may need a “book repository” instance in order to implement its functionality. For more information about ZIO ZLayers click here (or look into the “References” section).
But apart from that, the solution is also very short and concise. Let’s explain in more detail each part:
The Main object extends the ZIO application class / trait, that is similar to the IOApp.Simple (from Cats-Effect) in the sense that it provides default functionality for a simple desktop application
This Main object overrides the “run” method, providing all the needed dependencies for the main application logic to run independently
The Server.default dependency ZLayer provides also default functionality for a HTTP server
In the app logic, the BookRepository instance is taken from the ZLayers, then the MyRoutes object is created and the routes are extracted from it
The most important line is the server creation “Server.serve”, that requires the routes definitions. This line never returns because it contains the server loop.
The routes are defined in a similar way to the previous case, but this time in its own source file:
As mentioned before, in this example, the MyRoutes class needs a BookRepository dependency to define its behaviour.
The solution is very short, modular and easy to understand. The main reason for using this framework could be that your project is written in ZIO.
If you are starting a new project and you are not sure about choosing ZIO or Cats-Effect, the performance of the target HTTP server could be a determining factor. In that case, zio-http could be the best choice, because Ziverge (the company behind ZIO, see: https://www.ziverge.com), had put more time and effort into optimising the performance and the internals of ZIO.
The ultimate choice could be considering the following: for applications where performance is critical and you are comfortable with the ZIO ecosystem, ZIO-http could provide a more optimised path. For applications that prioritise ecosystem maturity, broader library support, and strong type-safety, Http4s remains a solid choice.
For completeness, this is the BookRepository object:
Here, the getBooks method could be an expression that returns a ZIO side effect, for example, of going to a database and fetching the matching book records.
Tapir
Tapir is developed by Softwaremill (https://softwaremill.com/). It is a robust framework that focuses on defining the endpoints and automatically generating API documentation in different formats. It is built on top of ZIO, but has integrations for Cats-Effect and other frameworks.
To be honest, my first experience with Tapir was not so good, in the sense that I needed to implement a very simple API and I used a tutorial, and I got stuck with some type definitions and aliases. I got to the official documentation but it was not only until I asked Chat GPT that I could resolve my issue. As you may see in the next snippet, the minimal example requires more boilerplate code.
In this case, I used a service and then a repo (instead of only the repo). But apart from that, I had to declare an “options” object with some definitions.
Making the long story short, the main 2 lines to define the server are these:
Here, you can see two endpoints (bookEndpoint and booksEndpoint), one returning a single book and the other returning two books (a list). The name of the endpoint is defined with the “.in” method, and the “.out” method defines the type of response of the endpoint. Also, examples are provided for the generation of the Swagger documentation.
After that, the two endpoints are combined in the swaggerEndpoints variable that is publicly exposed and taken in to define the server (see previous piece of code).
I won’t show the books repository for brevity and similarity with previous examples. But in Tapir, you are encouraged to define the “resolvers” (see GraphQL terminology) or the “server logic” in a different definition of the endpoints. Let’s look at the server definition:
Again, it is not the same the type alias (endpoint definition):
Endpoint[Unit, Unit, String, List[Book], Any]
Then the type ServerEndpoint (server logic definition):
ServerEndpoint[Any, zio.Task]
These generic types force you to define the server logic and the endpoints definition (input and output types, error channels and swagger examples) separately. This ensures statically that the endpoints are defined correctly.
Maybe the best advantage of using
Tapir over zio-http (its main competitor), could be that we get a Swagger definition (or Open API) for free. For those who don’t know Swagger or Open API, it is basically a web page where you can find API documentation and examples, and you can test the API endpoints:
In this screenshot you can see the two GET endpoints, one returning a book, and the other returning a list of books. Also, you can see the responses, with examples of the responses (in JSON format), and the schema definition, and also try them out.
Another remarkable feature of the Tapir framework is that client code (code that you use to call your API endpoints) is automatically generated.
If your project needs this kind of support for the development, Tapir could be a robust option and good choice.
Caliban
Caliban is a different kind of framework. It is a framework that you use to build GraphQL APIs, different from REST APIs in many senses, but ultimately used to build APIs. GraphQL is different from REST in many senses, like query language, documentation, error handling, testing, etc. See https://graphql.org/ for more information.
According to its website, “Caliban is a purely functional library for creating GraphQL servers and clients in Scala”. Caliban has some advantages, if we compare them as tools for building APIs:
The client code is generated automatically (code that you use to call your API endpoints)
The documentation is automatically generated in the GraphiQL interface (this is usually the case on GraphQL). See for example: https://cloud.hasura.io/public/graphiql
It has out-of-the-box support for major HTTP server libraries (http4s, Akka HTTP, Pekko HTTP, Play, ZIO HTTP), effect types (Future, ZIO, Cats Effect, Monix), JSON libraries (Circe, Jsoniter, Play Json, ZIO Json)
So Tapir and Caliban could be similar in these features, but personally I find the Caliban API much simpler to understand and use, from a developer perspective.
This is the main program of my sample
API, made in Caliban:
As you can see, all the boilerplate and the complexity of defining a server has been reduced to a single line of code. And this is the BookApi class:
The graphQL method and the RootResolver definition that are imported and used in this file, build the GraphQL API, based on the queries and mutation case classes and instances provided above.
The live method provides the layer with the BookApi instance that will be injected in the main program (see code above).
Also in this case, here is the BookRepository trait and object, with the list of books to return and also the definition of the operation as ZIO side effects (the UIO data type means that the operations cannot fail and don’t require any context):
If you analyse this code and watch its simplicity, together with the client and documentation automatic generation features, and also if you consider its good performance, Caliban is a very good option for many use cases. You define the types for queries and mutations of the GraphQL API using simple Scala code, as case classes and functions and Caliban does all the heavy lifting.
Finally, this is the GraphiQL local web page, where you can test your API and check its documentation:
Conclusion
Caliban is a very good framework if you need to build a new GraphQL API, but if you don’t need a GraphQL API, the more simple cases like Http4s or zio-http will be more adequate. However, if you want to build a new REST API and your team depends on good up-to-date Swagger documentation, then Tapir is your best choice.
Stay tuned for more in the next article about Scala API frameworks!
In the next edition we will consider Sangría and Play Framework, and compare those with the frameworks analysed in this article.
References
Http4s sample code repository: https://github.com/ignacio-hivemind/http4s-demo
zio-http sample code repository: https://github.com/ignacio-hivemind/zio-http-demo
Tapir sample code repository: https://github.com/ignacio-hivemind/tapir-demo
Caliban sample repository: https://github.com/ignacio-hivemind/caliban-demo
“How to use ZLayers in a ZIO 2.0 Application”: https://medium.com/@hivemind_tech/how-to-use-zlayers-in-a-zio-2-0-application-1c1de6531061