Using Akka HTTP with Sangria as GraphQL backend

Using Akka HTTP with Sangria as GraphQL backend

Using Akka HTTP with Sangria as GraphQL backend

Really quick introduction to GraphQL

What is GraphQL?
Many people think that GraphQL is ‘something’ related to Graph Databases, in my opinion they’re wrong. GraphQL is to Graph DB like Javascript to Java. Are based on similar concepts, but are used for completely different things.

GraphQL is a query language for API’s. Some people name it successor of REST, I’d rather say it supplementor of REST because both can work together. In this article you’ll find good comparison of both.

In short: GraphQL is a query language for APIs, optimized for performance, designed and open-sourced by Facebook. In GraphQL you can ask server for connected data and you’ll get in response only what you’ve asked for. Not more. If you need more information about this, you’ll find it on GraphQL webpage

Setup

To setup GraphQL backend server we’ll use few libraries. The first is Akka HTTP – one of the most popular HTTP servers in the Scala world nowadays. The library responsible for GraphQL implementation is Sangria. We’ll also use Slick with an in-memory database.

The source code for starting point you can find here. Feel free to fork this repository. master branch has final version of our code, but there are also branches ‘stage#’ representing states between. For easier tracking changes between stages I prepared patch files with differences. You can apply such patch (i.e. git apply stage1-stage2.patch) and your code editor will highlight all changes that were made.

The branch mentioned above has code able to run the basic server. There are all dependencies and plugins configured. I assume you’re at least little familiar with the Scala world and I don’t need to explain where to find such information in the codebase :D.

Let’s look into the code now. The basic Akka HTTP server is defined in Server file.

As you can see, route definition has only two endpoints. Every POST to /graphql endpoint is delegated to GraphQLServer, everything else is managed by graphiql.html file (placed in resources btw).

graphiql.html is a basic HTML console you can use to test your GraphQL backend. Here you can find an example to see how it should looks like. In my code I use slightly changed version of Graphiql provided by Sangria. When you run the server (sbt run) you should be able to use this console now, it’s configured to connect to our server so you can use it for testing.

Domain

In Models you can find entire domain we will be working with. In short: our domain is defined by Categories and Products related one to each other. Between them is a many-to-many relation, so in the file you can find simple case class representing this.

In Models.scala file there are also things I have to explain now. Firstly, I’ve defined type aliases CategoryId and ProductId. The reason for that is to improve the readability the code I’ll explain further.

Sangria library has not-the-best documentation, I would say. Especially when you’re working with relations it’s really easy to mess the types, to avoid this I decided to use type aliases.

There is also Identifiable trait in file. In this case I wanted to highlight support for polymorphic types provided by Sangria. Similar is for Picturemodel with factory method inside Product – I wanted to show you, how to use such kind of structures. I hope the rest of the file should be understandable and there is no need to explain it.

As you can find in application.conf the server uses in-memory database which is populated with data during start. All the things related to DB connection I placed in one file: ShopRepository. There you can find Slick Schema model definitions. There are also accessors for either single records and collections. It gives us 6 functions at this moment.

In the repository there are only two other files worth mentioning: GraphQLServer and SchemaDef. The first one is responsible for handling GraphQL calls and the second one is responsible for interpreting GraphQL queries. Both are described more detailed in the next section.

GraphQL implementation

As we saw previously, all the POST requests targeted to /graphql endpoint server delegates to GraphQLServer file. Let’s look onto this now. The server expects JSON model with the following shape:

It’d shed more light on topic when you’ve already saw the content of the file. GraphQLServer tries to read these three root objects and pass it to the executor. query is the GraphQL query itself, variables is additional data for that query. In GraphQL you can send the query and arguments separately.

You can also set name for the query, it’s what the third object is for. Imagine that query is like a function, usually you’re using anonymous functions, but for logging or other purposes you could add names. It’s send as operationName. More about that you can read here.

The most important call in this file is Executor.execute

If executor responds with success, the result is send back to the client, in other case server will responds with status code 4xx and some kind of explanation what was wrong with the query.

Executor also needs few things from SchemaDef file, so let’s discuss them now.

SchemaDef is the most important file in our example project. It contains our Schema – what we are able to query for. It also interprets how data is fetched and from which data source (i.e. one or more databases, REST call to the other server…). In short our SchemaDef file defines what we want to expose. There are defined types (from GraphQL point of view) and shape of the schema a client is able to query for.

