GraphQL in Scala with Caliban — Part 1: Turn a simple API into GraphQL

Pierre Ricadat
Jan 27 · 5 min read

Caliban is a library I created in September 2019 for writing GraphQL backends in Scala in a typesafe, boilerplate-free and purely functional manner. In this series of blog posts, I will demonstrate how it can make your life easier in various scenarios.

I am not going to cover what GraphQL is or how the library is designed, but you may check my talk at Functional Scala 2019 if you’d like to know more. The only prerequisite is some very rough knowledge of ZIO, which is used by Caliban to model effects.

There will be 3 different parts covering the following topics, from the simplest to the most complex:

Let’s say you have a classic REST API and you would like to expose the same functionality using GraphQL. At this stage, you’re not yet concerned with any kind of server optimization: you only want your client code to be able to request only the necessary fields as well as requesting data from multiple endpoints at once.

Let’s take the following PugService as an example. It’s a basic trait exposing a few features to access or modify a list of pugs and their picture. We can easily imagine each function being called by an endpoint from a REST API.

Now, how do we turn that into GraphQL?

Model your schema with case classes

To expose an API using GraphQL with Caliban, we need to define it using simple case classes where each parameter is equivalent to an endpoint from our REST API.

We need up to 3 case classes:

  • one for for Queries: it will contain our read-only endpoints, those we express using GET requests in REST
  • one for Mutations: it will contain endpoints for modifying data, those we express using POST, PUT and DELETE requests in REST
  • one for Subscriptions: this is not supported by REST, it allows your backend to push events to the client, typically using a WebSocket

For our example, we will create a case class Queries for our 2 GET endpoints findPug and randomPugPicture, and another case class Mutations for the 2 other endpoints addPug and editPugPicture.

This is our API definition: what fields are available, what arguments are needed and what types are returned. As you may have noticed, we use functions A => B to model GraphQL fields that require arguments, and we use case classes to wrap those arguments and give them a name. I used the suffix Args to identify them clearly.

Provide resolvers

We also need a resolver for each case class (queries, mutations, subscriptions) that we need. A resolver defines how to resolve each field, in other words which function to call when a field is requested. It’s very easy: we just need to create a value for each of our case classes, where each parameter is calling a function from our original business logic.

Now we can finally start using Caliban and turn these resolvers into something useful. First, we group our individual resolvers (queries, mutations, subscriptions) into a RootResolver, then we pass it to the graphQL function. This simple call will analyze our data types and values and transform them into a GraphQL API ready to be served.

However, this doesn’t compile. Why?

Custom Schemas for custom types

Caliban knows how to transform all the basic Scala types (String, List, Option, etc.) into GraphQL types. It is also able to automatically transform your own case classes and sealed traits thanks to a library called Magnolia. But if you use any type that doesn’t fit in these 2 categories, you need to tell Caliban how to transform that type into a valid GraphQL type, otherwise it will fail to compile.

This is done by providing an instance of caliban.schema.Schema for each of these unknown types. In our example, we used which is not supported natively, so we need to provide a Schema for it.

The nice thing about Schema is that you can reuse an existing schema that is similar instead of writing one from scratch. You just need to provide a function to transform from your type to the type you’re reusing. When we have a URL, we want to render it as a String, so we can simply start from Schema.stringSchema and use contramap to make it a Schema for URL.

If we use that custom type as an argument, we also need to provide an instance of caliban.schema.ArgBuilder for it, to tell Caliban how to parse an argument of that type. Just like Schema, we can reuse an existing ArgBuilder to make it easier. Here we start from ArgBuilder.string and use flatMap to make it an ArgBuilder for URL.

And voilà! It compiles! This means Caliban was able to convert each of our Scala types into corresponding GraphQL types. We can verify the GraphQL schema generated by calling render on our api object.

Serving requests

Then, how do we execute a request? We first need to turn our API into an interpreter by calling api.interpreter, then we can use the execute function to perform queries. execute returns a GraphQLResponse which can contain data if the request was successful and errors if there was any problem during the parsing, the validation or the execution of that request.

That was it! We are now able to execute requests. Altogether, we did nothing really fancy: we had to create a few case classes to model our API, we added support for our custom type URL and then finally we called Caliban to turn it into a GraphQL interpreter.

Caliban comes with several adapters to serve a given GraphQL interpreter over HTTP using libraries like http4s or Akka HTTP. You can find all the code from this article together with a runnable HTTP server example in this repository.

If you need more detail or have any question, you may consult the Caliban documentation and also join the #caliban channel on Discord where I’m usually active.

For some use cases, this is everything you will ever need. But you might want to go deeper and minimize the calls to your backend. In the next part of this series, I will discuss how to handle nested schemas and how to optimize your queries. Stay tuned!

Pierre Ricadat

Written by

Software Architect, Photographer

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade