What is Juniper?

Juniper is a GraphQL server library for Rust that allows you to create flexible and efficient APIs. It provides a type-safe way to define your GraphQL schema and integrates seamlessly with Rust's type system. This tutorial will walk you through the process of setting up a basic GraphQL server using Juniper, including defining your schema, creating resolvers, and handling queries.

Getting Started

Prerequisites

  • Rust installed on your machine. You can install Rust by following the instructions at rustup.rs.
  • Basic understanding of Rust programming language and GraphQL concepts.

Setting Up the Project

First, create a new Rust project using Cargo:

cargo new rust_graphql_example
cd rust_graphql_example

Next, add the required dependencies to your Cargo.toml file:

[dependencies]
juniper = "0.15.0"
juniper_rocket = "0.6.0"
rocket = "0.4.10"

Defining the GraphQL Schema

In GraphQL, the schema defines the structure of the API. Create a new file called schema.rs in the src directory. Here, we will define a simple schema with a Query type.

// src/schema.rs
use juniper::{graphql_object, FieldResult};

pub struct Query;

#[graphql_object]
impl Query {
    fn hello() -> FieldResult<String> {
        Ok("Hello, GraphQL!".to_string())
    }
}

Setting Up the Server

Next, we will set up a Rocket server to handle incoming GraphQL requests. Create a new file called main.rs and configure the server.

// src/main.rs
#[macro_use]
extern crate rocket;

use juniper::{EmptyMutation, RootNode};
use juniper_rocket::{GraphQLRequest, GraphQLResponse};
use rocket::{routes, State};
use std::sync::Arc;

mod schema;

type Schema = RootNode<'static, schema::Query, EmptyMutation<()>>;

#[post("/graphql", data = "<request>")]
fn graphql_handler(schema: &State<Arc<Schema>>, request: GraphQLRequest) -> GraphQLResponse {
    let response = schema.execute(request.into_inner()).await.unwrap();
    GraphQLResponse::from(response)
}

#[launch]
fn rocket() -> _ {
    let schema = Arc::new(Schema::new(schema::Query {}, EmptyMutation::new()));
    rocket::build()
        .manage(schema)
        .mount("/", routes![graphql_handler])
}

Running the Server

Now that we have set up the server, you can run the application:

cargo run

Once the server is running, you can access the GraphQL API at http://localhost:8000/graphql.

Making Queries

You can test your GraphQL API using a tool like Postman or GraphiQL. Here’s an example query you can use:

{
  hello
}

You should receive the following response:

{
  "data": {
    "hello": "Hello, GraphQL!"
  }
}

Adding More Functionality

To make the API more useful, you can expand the schema by adding more types and resolvers. For example, let’s add a User type and a query to fetch users.

First, update the schema.rs file:

// src/schema.rs
use juniper::{graphql_object, FieldResult};

pub struct User {
    id: i32,
    name: String,
}

#[graphql_object]
impl User {
    fn id(&self) -> i32 {
        self.id
    }

    fn name(&self) -> &str {
        &self.name
    }
}

pub struct Query;

#[graphql_object]
impl Query {
    fn user(&self, id: i32) -> FieldResult<User> {
        Ok(User {
            id,
            name: format!("User {}", id),
        })
    }
}

Now you can query for a user by ID:

{
  user(id: 1) {
    id
    name
  }
}

The expected response would be:

{
  "data": {
    "user": {
      "id": 1,
      "name": "User 1"
    }
  }
}

Error Handling

In production applications, error handling is crucial. Juniper allows you to return errors in a structured way. You can modify your resolvers to return custom error messages. For instance:

#[graphql_object]
impl Query {
    fn user(&self, id: i32) -> FieldResult<User> {
        if id <= 0 {
            return Err("Invalid user ID".into());
        }
        Ok(User {
            id,
            name: format!("User {}", id),
        })
    }
}

Conclusion

In this tutorial, you learned how to build a simple GraphQL API using the Juniper library in Rust. You set up a server, defined a schema, created resolvers, and handled queries. This foundational knowledge will enable you to expand your API with additional features and functionality.

Learn more with useful resources