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