How to implement a Rust micro-service using Rocket, GraphQL, PostgreSQL

Keng Lim
4 min readJul 7, 2021
Photo by Daniel Leone on Unsplash

Tl;dr — just show me the code.

Building a robust and scalable graphQL service is challenging. Making a graphQL micro-service vertically scalable requires augmenting a graphQL server with a variety of goodness that includes Data Loader, batched queries, server-side caching, and edge/CDN caching.

The vertical scalability of nodeJS based graphQL servers has been benchmarked here. A comparison of graphQL servers implemented with different programming languages has also been done here. However, as far as I am aware, not a whole lot of published material is available concerning the performance of Rust-based graphQL servers. But before we can even go about measuring the scalability of any Rust-based graphQL servers, the availability of up-to-date articles on implementing Rust-based graphQL micro-service is also lacking.

This article outlines my learning journey towards implementing an example Rust graphQL micro-service using the following main Rust crates; Rocket, async-graphql, and sqlx.

Open Source Software Toolchains/Libraries

Your choice of OSS toolchains/libraries will affect how your implementation is put together. For my purpose, my criteria looks like this:

  • Use an async implementation of graphQL server.

For this reason, even though juniper has been around longer and has gained async support, I have decided to give async-graphql a try.

  • Use SQL queries for database access.

An example graphQL implementation should include query resolvers that retrieve data from a database. To access the database, there are a handful of examples available online that use Diesel — a well-known Rust ORM. However, I wanted to keep the database access part simple and as close to raw SQL queries as I can. For that reason, I have chosen to use sqlx.

  • Use a performant web-server framework

A simple search shows that actix-web would be a good choice. However, the current version of actix-web is incompatible with the current version of sqlx due to dependence on different versions of tokio (an asynchronous runtime in Rust). For that reason, Rocket was chosen as the web framework.

Prior Art

To date, I have found Roman Kudryashov’s article is to be the most comprehensive when it comes to published articles on async-graphql. There is also a pretty awesome walkthrough presented by Cameron Manavian. Both are must-read/watch if you want to learn how to implement graphQL services using async-graphql and actix-web. Both authors implemented their examples using version 3 of actix-web. As mentioned earlier, version 3 of actix-web is incompatible with tokio 1.x.

My Learning Outline

With our choice of using async-graphql + sqlx + tokio 1.x runtime, we have to pivot from the above-mentioned articles and use Rocket instead of actix-web. Other than the example in the async-graphql repository, there are no other examples that I could find at the time of writing that show an implementation based on Rocket + async-graphql + sqlx. After grokking related articles and rummaging through github issues, here is an implementation outline for those of you who are choosing the same combination of tools as I am.

  1. Setup routes

Familiarize yourself with setting up the Rocket framework. Go over the getting started section in the programming guide. It will be well worth your time to skim through the programming guide.

Setup a simple route like below,cargo run, and make sure you can visit your route.

2. Setup graphQL

Now that you have the Rocket web framework going, you will need to create a graphQL schema for your service. With async-graphql, a schema is built from a tuple of <Query, Mutation, Subscription> objects. For simplicity, we will assume there are no mutations and no subscriptions. We will therefore create the schema with a root Query object and default to EmptyMutationand EmptyMutation.

Connect the graphQL schema to the Rocket web framework, pass the schema to the Rocket instance as a managed object.

By doing so, any route invocation in Rocket can access the graphQL schema as a Rocket-managed state.

3. Implement a simple graphQL query

Define your root query object and add a simple resolver. Since async-graphql is a code-first graphQL implementation, the resolver here also defines a query named hello that returns a graphQL type::String. If you have connected the root route (“/”) to the graphQL playground, you should be able to test your query.

4. Setup a database connection pool

An efficient way of providing a database connection to service a graphQL request is to recycle an already open connection to the database from a connection pool. The idiomatic way of providing a connection pool to a route in Rocket is to provide the pool as a Rocket-managed state. But on the other hand, the idiomatic way of maintaining global state in graphQL is to store it as data in the schema’s context. Hence, we will attach the database pool to the graphQL schema’s context.

To use a database pool then is a matter of retrieving the pool from the graphQL schema’s Context.

For convenience, the learning outline described above is implemented in an example graphQL micro-service that can be found on GitHub here. Happy learning!

--

--

Keng Lim

I love building software for the web. Full-stack Developer. Full-time dad, husband, and follower of Isa al-Masih.