GraphQL API с Rust: пошаговый туториал по созданию быстрого бэкенда

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в GraphQL API на Rust

Современная веб-разработка требует высокой производительности и гибкости при работе с данными. Rust — язык системного программирования, известный своей скоростью и безопасностью памяти, а GraphQL — мощный язык запросов, позволяющий клиентам получать только необходимые данные. Объединение этих технологий даёт невероятно быстрые и удобные API.

В этом туториале мы создадим полноценный GraphQL API на Rust с использованием популярных крейтов Juniper (для реализации GraphQL) и Actix-web (для HTTP-сервера). Вы узнаете, как настроить проект, определить схему, реализовать резолверы и обработать запросы.

Статья рассчитана на разработчиков, уже знакомых с основами Rust и базовыми концепциями веб-API. Мы будем использовать Rust 2021 edition и последние стабильные версии зависимостей.

Шаг 1: Настройка проекта и зависимости

Начнём с создания нового проекта Rust. Откройте терминал и выполните:

cargo new graphql-rust-api
cd graphql-rust-api

Затем откройте файл Cargo.toml и добавьте следующие зависимости:

[dependencies]
actix-web = "4"
actix-rt = "2"
juniper = "0.16"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
  • actix-web — асинхронный веб-фреймворк для Rust.
  • juniper — библиотека для реализации GraphQL на Rust.
  • serde — для сериализации/десериализации данных.
  • tokio — асинхронный рантайм, необходимый для Actix.

После обновления зависимостей выполните cargo build, чтобы убедиться, что всё устанавливается корректно.

Шаг 2: Определение модели данных и GraphQL-схемы

Для примера создадим простую модель "Книга" с полями: id, название, автор и год издания. Создадим файл 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,
}

Теперь определим GraphQL-схему. Создадим src/graphql.rs:

use juniper::{FieldResult, GraphQLObject, GraphQLInputObject, RootNode};
use crate::models::Book;

// GraphQL-тип для книги (вывод)
#[derive(GraphQLObject)]
#[graphql(description = "Книга")]
pub struct BookGQL {
    pub id: i32,
    pub title: String,
    pub author: String,
    pub year: i32,
}

// Входной тип для создания книги
#[derive(GraphQLInputObject)]
#[graphql(description = "Входные данные для новой книги")]
pub struct NewBook {
    pub title: String,
    pub author: String,
    pub year: i32,
}

// "База данных" в памяти
use std::sync::Mutex;
lazy_static::lazy_static! {
    static ref BOOKS: Mutex<Vec<Book>> = Mutex::new(vec![
        Book { id: 1, title: "Rust в действии".to_string(), author: "Тим Макнамара".to_string(), year: 2021 },
        Book { id: 2, title: "Программирование на Rust".to_string(), author: "Джим Блэнди".to_string(), year: 2020 },
    ]);
}

// Корневой запрос (Query)
pub 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("Книга не найдена", graphql_value!({"code": "NOT_FOUND"})))?;
        Ok(BookGQL {
            id: book.id,
            title: book.title.clone(),
            author: book.author.clone(),
            year: book.year,
        })
    }
}

// Корневая мутация (Mutation)
pub struct MutationRoot;

#[juniper::object]
impl MutationRoot {
    fn create_book(new_book: NewBook) -> FieldResult<BookGQL> {
        let mut books = BOOKS.lock().unwrap();
        let new_id = books.len() as i32 + 1;
        let book = Book {
            id: new_id,
            title: new_book.title,
            author: new_book.author,
            year: new_book.year,
        };
        books.push(book.clone());
        Ok(BookGQL {
            id: book.id,
            title: book.title,
            author: book.author,
            year: book.year,
        })
    }
}

// Создаём корневой узел схемы
pub type Schema = RootNode<'static, QueryRoot, MutationRoot>;

pub fn create_schema() -> Schema {
    Schema::new(QueryRoot {}, MutationRoot {})
}

Обратите внимание: мы используем lazy_static для глобального хранилища. Добавьте lazy_static = "1.4" в Cargo.toml.

Шаг 3: Настройка Actix-web сервера

Теперь свяжем GraphQL-схему с HTTP-сервером. Отредактируем src/main.rs:

mod models;
mod graphql;

use actix_web::{web, App, HttpServer, HttpResponse, middleware};
use juniper::http::graphiql::graphiql_source;
use juniper::http::GraphQLRequest;
use std::sync::Arc;

async fn graphql_handler(
    schema: web::Data<Arc<graphql::Schema>>,
    data: web::Json<GraphQLRequest>,
) -> HttpResponse {
    let response = data.execute(&schema, &()).await;
    HttpResponse::Ok().json(response)
}

async fn graphiql_handler() -> HttpResponse {
    let html = graphiql_source("/graphql", None);
    HttpResponse::Ok()
        .content_type("text/html; charset=utf-8")
        .body(html)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let schema = Arc::new(graphql::create_schema());

    println!("Сервер запущен на http://127.0.0.1:8080");
    println!("GraphQL Playground: http://127.0.0.1:8080/graphiql");

    HttpServer::new(move || {                

Блоги

Рекомендаций по книгам