Defining types

The Models file contains classes for our domain, now we have to expose it. But we cannot use the same case classes, we have to define types what are understandable by Sangria. It’s worth pointing out that there doesn’t have to be a 1-1 mapping between our Sangria and DB models. This abstraction allows us to freely hide, add or aggregate fields.

Let’s discuss it on this simple class:

Now we have to define proper ObjectType for this case class. We have to map all the fields like this:

Let’s look onto it line by line:

ObjectType[Unit, Picture] tells us that it’s ObjectType for our Picturecase class. But what is the Unit there? It’s Context type. In our example we’re using Unit because we don’t need it at all, but in complex, production schemas probably you will. Context is an object that flows across the whole execution. Usually it doesn’t change state, but if you need you can store some helpful data there to use in following sub queries.

Often developers put there object responsible for the access to the data source but you can use it in other way. For example (especially if you use middleware) you can put there users permissions’ list for easy access in the next processing step. To be clear: Context lives only during one execution, I mean: from request to response.

Two next lines define name and description for our type. Server also exposes documentation for schema, so don’t be shy and document your types!

Next we’re defining fields. Each Field, has name, type and resolver. Typically you can use resolver as function that read the value from provided case class and context.

In our example above we’re mapping width and height exactly like in case class, but for url we’ve added also a description.

Let’s look at the produced schema. Run the project and open the browser at https://localhost:8080, on top-right you have Docs tab, open it now and search for Picture type. You should see something like this:

schema doc

Easy like this. But it could be even easier. Sangria provides some macros that read the case classes and creates proper object types for it. For example, you can define type for Picture like this:

deriveObjectType is included in package sangria.macros.derive so don’t forget to import this. Macros are handy for simple cases, but they can only extract data from case classes, not read your mind. So the rest you will have to add manually. For instance documentation: you have to put all the information manually.

The code above will produce exactly the same object type for picture like we made few lines above.

Let’s look on types defined in SchemaDef once again. There you can find also definition of Identifiable type we’ve used as parent for both: Product and Category.

To define such interface you should use InterfaceType instead of ObjectType and then you can pass it to proper object type. In the definition of ProductType there is also IncludeMethods("picture") what tells the macro which additional functions we want to expose in our schema.

Please look on the last two constants defined in the file. QueryType is one more ObjectType, but it defines our root schema, it defines what we allow to fetch for, actually… it’s used in the Schema at the last line and passed to Executor.

What the QueryType has inside? There are three fields exposed: allProductsproduct and products.

For now we don’t touch categories. allProducts returns list of products ListType(ProductType) and use ShopRepository (provided as context) and its function with the same name (allProducts). Easy like this. ListTypeIntType and similar basic types are already defined by Sangria so you don’t have to do it yourself.

In the example above we’re also expecting arguments. arguments property is always a list of Argument hence you can see :: Nil at the end.Argument("id", IntType) means it expects argument of type Integer and name id, the same argument we are passing to the ctx.product function in the resolver, so we can even extract it as variable instead of keep it inline.

It’s worth of extracting especially, when you want to reuse it in the in other types. You can replace the code above with the following.

That’s all for this step. You can run the server, open the console and play with that. Remember, at this step we have only three fields exposed.

There are example queries you can use:

You can also pass some values as variables, you can use such query in that case:

But then you have to provide values as JSON in the the Query variablesinput, like this:

So far, so good. But what about performance? What if we execute a query like this?

As we remember, resolver for products field fetches data directly from database. In this case it fetches three times same results. To optimize this, Sangria provides Fetchers and DeferredResolvers

Fetchers and Deferred Resolvers

Fetchers and Deferred Resolvers are mechanisms for batch retrieval of object from their sources like database or external API. Deferred Resolverprovides low-level efficient API but, on the other hand, it’s more complicated in use and less secured.

Fetcher is a specialized version of Deferred Resolver. It provides high-level API, it’s easier to use and usually provides all you need. It optimizes resolution of fetched entities based on its ID or relation, it deduplicates entities and caches the results, to name but a few possibilities.

Let’s implement one for Product:

and change fields in QueryType to use this:

