Juniper — GraphQL библиотека для Rust
Juniper — это мощная и производительная библиотека для создания GraphQL API на языке Rust. Она предоставляет разработчикам удобные макросы и типы для быстрого построения схем, резолверов и обработки запросов. Juniper идеально подходит для проектов, где критичны скорость работы, безопасность типов и низкое потребление памяти — сильные стороны Rust.
Библиотека поддерживает как синхронные, так и асинхронные резолверы (через async-trait), интеграцию с популярными веб-фреймворками (Actix-web, Rocket, Warp, Axum), а также расширенные возможности: подписки (Subscriptions), фрагменты, директивы и кастомные скаляры.
Основные возможности Juniper
- Макросы для быстрой разработки:
#[derive(GraphQLObject)],#[derive(GraphQLInputObject)],#[graphql_interface]автоматически генерируют GraphQL-типы из структур Rust. - Безопасность типов на уровне компиляции: все ошибки несоответствия схемы и типов данных отлавливаются на этапе сборки, а не в рантайме.
- Поддержка асинхронности: встроенная интеграция с
async/awaitчерезjuniper::graphql_objectиasync-graphql. - Гибкие резолверы: возможность использовать контекст, базы данных, внешние API и сложную бизнес-логику.
- Подписки (WebSockets): реализация real-time уведомлений с использованием
futures. - Поддержка GraphQL Playground и GraphiQL: встроенные инструменты для тестирования запросов.
Установка и настройка
Добавьте Juniper в ваш проект через Cargo.toml:
[dependencies]
juniper = "0.16"
# Для асинхронной поддержки:
juniper = { version = "0.16", features = ["async-trait"] }
# Для интеграции с Actix-web:
actix-web = "4"
juniper-actix = "2"
Базовая структура схемы состоит из трех элементов: Query (запросы), Mutation (изменения) и Subscription (подписки). Ниже приведен пример простого API для управления списком книг.
Пример кода: создание простого GraphQL API
use juniper::{graphql_object, FieldResult, GraphQLObject, RootNode};
// Модель данных
#[derive(GraphQLObject)]
struct Book {
id: String,
title: String,
author: String,
year: i32,
}
// Контекст (например, доступ к БД)
struct Context {
pub books: Vec<Book>,
}
impl juniper::Context for Context {}
// Резолверы для Query
struct Query;
#[graphql_object]
#[graphql(context = Context)]
impl Query {
fn books(context: &Context) -> &Vec<Book> {
&context.books
}
fn book_by_id(context: &Context, id: String) -> FieldResult<&Book> {
context.books.iter()
.find(|b| b.id == id)
.ok_or_else(|| juniper::FieldError::new("Book not found", graphql_value!({"code": "NOT_FOUND"})))
}
}
// Резолверы для Mutation
struct Mutation;
#[graphql_object]
#[graphql(context = Context)]
impl Mutation {
fn add_book(context: &mut Context, title: String, author: String, year: i32) -> FieldResult<Book> {
let new_book = Book {
id: uuid::Uuid::new_v4().to_string(),
title,
author,
year,
};
context.books.push(new_book);
Ok(new_book)
}
}
// Создание корневого узла
pub type Schema = RootNode<'static, Query, Mutation>;
// Точка входа для обработки запроса
pub fn create_schema() -> Schema {
Schema::new(Query, Mutation)
}
Для запуска сервера на Actix-web используйте готовый коннектор:
use actix_web::{web, App, HttpServer, middleware};
use juniper_actix::{graphql_handler, playground};
async fn index(
req: actix_web::HttpRequest,
payload: actix_web::web::Payload,
schema: web::Data<Schema>,
) -> actix_web::Result<actix_web::HttpResponse> {
let context = Context { books: vec![] };
graphql_handler(&schema, &context, req, payload).await
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let schema = std::sync::Arc::new(create_schema());
HttpServer::new(move || {
App::new()