How to create a GraphQL server in Rust
Tags: graphql, rust, tutorial, webdev
Published: 2020-06-01 20:40:28

---
title: How to create a GraphQL server in Rust
published: true
date: 2020-05-14 18:05:43 UTC
tags: graphql,rust,tutorial,webdev
canonical_url: https://blog.logrocket.com/how-to-create-a-graphql-server-in-rust/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/t47xrj4vwhi6ny509elo.png
---
**Written by [Joshua Cooper](https://blog.logrocket.com/author/joshuacooper/)**✏️

As the ecosystem around Rust grows, it’s becoming a more appealing option for backend web services. In this guide, we’ll show you how to get started with GraphQL in Rust using the Juniper library for queries and mutations that persist to Postgres.

For reference, the full source code for the final application is available on [GitHub](https://github.com/joshua-cooper/rust-graphql-intro).

## Project setup

Start by creating a new project with Cargo and adding the needed dependencies to `Cargo.toml`.

```jsx
cargo new graphql_intro
cd graphql_intro
# Cargo.toml
[dependencies]
warp = "0.2"
tokio = { version = "0.2", features = ["macros"] }
serde_json = "1.0"
futures = { version = "0.3.1", features = ["compat"] }
futures-macro = "=0.3.1"
juniper = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] }
tokio-postgres = { version = "0.5", features = ["with-uuid-0_8"] }
uuid = { version = "0.8", features = ["v4"] }
```

We’ll use Juniper for the GraphQL-specific functionality, warp for the web server, and `tokio-postgres` to access a database. Since we’ll be using async Rust, an executor is needed to poll Futures. In this example, we’ll use the executor provided by Tokio with the macros feature flag for an async main function.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Setting up the web server

Before we get started with GraphQL, we should set up a web server to handle the HTTP requests. One of the great things about Juniper is that it works with any web server with very little effort. If you want to use something other than warp, feel free adapt this example accordingly.

Only two routes are needed in the web server: one to handle GraphQL requests and one to serve the GraphiQL test client.

```jsx
// main.rs
use warp::Filter;
use std::sync::Arc;
use juniper::http::graphiql::graphiql_source;

#[tokio::main]
async fn main () {
    let schema = Arc::new(Schema::new(QueryRoot, MutationRoot));
    // Create a warp filter for the schema
    let schema = warp::any().map(move || Arc::clone(&schema));

    let ctx = Arc::new(Context { client });
    // Create a warp filter for the context
    let ctx = warp::any().map(move || Arc::clone(&ctx));

    let graphql_route = warp::post()
        .and(warp::path!("graphql"))
        .and(schema.clone())
        .and(ctx.clone())
        .and(warp::body::json())
        .and_then(graphql);

    let graphiql_route = warp::get()
        .and(warp::path!("graphiql"))
        .map(|| warp::reply::html(graphiql_source("graphql")));

    let routes = graphql_route.or(graphiql_route);

    warp::serve(routes).run(([127, 0, 0, 1], 8000)).await;
}
```

This won’t compile yet; we still need to define the `Schema`, `QueryRoot`, `MutationRoot`, and `Context` types.

First, we defined some schema and made it into a warp filter so we can access it in our route handlers. We then did the same thing for a context, which can contain things like database connections.

Next, we created the `graphql_route` variable, which is a warp filter that will match any POST request to the path “graphql,” then make the schema, context and JSON body available to a handler called `graphql`, which we’ll define later.

Similarly, the `graphiql_route` variable is a filter that will match any GET request to the path “graphiql” and respond with the HTML for a GraphiQL client.

Finally, those filters are combined and the server is started.

To get this code to compile, let’s define those types that we missed.

```jsx
use juniper::RootNode;
use tokio_postgres::Client;

struct QueryRoot;
struct MutationRoot;

#[juniper::graphql_object(Context = Context)]
impl QueryRoot {}

#[juniper::graphql_object(Context = Context)]
impl MutationRoot {}

type Schema = RootNode<'static, QueryRoot, MutationRoot>;

struct Context {
    client: Client,
}

impl juniper::Context for Context {}
```

Next, define the `graphql` route handler.

```jsx
use std::convert::Infallible;
use juniper::http::GraphQLRequest;

async fn graphql(
    schema: Arc<Schema>,
    ctx: Arc<Context>,
    req: GraphQLRequest,
) -> Result<impl warp::Reply, Infallible> {
    let res = req.execute_async(&schema, &ctx).await;
    let json = serde_json::to_string(&res).expect("Invalid JSON response");
    Ok(json)
}
```

Create a connection to a Postgres database.

```jsx
use tokio_postgres::NoTls;

#[tokio::main]
async fn main() {
    let (client, connection) = tokio_postgres::connect("host=localhost user=postgres", NoTls)
        .await
        .unwrap();

    // The connection object performs the actual communication with the database,
    // so spawn it off to run on its own.
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });

    // ...
}
```

Note that the client we created here is what’s used in `ctx`. From now on, you’ll need to have a running Postgres instance to connect to it. If you want to use Docker, the following command will spin up a database container for you.

```jsx
docker run --rm -it -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:alpine
```

If you’re using different credentials for your database, be sure to put them in `tokio_postgres::connect()`. At this point, you can compile and run the server, then open [http://localhost:8000/graphiql](http://localhost:8000/graphiql) in your browser to see if it’s working.

## Database schema

The last thing to do before moving on to GraphQL resolvers is to define a database schema. For this example, we’ll make a customer API that allows us to query customer data and create new customer entries.

Let’s create a table after the connection is established.

```jsx
client
    .execute(
        "CREATE TABLE IF NOT EXISTS customers(
            id UUID PRIMARY KEY,
            name TEXT NOT NULL,
            age INT NOT NULL,
            email TEXT UNIQUE NOT NULL,
            address TEXT NOT NULL
        )",
        &[],
    )
    .await
    .expect("Could not create table");
```

The first argument in the execute method is just normal SQL and the second is an array of paramaters. In this case, we aren’t using any paramaters, but we will use them later.

## GraphQL

Now that we’ve set up the server boilerplate and database, we can start implementing GraphQL resolvers to make it function.

Let’s create a struct for customer data.

```jsx
#[derive(juniper::GraphQLObject)]
struct Customer {
    id: String,
    name: String,
    age: i32,
    email: String,
    address: String,
}
```

Notice the derive macro above the struct. This is all we need to do to make our custom data type work seamlessly with Juniper. Since our database is empty at the moment, we’ll start with GraphQL mutations to add customer data.

### GraphQL mutations

Remember `MutationRoot` from earlier? It’s the struct where all GraphQL mutations are implemented.

The simplest mutation we can make is to create a record for a new customer. Here’s a dummy `register_customer` method inside the `impl` block we created before.

```jsx
#[juniper::graphql_object(Context = Context)]
impl MutationRoot {
    async fn register_customer(
        ctx: &Context,
        name: String,
        age: i32,
        email: String,
        address: String,
    ) -> juniper::FieldResult<Customer> {
        Ok(Customer {
            id: "1".into(),
            name,
            age,
            email,
            address,
        })
    }
}
```

Again, we make use of Rust macros to remove lots of boilerplate. In this case, we’re making the context, which contains our database connection, available to all mutations.

For now the `register_customer` method immediately returns a user with an ID of `1` and the name, age, email, and address that are passed in. The user is wrapped in `Ok()` because this method returns a `Result`.

Now you can run the server and open a browser at [http://localhost:8000/graphiql](http://localhost:8000/graphiql) to test this dummy mutation.

```jsx
mutation {
  registerCustomer(name: "John Smith", age: 29, email: "john@example.com", address: "19 Small Street London") {
    id,
    name,
    age,
  }
}
```

As you can see, our idiomatic Rust snake\_cased method name has been changed to idiomatic GraphQL camelCase automatically and all the GraphQL types have been derived from our `Customer` struct. If you try to remove some of the arguments to `registerCustomer`, you’ll get a nice error message. Rust doesn’t have a null type, so we would have to explicitly opt into having a nullable field with Rust’s `Option` type.

Instead of just returning the data, we can store it in our database.

```jsx
async fn register_customer(
    ctx: &Context,
    name: String,
    age: i32,
    email: String,
    address: String,
) -> juniper::FieldResult<Customer> {
    let id = uuid::Uuid::new_v4();
    let email = email.to_lowercase();
    ctx.client
        .execute(
            "INSERT INTO customers (id, name, age, email, address) VALUES ($1, $2, $3, $4, $5)",
            &[&id, &name, &age, &email, &address],
        )
        .await?;
    Ok(Customer {
        id: id.to_string(),
        name,
        age,
        email,
        address,
    })
}
```

Now we’re generating a random UUID then save the user in the database. We’re also normalizing the provided email by converting it to lowercase. You can see how paramaterized SQL looks with `tokio_postgres` here, where the second argument to `execute` is a list of references to the data we want to use.

Unlike the dummy example, this can produce an error. Fortunately, Juniper will format the response accordingly. Anything that implements `std::fmt::Display` can be formatted as an error, so it’s easy to use custom error messages. But for now, we’ll just use the ones provided by `tokio_postgres`.

Other mutations will be similar to this one. Let’s examine how we’d update a customer’s email and delete a customer record.

```jsx
async fn update_customer_email(
    ctx: &Context,
    id: String,
    email: String,
) -> juniper::FieldResult<String> {
    let uuid = uuid::Uuid::parse_str(&id)?;
    let email = email.to_lowercase();
    let n = ctx
        .client
        .execute(
            "UPDATE customers SET email = $1 WHERE id = $2",
            &[&email, &uuid],
        )
        .await?;
    if n == 0 {
        return Err("User does not exist".into());
    }
    Ok(email)
}

async fn delete_customer(ctx: &Context, id: String) -> juniper::FieldResult<bool> {
    let uuid = uuid::Uuid::parse_str(&id)?;
    let n = ctx
        .client
        .execute("DELETE FROM customers WHERE id = $1", &[&uuid])
        .await?;
    if n == 0 {
        return Err("User does not exist".into());
    }
    Ok(true)
}
```

This time, we’re checking to see how many rows are updated. If no rows are updated, it means the user didn’t exist and we return an error.

### GraphQL queries

Queries are implemented in a similar way to mutations, but using `QueryRoot` instead of `MutationRoot`. Our first query will simply find a customer with a given ID.

```jsx
#[juniper::graphql_object(Context = Context)]
impl QueryRoot {
    async fn customer(ctx: &Context, id: String) -> juniper::FieldResult<Customer> {
        let uuid = uuid::Uuid::parse_str(&id)?;
        let row = ctx
            .client
            .query_one(
                "SELECT name, age, email, address FROM customers WHERE id = $1",
                &[&uuid],
            )
            .await?;
        let customer = Customer {
            id,
            name: row.try_get(0)?,
            age: row.try_get(1)?,
            email: row.try_get(2)?,
            address: row.try_get(3)?,
        };
        Ok(customer)
    }
}
```

Instead of using `execute` on the database client, we use `query_one`, which will return exactly one database row or an error. We’d do a similar thing to query a list of customers.

```jsx
async fn customers(ctx: &Context) -> juniper::FieldResult<Vec<Customer>> {
    let rows = ctx
        .client
        .query("SELECT id, name, age, email, address FROM customers", &[])
        .await?;
    let mut customers = Vec::new();
    for row in rows {
        let id: uuid::Uuid = row.try_get(0)?;
        let customer = Customer {
            id: id.to_string(),
            name: row.try_get(1)?,
            age: row.try_get(2)?,
            email: row.try_get(3)?,
            address: row.try_get(4)?,
        };
        customers.push(customer);
    }
    Ok(customers)
}
```

This time, we’re using `query` because we expect more than one row from the database. After that, loop through each row and add each customer to a vector. Try this query in the GraphiQL test client; it should return a list of customers.

```jsx
{
  customers {
    id
    name
    email
    address
  }
}
```

## Conclusion

Now you should have a good grasp on how GraphQL in Rust works with Juniper. Here we just used a single data type, but adding more works in exactly the same way. You can even use fields with nested custom data types and, due to the macros provided by Juniper, everything will just work. The next step could be to add authentication and permissions to the API and use a custom error type for all possible failure conditions.

Rust is a great option for building reliable and performant web backends in general, and its powerful macro support makes working with GraphQL an absolute pleasure.

* * * 
## Monitor failed and slow GraphQL requests in production 

While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you're interested in ensuring network requests to the backend or third party are successful, [try LogRocket.](https://www2.logrocket.com/signup-lr) 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qzrswjwjl533u9pm2spt.png)

[LogRocket](https://www2.logrocket.com/signup-lr) is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs. 

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. [Start monitoring for free.](https://www2.logrocket.com/signup-lr) 
* * *

The post [How to create a GraphQL server in Rust](https://blog.logrocket.com/how-to-create-a-graphql-server-in-rust/) appeared first on [LogRocket Blog](https://blog.logrocket.com).