We’re still using repo.products to fetch products from database, but now it’s wrapped in Fetcher. It optimizes the query before call. Firstly it gathers all data it should fetch and then it executes the query. Caching and deduplication mechanisms allow to avoid duplicated queries and give results faster.

Ok, if we have our fetcher defined, we push it to lower level.

Such resolver have to be passed into the Executor to make it available for use. As you can see, we use the same fetcher in two fields, in the first example we’re providing only one ID and expecting one object (defer function) in the second case we’re providing a list of IDs and expecting a sequence of objects (deferSeq). Look at the Sangria docs if you’re interested in other cases.

Since, we’re using ShopRepository.products to fetch single entity or entire list, we don’t need product function anymore.

To extract ID from entities, Resolver uses HasId type class. You have few choices how to provide such class for your model. Firstly you can explicitly pass it.

On the other hand, you can declare implicit constant in the same context so fetcher will take it implicitly. For example you can use generic HasId for every child of Identifiable trait and import it to the context.

The third way is the way I’ve chosen. Providing HasId implicitly inside Model, like this:

BTW you can read more about type classes in another good article on our blog.

Applying the last changes should makes your code similar to branch stage2. In repository there is also code responsible for Category entities, but we will skip it here – it’s pretty similar .

Implementing Many-to-Many relation

Time to implement relation between Product and Category models. One-to-Many is much easier to manage but I chose to make some extra miles.

Our plan is to make us able to execute such queries:

In short, we want to get all categories related to proper product, and in opposite – all products in category. To make this plan done we have to:

  1. Define relation between entities using Relation class,
  2. extend already defined Fetcher to find the entities based on that relation,
  3. add proper fields in exposed schema,
  4. finally we need to implement function which will fetch proper records from database.

Let’s make it happen.

Defining a relation between Product and Category will be the first thing to do, so let’s look how it works in Sangria

Basic Relation costructor’s signature looks like that:

T in this case, is the type of model for which we’re defining relation, RelId is type of Id of related model.

In example where we have categories stored in database as list of ids like:

We are able to define relation in this way:

Probably it’d fit big part of your needs, but not ours… We’ve added few extra miles and used model with many-to-many relation, so it isn’t impossible to do as in the example above. Of course we could define our schema definition and fetch all Category’s ids by Slick, while records are loaded from database. Yes, we could. But it produces additional database operation even if we don’t need such data. But there’s another way.

There is another Relation constructor we can use:

This constructor needs additional object (Tmp) and uses this to retrieve a model and its relations.

In our example, we want to find all categories for product, so T will be Category(type of related model) and RelId will be ProductId(Id of model we’re looking relation for), so we can define our relation like this:

Note: Sangria uses Seq[IdType] in every function related to ID’s, even if function logically should work for single ID only. In some cases it could be misleading.

We used tuple (Seq[ProductId], Category) as temporary object, so it’s easy to retrieve proper data. Relation is defined, but where we’ll get such temporary object from? The most logical answer is: from data source. Yes, in our case database. We have to provide function, that gets product id as argument and return sequence of such tuples. Remember that even for single id, we use Seq[IdType].

So far, so good. We are in the middle of our list now. The next step is Fetcher. As I mentioned previously, one of the features of this type class is support for model ID along with relation ids. Now we have to tell fetcher, there is relation defined and it can be used when needed. We have to refactor our categoriesFetcher which now looks like this:

This basic constructor uses apply function, but to add support for relation we have to use rel or relCaching. The difference is that the second uses caching mechanisms for fetched models. Of course you can define your own mechanism but how to do this is not covered in this article.

relCaching needs two functions, the first is like in previous example: responsible for getting categories by its ID, and the second is responsible to get the categories by their relations. When we want to find categories for product, Product’s ID is extracted by RelationIds and result is passed to repo.categoriesByProducts where data is read from the database. At the end Relation maps and returns result.

We’re almost done, all we need to do is to expose categories function for product field. To define how the Product is exposed we’ve used ProductType class. All the fields are extracted by macro, but also have possibility to add more if we need. For such case is AddFields type class.

What to explain… We just added field with name categories that expects a list of categories in the response. Resolver uses fetcher, explicitly sets a relation and passes product ID as a parameter for that relation.

That’s it. We did all to set up many-to-many relation. When you’ll do the same work for Category and Products relation, your code should be similar to ‘stage3’ branch code

If you run code from the branch you should be able to make queries like that:

