Introduction to GraphQL API in Rust
Modern web development requires high performance and flexibility when working with data. Rust is a systems programming language known for its speed and memory safety, while GraphQL is a powerful query language that allows clients to receive only the necessary data. Combining these technologies yields incredibly fast and convenient APIs.
In this tutorial, we will create a full-fledged GraphQL API in Rust using the popular crates Juniper (for implementing GraphQL) and Actix-web (for the HTTP server). You will learn how to set up a project, define a schema, implement resolvers, and handle requests.
This article is intended for developers already familiar with the basics of Rust and the fundamental concepts of web APIs. We will use the Rust 2021 edition and the latest stable versions of dependencies.
Step 1: Project Setup and Dependencies
Let's start by creating a new Rust project. Open a terminal and execute:
cargo new graphql-rust-apicd graphql-rust-apiThen, open the Cargo.toml file and add the following dependencies:
[dependencies]actix-web = "4"actix-rt = "2"juniper = "0.16"serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }- actix-web — an asynchronous web framework for Rust.
- juniper — a library for implementing GraphQL in Rust.
- serde — for data serialization/deserialization.
- tokio — an asynchronous runtime required for Actix.
After updating the dependencies, run cargo build to ensure everything installs correctly.
Step 2: Defining the Data Model and GraphQL Schema
For this example, let's create a simple "Book" model with fields: id, title, author, and publication year. Create the file src/models.rs:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]pub struct Book { pub id: i32, pub title: String, pub author: String, pub year: i32,}Now, let's define the GraphQL schema. Create src/graphql.rs:
use juniper::{FieldResult, GraphQLObject, GraphQLInputObject, RootNode};use crate::models::Book;
// GraphQL type for a book (output)#[derive(GraphQLObject)]#[graphql(description = "A book")]pub struct BookGQL { pub id: i32, pub title: String, pub author: String, pub year: i32,}
// Input type for creating a book#[derive(GraphQLInputObject)]#[graphql(description = "Input data for a new book")]pub struct NewBook { pub title: String, pub author: String, pub year: i32,}
// In-memory "database"use std::sync::Mutex;lazy_static::lazy_static! { static ref BOOKS: Mutex<Vec<Book>> = Mutex::new(vec![ Book { id: 1, title: "Rust in Action".to_string(), author: "Tim McNamara".to_string(), year: 2021 }, Book { id: 2, title: "Programming Rust".to_string(), author: "Jim Blandy".to_string(), year: 2020 }, ]);}
// Root Querypub struct QueryRoot;
#[juniper::object]impl QueryRoot { fn books() -> FieldResult<Vec<BookGQL>> { let books = BOOKS.lock().unwrap(); Ok(books.iter().map(|b| BookGQL { id: b.id, title: b.title.clone(), author: b.author.clone(), year: b.year, }).collect()) }
fn book(id: i32) -> FieldResult<BookGQL> { let books = BOOKS.lock().unwrap(); let book = books.iter().find(|b| b.id == id) .ok_or_else(|| juniper::FieldError::new("Book not found", graphql_value!({"code": "NOT_FOUND"})))?; Ok(BookGQL { id: book.id,
```