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

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

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

Начать курс

GraphQL API с Rust: пошаговый туториал

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

Этот материал будет полезен разработчикам, знакомым с основами Rust и веб-разработки. Мы сосредоточимся на практической реализации, избегая излишней теории.

Настройка проекта и установка зависимостей

Сначала создадим новый проект Rust:

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

Добавим необходимые зависимости в файл Cargo.toml:

[dependencies]
actix-web = "4"
actix-rt = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
juniper = "0.16"
juniper_actix = "2"

Здесь actix-web — это веб-фреймворк, а juniper — библиотека для работы с GraphQL. juniper_actix обеспечивает интеграцию между ними.

Создание базовой схемы GraphQL

Теперь определим простую модель данных и схему. Для примера создадим систему управления пользователями и постами.

Создадим файл src/schema.rs:

use juniper::{GraphQLObject, GraphQLInputObject, RootNode};

#[derive(GraphQLObject)]
#[graphql(description = "Пользователь системы")]
pub struct User {
    pub id: i32,
    pub name: String,
    pub email: String,
}

#[derive(GraphQLInputObject)]
#[graphql(description = "Входные данные для создания пользователя")]
pub struct NewUser {
    pub name: String,
    pub email: String,
}

#[derive(GraphQLObject)]
#[graphql(description = "Пост пользователя")]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub author_id: i32,
}

pub struct QueryRoot;

#[juniper::object]
impl QueryRoot {
    fn users() -> Vec<User> {
        vec![
            User { id: 1, name: "Алексей".into(), email: "alex@example.com".into() },
            User { id: 2, name: "Мария".into(), email: "maria@example.com".into() },
        ]
    }

    fn posts() -> Vec<Post> {
        vec![
            Post { id: 1, title: "Первая запись".into(), body: "Текст поста".into(), author_id: 1 },
        ]
    }
}

pub struct MutationRoot;

#[juniper::object]
impl MutationRoot {
    fn create_user(new_user: NewUser) -> User {
        User {
            id: 3,
            name: new_user.name,
            email: new_user.email,
        }
    }
}

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

Мы определили два корневых объекта: QueryRoot для запросов (получение данных) и MutationRoot для мутаций (изменение данных). #[derive(GraphQLObject)] автоматически генерирует GraphQL-типы из структур Rust.

Настройка веб-сервера и интеграция с Actix-web

Теперь создадим основной файл src/main.rs для запуска сервера:

mod schema;

use actix_web::{web, App, HttpServer, HttpResponse, middleware};
use juniper_actix::{graphql_handler, playground_handler};
use std::sync::Arc;

use crate::schema::Schema;

async fn graphql_route(
    req: actix_web::HttpRequest,
    payload: actix_web::web::Payload,
    schema: web::Data<Arc<Schema>>,
) -> actix_web::Result<HttpResponse> {
    let context = ();
    graphql_handler(&schema, &context, req, payload).await
}

async fn playground_route() -> actix_web::Result<HttpResponse> {
    playground_handler("/graphql", None).await
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let schema = Arc::new(Schema::new(
        schema::QueryRoot {},
        schema::MutationRoot {},
    ));

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

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(schema.clone()))
            .route("/graphql", web::post().to(graphql_route))
            .route("/playground", web::get().to(playground_route))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Мы настроили два эндпоинта:

  • /graphql — основной эндпоинт для выполнения GraphQL-запросов (POST)
  • /playground — встроенный GraphQL Playground для тестирования (GET)

Обратите внимание на использование Arc для безопасного разделения схемы между потоками.

Тестирование API с помощью GraphQL Playground

Запустите сервер командой cargo run и откройте браузер по адресу http://127.0.0.1:8080/playground. Вы увидите интерфейс GraphQL Playground, где можно выполнять запросы.

Пример запроса (Query):

{
  users {
    id
    name
    email
  }
  posts {
    title
    body
  }
}

Пример мутации (Mutation):

mutation {
  createUser(newUser: {name: "Иван", email: "ivan@example.com"}) {
    id
    name
    email
  }
}

Playground автоматически предоставляет автодополнение и документацию по схеме, что значительно упрощает разработку.

Добавление контекста и базы данных

В реальном проекте данные хранятся в базе данных. Покажем, как передать контекст (например, пул соединений) в обработчики GraphQL.

Модифицируем main.rs:

use std::collections::HashMap;

pub struct Context {
    pub db: HashMap<i32, User>,
}

impl juniper::Context for Context {}

// В функции main:
let mut db = HashMap::new();
db.insert(1, User { id: 1, name: "Алексей".into(), email: "alex@example.com".into() });
let context = Context { db };
let schema = Arc::new(Schema::new                

Блоги

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