GraphQL API с Rust: пошаговый туториал на практике
GraphQL — это мощный язык запросов для API, который позволяет клиентам запрашивать только те данные, которые им нужны. В сочетании с производительностью и безопасностью Rust вы получаете идеальное решение для высоконагруженных веб-сервисов. В этом туториале мы создадим полноценное GraphQL API с нуля, используя крейт async-graphql.
Почему Rust? Он обеспечивает нулевую стоимость абстракций, безопасность памяти и потрясающую скорость. GraphQL на Rust работает быстрее, чем аналоги на JavaScript или Python, что критично для production-систем.
1. Настройка проекта и зависимости
Начнём с создания нового проекта Rust. Откройте терминал и выполните:
cargo new graphql_rust_api
cd graphql_rust_api
Теперь добавим необходимые зависимости в файл Cargo.toml:
[dependencies]
async-graphql = "7.0"
async-graphql-actix-web = "7.0"
actix-web = "4"
actix-cors = "0.7"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
Эти крейты предоставляют всё необходимое: async-graphql — ядро GraphQL, actix-web — веб-сервер, serde — сериализацию данных.
2. Создание модели данных и схемы
Определим простую модель для нашего API — книги. Создадим структуры и схему GraphQL:
use async_graphql::*;
use serde::{Serialize, Deserialize};
#[derive(Clone, Debug, Serialize, Deserialize, SimpleObject)]
struct Book {
id: ID,
title: String,
author: String,
year: i32,
}
#[derive(Clone, Debug, Serialize, Deserialize, InputObject)]
struct BookInput {
title: String,
author: String,
year: i32,
}
Обратите внимание на атрибуты #[derive(SimpleObject)] и #[derive(InputObject)]. Первый автоматически генерирует GraphQL-тип для вывода, второй — для ввода данных.
3. Реализация резолверов (Query и Mutation)
Резолверы — это функции, которые обрабатывают запросы. Создадим структуру хранилища и резолверы:
use std::sync::Mutex;
struct QueryRoot;
#[Object]
impl QueryRoot {
async fn books(&self, ctx: &Context<'_>) -> Vec<Book> {
let storage = ctx.data_unchecked::<Mutex<Vec<Book>>>();
storage.lock().unwrap().clone()
}
async fn book(&self, ctx: &Context<'_>, id: ID) -> Option<Book> {
let storage = ctx.data_unchecked::<Mutex<Vec<Book>>>();
storage.lock().unwrap().iter().find(|b| b.id == id).cloned()
}
}
struct MutationRoot;
#[Object]
impl MutationRoot {
async fn add_book(&self, ctx: &Context<'_>, input: BookInput) -> Book {
let mut storage = ctx.data_unchecked::<Mutex<Vec<Book>>>()
.lock().unwrap();
let book = Book {
id: ID::from(storage.len() + 1),
title: input.title,
author: input.author,
year: input.year,
};
storage.push(book.clone());
book
}
}
Мы используем Mutex для потокобезопасного доступа к данным. В реальном проекте вы бы подключили базу данных, но для туториала это идеальный вариант.
4. Сборка и запуск сервера
Финальный шаг — объединить всё в работающий сервер. Создадим точку входа:
use actix_web::{guard, web, App, HttpServer, HttpRequest, HttpResponse};
use actix_cors::Cors;
use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
async fn index(
schema: web::Data<Schema<QueryRoot, MutationRoot, EmptySubscription>>,
req: GraphQLRequest,
) -> GraphQLResponse {
schema.execute(req.into_inner()).await.into()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let storage = Mutex::new(vec![
Book {
id: ID::from(1),
title: "Rust Programming".to_string(),
author: "Steve Klabnik".to_string(),
year: 2023,
},
]);
let schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription)
.data(storage)
.finish();
HttpServer::new(move || {
let cors = Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header();
App::new()
.wrap(cors)
.data(schema.clone())
.service(web::resource("/graphql")
.route(web::post().to(index))
.route(web::get().to(index)))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Запустите сервер командой cargo run и откройте браузер. GraphQL Playground будет доступен по адресу http://127.0.0.1:8080/graphql.
5. Тестирование API
Вот несколько примеров запросов, которые вы можете выполнить в Playground:
Запрос всех книг:
query {
books {
id
title
author
year
}
}
Добавление новой книги:
mutation {
addBook(input: { title: "Learn Rust", author: "John Doe", year: 2024 }) {
id
title
}
}
Запрос конкретной книги:
query {
book(id: "1") {
title
author
}
}
Заключение
Мы создали полноценное GraphQL API на Rust за несколько шагов. Этот туториал демонстрирует ключевые преимущества Rust: безопасность типов на уровне компиляции, отличную производительность и простоту интеграции с современными веб-фреймворками.
Что дальше? Вы можете расширить API, добавив:
- Подключение PostgreSQL через
sqlxилиdiesel - Аутентификацию и авторизацию через JWT
- Подписки (Subscriptions) для real-time обновлений
- Пагинацию и фильтрацию запросов
Rust с async-graphql