And get the response:

But what if we will execute following query?

Implementing Complexity

We can analyze complexity of our query and react somehow when it reaches the limit. To give you an example: Add fixed complexity value for every field and when complexity crosses some threshold, it will respond with an error. I chose to make fixed values, but if you want, you can compute the value somehow, for example based on amount of returned records, etc.

Firstly we’ll define helper function and use it in fields

complexity field needs parameter of type Option[(Ctx, Args, Double) => Double] where Ctx in our example is ShopRepository but if you want, you can keep it generic. Because query is tree-like, we compute complexity down the tree recurrently. Now populate some values along defined fields like:

Now our exposed model looks like:

We also set complexity of value 30 to ProductType and CategoryType. Ok, but server has to react somehow when complexity reaches value of 300, for example. Such operation could be managed by query reducers. We can create our own QueryReducer, that checks value of complexity of query, and in case it’s greater than 300 it will throw an exception. Then we will catch the exception and respond with proper status and message.

Let’s begin from implementing Exception then:

Now query reducer responsible for checking the complexity’s value:

At the and we have to tell the Executor about our handler and reducer. Note that you can define as many reducers as you want. Add to our Executor:

too complex query example

This is what we wanted to achieve. Entire article was about implementing read operations, now I want to show you how to implement really simple write operation.

Simple mutation

So far we’ve used query for getting data. If we want to add or update data we have to use mutation instead.

Assume you want to add Category with name “Foo”, you have to execute such query:

Note: my slick setup doesn’t support autoincrementing of ID, so you have to provide it while adding a record. Of course, this wouldn’t be necessary for a production ready system.

Let’s implement this, starting from database operation. The query above shows, that we have to add a Category, providing id and name. It also expects Category in return. Following function will manage this:

Time to update the schema. Instead adding another field to QueryType we have to implement a new ObjectType responsible for all mutations.

We’re reading arguments id and name and then we pass these to the proper repository function. Easy like this. The last thing is to tell exposed Schemaobject that we also support mutations.

That’s all. Now, such prepared server should supports queries like this:

After that we should see “FOO” in the categories listing.

Recap

In this article I wanted to show how to implement basic GraphQL server in Scala. I deliberately added some extra effort to show a little more complicated model than you can find in official examples. But it’s still only tip of the iceberg what Sangria provides.

Websocket based subscriptions, Middleware, authorization, caching to name but a few. Read the official guide to find out all the things this library offers. I know I didn’t cover all the possibilities and responded for all beginner’s question, but I hope I encouraged you to go deeper in this topic. Leave a comment for all the questions feedback :D

Links

  1. GraphQL
  2. Sangria
  3. Learn Sangria
  4. Akka HTTP
  5. GraphQL vs REST
  6. Repository with code used in this article
  7. Sangria examples with Akka HTTP

Read more

Download e-book:

Scalac Case Study Book

Download now

Authors

Mariusz Nosiński

I’m an experienced developer who has acquired a broad knowledge. I’m always ready for new challenges and learning new skills.

Latest Blogposts

14.03.2024 / By  Dawid Jóźwiak

Implementing cloud VPN solution using AWS, Linux and WireGuard

Implementing cloud VPN solution using AWS, Linux and WireGuard

What is a VPN, and why is it important? A Virtual Private Network, or VPN in short, is a tunnel which handles all the internet data sent and received between Point A (typically an end-user) and Point B (application, server, or another end-user). This is done with security and privacy in mind, because it effectively […]

07.03.2024 / By  Bartosz Puszczyk

Building application with AI: from concept to prototype.

Blogpost About Building an application with the power of AI.

Introduction – Artificial Intelligence in Application Development When a few years ago the technological world was taken over by the blockchain trend, I must admit that I didn’t hop on that train. I couldn’t see the real value that this technology could bring to someone designing application interfaces. However, when the general public got to […]

28.02.2024 / By  Matylda Kamińska

Scalendar March 2024

scalendar march 2024

Event-driven Newsletter In the rapidly evolving world of software development, staying in line with the latest trends, technologies, and community gatherings is crucial for professionals seeking to enhance their skills and network. Scalendar serves as your comprehensive guide to navigate events scheduled worldwide, from specialized Scala conferences in March 2024 to broader gatherings on software […]

software product development

Need a successful project?

Estimate project