GraphQL vs REST в Rust: что выбрать для бэкенда в 2025?

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

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

Начать курс

Введение: REST или GraphQL — вечный спор, который мы решим на Rust

Если вы пишете на Rust и выбираете архитектуру для своего API, то наверняка задавались вопросом: что выбрать — классический REST или модный GraphQL? Оба подхода имеют армию фанатов и гору хейтеров. REST обвиняют в избыточности данных и "болтовне" клиента с сервером. GraphQL — в сложности кэширования и "адовых" запросах к базе данных.

Но давайте посмотрим правде в глаза: Rust — это не JavaScript и не Python. Его философия — zero-cost abstractions и максимальная производительность. Значит ли это, что один из подходов лучше подходит для экосистемы Rust? Давайте разбираться на реальных примерах с кодом.

В этой статье мы не просто перечислим плюсы и минусы. Мы напишем два микросервиса на actix-web (REST) и async-graphql, сравним их производительность, сложность разработки и удобство поддержки. Поехали!

Краткий ликбез: REST и GraphQL за 30 секунд

REST (Representational State Transfer) — это архитектурный стиль, где каждый ресурс (пользователь, статья, заказ) имеет свой URL. Вы получаете данные через GET, создаёте через POST, обновляете через PUT/PATCH. Всё просто и предсказуемо. Но часто вы получаете больше данных, чем нужно, или делаете несколько запросов, чтобы собрать связанные сущности.

GraphQL — это язык запросов и среда выполнения. Клиент сам решает, какие поля ему нужны. Один запрос — и вы получаете ровно то, что попросили. Но за эту гибкость приходится платить сложностью реализации на сервере и потенциальными проблемами с производительностью.

В Rust оба подхода реализованы на высшем уровне благодаря системе типов и асинхронности. Но давайте копать глубже.

REST на Rust: классика с actix-web

Начнём с привычного REST. Для примера возьмём простой блог: есть пользователи, статьи и комментарии. В REST нам нужно будет создать несколько эндпоинтов.

use actix_web::{web, App, HttpServer, Responder, HttpResponse};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Article {
    id: i32,
    title: String,
    content: String,
    author_id: i32,
}

async fn get_article(path: web::Path<i32>) -> impl Responder {
    let article_id = path.into_inner();
    // Представим, что мы достаём статью из БД
    let article = Article {
        id: article_id,
        title: "GraphQL vs REST".to_string(),
        content: "Подробное сравнение...".to_string(),
        author_id: 1,
    };
    HttpResponse::Ok().json(article)
}

async fn get_articles_by_author(path: web::Path<i32>) -> impl Responder {
    let author_id = path.into_inner();
    // Список статей автора
    let articles = vec![
        Article { id: 1, title: "Статья 1".to_string(), content: "Текст...".to_string(), author_id },
        Article { id: 2, title: "Статья 2".to_string(), content: "Текст...".to_string(), author_id },
    ];
    HttpResponse::Ok().json(articles)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/articles/{id}", web::get().to(get_article))
            .route("/authors/{id}/articles", web::get().to(get_articles_by_author))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Всё выглядит чисто и понятно. Плюсы REST на Rust:

  • Простота кэширования на уровне HTTP (ETag, Cache-Control).
  • Прозрачная структура — каждый эндпоинт делает одну вещь.
  • Лёгкая интеграция с любыми клиентами (curl, Postman, браузер).

Но есть и минусы:

  • Проблема N+1 запросов. Чтобы получить статью и автора, нужно два запроса. Или создавать отдельный эндпоинт с JOIN.
  • Over-fetching. Клиент получает все поля Article, даже если ему нужен только title.
  • Со временем количество эндпоинтов растёт как снежный ком.

GraphQL на Rust: гибкость с async-graphql

Теперь реализуем ту же логику, но через GraphQL. Для Rust лучшая библиотека — async-graphql. Она отлично интегрируется с actix-web и tokio.

use async_graphql::*;
use async_graphql::http::GraphQLPlaygroundConfig;
use actix_web::{web, App, HttpServer, HttpResponse, middleware};

struct Query;

#[Object]
impl Query {
    async fn article(&self, ctx: &Context<'_>, id: i32) -> Result
{ // Получаем статью из БД Ok(Article { id, title: "GraphQL vs REST".to_string(), content: "Текст...".to_string(), author_id: 1, }) } async fn articles_by_author(&self, ctx: &Context<'_>, author_id: i32) -> Result> { Ok(vec![ Article { id: 1, title: "Статья 1".to_string(), content: "Текст...".to_string(), author_id }, Article { id: 2, title: "Статья 2".to_string(), content: "Текст...".to_string(), author_id }, ]) } } #[derive(SimpleObject)] struct Article { id: i32, title: String, content: String, #[graphql(skip)] author_id: i32, } async fn index() -> HttpResponse { HttpResponse::Ok() .content_type("text/html; charset=utf-8") .body(GraphQLPlaygroundConfig::new("/graphql").title("Blog API").into_html()) } #[actix_web::main] async fn main() -> std::io::Result<()> { let schema = Schema::new(Query, EmptyMutation, EmptySubscription); HttpServer::new(move || { App::new() .service(web::resource("/graphql").route(web::post().to( |schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>, req: actix_web::HttpRequest, payload: actix_web::web::Payload| { async_graphql_actix_web::GraphQL::new(schema.get_ref().clone()) .respond(&req, payload) }, ))) .service(web::resource("/playground").route(web::get().to(index))) }) .bind("127.0.0.1:8080")? .run() .await }

Теперь клиент может запросить только нужные поля:

query {
  article(id: 1) {
    title
    author_id
  }
}

Плюсы GraphQL на Rust:

  • Клиент сам решает, какие данные получать. Никакого over-fetching.
  • Один эндпоинт — вся логика в schema.
  • Встроенная документация через GraphQL Playground.

Минусы:

  • Сложное кэширование. POST-запросы не кэшируются так же легко, как GET.
  • Проблема N

Блоги

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