Антон Ларичев

Введение
Выбор между GraphQL и REST — один из первых архитектурных вопросов при проектировании нового API. В 2026 году оба подхода живы, активно используются и имеют свои ниши. Проблема в том, что многие команды выбирают «модный» вариант вместо подходящего, и потом расплачиваются за это техдолгом.
В этой статье мы разберём реальные отличия, покажем код на Node.js и поможем принять осознанное решение.
Как работает REST
REST (Representational State Transfer) строится вокруг ресурсов и стандартных HTTP-методов. Каждый ресурс имеет свой URL, а действия над ним выражаются через GET, POST, PUT, PATCH, DELETE.
// Получить пользователя и его посты — два отдельных запроса
GET /users/42
GET /users/42/posts
// Ответ от /users/42
{
"id": 42,
"name": "Иван Петров",
"email": "ivan@example.com",
"role": "admin",
"createdAt": "2024-01-15"
}
Это просто, предсказуемо и отлично кешируется на уровне HTTP. Большинство CDN и прокси понимают REST из коробки.
Как работает GraphQL
GraphQL — это язык запросов, где клиент сам описывает форму нужных данных. Все запросы идут на один эндпоинт, обычно POST /graphql.
// Один запрос — точно нужные поля
query GetUserWithPosts {
user(id: 42) {
name
email
posts(limit: 5) {
title
publishedAt
}
}
}
// Ответ содержит только то, что запросили
{
"data": {
"user": {
"name": "Иван Петров",
"email": "ivan@example.com",
"posts": [
{ "title": "Мой первый пост", "publishedAt": "2025-03-01" }
]
}
}
}
Схема как контракт
Главная сила GraphQL — типизированная схема. Она служит живой документацией и позволяет генерировать типы для фронтенда.
type User {
id: ID!
name: String!
email: String!
posts(limit: Int): [Post!]!
}
type Post {
id: ID!
title: String!
publishedAt: String
author: User!
}
type Query {
user(id: ID!): User
posts: [Post!]!
}
Серверная реализация
Сравним минимальную реализацию на Node.js. REST через Express:
const express = require('express');
const app = express();
// Каждый ресурс — отдельный маршрут
app.get('/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'Не найден' });
res.json(user);
});
app.get('/users/:id/posts', async (req, res) => {
const posts = await db.posts.findByUserId(req.params.id);
res.json(posts);
});
Тот же функционал через Apollo Server (GraphQL):
const { ApolloServer, gql } = require('@apollo/server');
const typeDefs = gql`
type User {
id: ID!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
}
type Query {
user(id: ID!): User
}
`;
const resolvers = {
Query: {
// Резолвер вызывается только если поле запрошено
user: (_, { id }) => db.users.findById(id),
},
User: {
posts: (user) => db.posts.findByUserId(user.id),
},
};
const server = new ApolloServer({ typeDefs, resolvers });
Проблема N+1 в GraphQL
Классическая ловушка — когда запрос списка пользователей с постами порождает N дополнительных запросов к БД. Решается через DataLoader:
const DataLoader = require('dataloader');
// Группируем запросы в один батч
const postsLoader = new DataLoader(async (userIds) => {
const posts = await db.posts.findByUserIds(userIds);
// Возвращаем посты в том же порядке, что userIds
return userIds.map(id => posts.filter(p => p.userId === id));
});
const resolvers = {
User: {
// Теперь 100 пользователей = 1 запрос к БД, а не 100
posts: (user) => postsLoader.load(user.id),
},
};
Когда выбирать REST
REST — правильный выбор в следующих случаях:
- Публичное API — REST легче документировать через OpenAPI/Swagger, клиентам не нужно учить GraphQL
- Файлы и бинарные данные — загрузка файлов через multipart в REST прямолинейна
- Агрессивное кеширование — HTTP-кеш, CDN и браузерное кеширование работают из коробки
- Простые CRUD-операции — если у вас интернет-магазин с типовыми операциями, GraphQL добавит сложности без выгоды
- Команда без опыта с GraphQL — кривая обучения GraphQL реальна
Когда выбирать GraphQL
GraphQL побеждает в таких сценариях:
- Несколько клиентов с разными нуждами — мобильное приложение, веб и SmartTV хотят разный набор полей
- Быстро меняющийся фронтенд — бэкенд не нужно трогать при изменении UI
- Сложные связанные данные — социальные сети, графы знаний, CMS
- Developer Experience в приоритете — GraphQL Playground и типогенерация ускоряют разработку
Частые ошибки
1. GraphQL поверх REST вместо прямого доступа к данным
Оборачивать REST-эндпоинты в GraphQL-резолверы — антипаттерн. Теряются все преимущества обоих подходов, зато добавляется latency и сложность.
2. Игнорирование кеширования в GraphQL
POST-запросы не кешируются браузером. Для GraphQL нужно настраивать кеш на уровне клиента (Apollo Client, urql) и использовать Persisted Queries для CDN-кеширования.
3. Открытые мутации без авторизации на уровне полей
// Плохо: проверяем авторизацию только на уровне резолвера верхнего уровня
const resolvers = {
Mutation: {
updateUser: (_, args, ctx) => {
if (!ctx.user) throw new Error('Не авторизован');
// Но любой авторизованный может менять любого пользователя!
return db.users.update(args.id, args.input);
},
},
};
// Правильно: проверяем владельца ресурса
updateUser: (_, { id, input }, ctx) => {
if (!ctx.user || ctx.user.id !== id) {
throw new Error('Доступ запрещён');
}
return db.users.update(id, input);
},
4. Глубокая вложенность без ограничений
Злоумышленник может отправить запрос с глубиной вложенности 100 уровней и положить сервер. Всегда ограничивайте глубину и сложность запросов через graphql-depth-limit или встроенные механизмы.
5. REST без версионирования
Ломанье обратной совместимости в REST без версии /v2/ — частая причина инцидентов. GraphQL решает это через deprecation полей, но REST требует явного планирования.
Заключение
В 2026 году нет универсального победителя. REST остаётся стандартом для публичных API, простых сервисов и там, где важно HTTP-кеширование. GraphQL выигрывает при сложных данных, множестве клиентов и высоких требованиях к DX.
Прагматичный выбор: начните с REST. Если через месяц работы обнаружите, что пишете десятки эндпоинтов под разные экраны мобильного приложения или тонете в over-fetching — переходите на GraphQL. Не оптимизируйте архитектуру заранее.






Комментарии
0