Когда команда проектирует архитектуру для нового продукта, первый спор почти всегда сводится к вопросу: REST или GraphQL. Разработчикам предстоит выбрать — остаться на привычных эндпоинтах или перевести клиент-серверное взаимодействие на схему с единственной точкой входа.
Расстановка сил такая: REST по-прежнему самый распространённый стиль API: по данным Postman его используют 86% разработчиков (2023), а в 2025 году — уже 93%. GraphQL тоже активно используется: по данным Hygraph, его применяют 61,7% разработчиков, а Postman фиксирует около 33% команд, которые работают с GraphQL.
В этой статье мы разберем оба подхода на одном сквозном сценарии. Представьте, что мы разрабатываем экран карточки товара в интернет-магазине. Для его отрисовки фронтенду нужны базовая информация о товаре, актуальные остатки на складах, отзывы с именами авторов и блок рекомендаций.
ВАМ ПРИШЛО ПРИГЛАШЕНИЕ 💌
Приходите к нам в соцсети поделиться своим мнением и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте — шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!
Как устроен REST
Архитектура REST API строится на ресурсной модели. Каждая сущность (товар, отзыв, склад) — это отдельный ресурс, который живет по своему уникальному адресу (URL). Действие, которое мы хотим совершить над ресурсом, задается HTTP-методами (GET для чтения, POST для создания), а результат операции описывается статус-кодом ответа.
Чтобы собрать наш экран карточки товара (допустим, с ID 42), бэкенд предоставит следующий набор эндпоинтов:
GET /api/v1/products/42— основные данные товара.GET /api/v1/products/42/stock— остатки.GET /api/v1/products/42/reviews— список отзывов.GET /api/v1/products/42/recommendations— рекомендации.
Клиент делает первый запрос к ресурсу товара:
GET /api/v1/products/42 HTTP/1.1
Host: api.shop.com
# Ответ сервера (200 OK):
{
"id": "42",
"name": "Механическая клавиатура",
"price": 12000,
"weight": 800,
"supplier_id": "sup_99"
}
Ключевая идея REST в том, что каждый кусок данных живёт в своём отдельном запросе. Чтобы собрать одну страницу, клиенту приходится ходить по нескольким эндпоинтам и самостоятельно склеивать результат.
Как устроен GraphQL
Подход GraphQL кардинально отличается: он строится вокруг строгой типизации. Разработчики заранее описывают граф данных (схему), в которой указаны все типы и связи между ними. Сервер предоставляет клиентам ровно один адрес (например, POST /graphql). Клиент сам пишет запрос, перечисляя только те поля, которые ему нужны, а специальная функция на сервере (резолвер) собирает эти данные. Сервер возвращает ответ, форма которого в точности повторяет форму запроса.
Сначала на сервере описывается схема на языке SDL (Schema Definition Language):
type Product {
id: ID!
name: String!
price: Int!
stock: Int
reviews: [Review!]
recommendations: [Product!]
}
type Review {
id: ID!
text: String!
authorName: String!
}
type Query {
product(id: ID!): Product
}
Затем клиент отправляет единый запрос, чтобы получить и товар, и отзывы к нему:
# Запрос клиента:
query GetProductScreen {
product(id: "42") {
name
price
reviews {
text
authorName
}
}
}
# Ответ сервера:
{
"data": {
"product": {
"name": "Механическая клавиатура",
"price": 12000,
"reviews": [
{ "text": "Отличный ход клавиш", "authorName": "Иван" }
]
}
}
}
Главное отличие на одном экране
Чтобы понять, чем отличается GraphQL от REST, посмотрим на процесс сборки нашего экрана карточки товара.
| Шаг | REST | GraphQL |
| 1 | Запрос к /products/42 (ждем ответ). | Отправляем один большой запрос к /graphql. |
| 2 | Запросы к /stock, /reviews, /recommendations (можно параллельно). | Сервер собирает все данные по графу связей. |
| 3 | Клиент склеивает 4 JSON-ответа у себя. | Сервер отдает один готовый JSON под нужды экрана. |
При таком сценарии REST неизбежно страдает от двух симптомов:
- Over-fetching (избыточная выборка): эндпоинт
/products/42возвращает вес товара (weight) и ID поставщика (supplier_id), которые нам на экране не нужны, но мы вынуждены тратить на них трафик. - Under-fetching (недостаточная выборка): первого запроса не хватает для отрисовки интерфейса, и клиент вынужден идти за добавкой (отзывами и остатками), тратя время на установку новых сетевых соединений.
Разница хорошо видна в цифрах: в 2026 году при переходе на GraphQL компании фиксируют экономию 20–30% мобильного трафика. Но за гибкость приходится платить вычислительной мощностью: медианная задержка (latency) формирования ответа на бэкенде у REST составляет около 12 мс, тогда как у GraphQL — около 15 мс из-за необходимости парсить текст запроса.
Большая скидка — 16% на все курсы Практикума
Если вы читаете эту статью, тогда вы точно разбираетесь в технологиях. Стать лучше и зарабатывать больше можно после курсов Практикума — по программированию, анализу данных и искусственному интеллекту.
До 30 сентября на все курсы действует скидка 16%, она применится автоматически при оплате. Потом цены станут выше, поэтому не откладывайте!
Если не хватает уверенности в асинхронном Python — берите «Мидл Python-разработчик»; сервис уже вырос из одного репозитория и пора резать его на части, а не переписывать с нуля — «Микросервисная архитектура»; а если зона ответственности давно включает не только код, но и мониторинг с разбором инцидентов — «SRE — обеспечение надёжности систем».
Кэширование
Кэширование API — это территория, где REST выигрывает по умолчанию.
Поскольку в REST каждый ресурс имеет свой уникальный URL (например, GET /products/42), то браузерный кэш, прокси-серверы и сети доставки контента (CDN) могут кэшировать ответы из коробки, опираясь на стандартные HTTP-заголовки (Cache-Control, ETag)..
В GraphQL все запросы летят на один адрес (POST /graphql), а тело запроса каждый раз разное. HTTP-кэш не понимает, что внутри, и пропускает запросы насквозь к бэкенду. Для GraphQL приходится изобретать обходные пути.
| Уровень кэша | Работает в REST | Работает в GraphQL |
| Браузер / HTTP-кэш | Да, нативно (GET-запросы) | Нет (все запросы POST на один URL) |
| CDN (Cloudflare и др.) | Да, из коробки | Только через Persisted Queries (сохраненные запросы с хэшами) |
| Кэш в клиенте (ОЗУ) | Требует ручной логики | Из коробки в Apollo/Relay (кэширование по ID полей) |
| Серверный кэш | На уровне эндпоинта/Redis | Требует настройки слоя кэширования внутри каждого резолвера |
Ошибки и коды ответов
Обработка ошибок API в этих двух подходах кардинально различается, что часто ломает привычный мониторинг инфраструктуры.
В REST код ответа несет смысловую нагрузку. Если товар не найден, сервер вернет:
HTTP/1.1 404 Not Found
{ "error": "Product not found" }
В GraphQL HTTP-статус почти всегда равен 200 OK, даже если внутри произошла катастрофа. В ответе появляется специальный массив “errors”, при этом частично заполненные данные могут соседствовать с упавшими полями: например, товар нашелся, а сервис отзывов недоступен.
{
"data": {
"product": { "name": "Клавиатура", "reviews": null }
},
"errors": [
{ "message": "Failed to fetch reviews", "path": ["product", "reviews"] }
]
}
Как это влияет на работу:
- Системы мониторинга не могут просто смотреть на 5xx ошибки HTTP — они обязаны парсить тела JSON-ответов.
- Сетевые ретраи (повторные попытки) на клиенте нужно настраивать кастомно, так как код всегда будет 200.
- Логирование усложняется: нужно вытаскивать
pathиз массива ошибок, чтобы понять, какой резолвер упал. - В схеме GraphQL рекомендуется создавать специальные типы-юнионы для бизнес-ошибок (например,
ProductResult = Product | NotFoundError), чтобы обрабатывать их как часть данных, а не системных сбоев.
Нагрузка на сервер и проблема N+1
Этот раздел важен потому, что в GraphQL производительность часто зависит не от самого запроса, а от того, как написаны резолверы на сервере. Именно здесь чаще всего появляются скрытые проблемы с нагрузкой.
Разберём типичный сценарий. Допустим, мы запрашиваем список из 100 товаров, и для каждого товара нужны отзывы. В GraphQL это выглядит удобно на клиенте, но на сервере может привести к неочевидному числу обращений к базе.
Наивная реализация работает так: сначала выполняется один запрос за товарами, а затем для каждого товара отдельно запрашиваются его отзывы. В итоге вместо 2 запросов к базе получается 1 + 100 — это и есть классическая проблема N+1 запросов. То есть мы один раз получили список товаров, а потом «дёрнули базу» ещё N раз за связанными данными.
До оптимизации код резолвера выглядит так:
// Вызовется 100 раз!
Review: {
author: async (review) => {
return await db.query(`SELECT * FROM users WHERE id = ${review.author_id}`);
}
}
Решение — использование утилиты DataLoader. Он собирает все запрошенные ID в рамках одного запроса и выполняет их одним обращением к базе:
// Вызовется 1 раз для всех авторов:
Review: {
author: async (review, context) => {
return await context.userLoader.load(review.author_id); // DataLoader батчит запросы
}
}
За счёт этого вместо множества отдельных запросов выполняется один пакетный запрос, а результаты кэшируются на время выполнения текущего HTTP-запроса.
Но даже при использовании DataLoader GraphQL не становится «бесплатным» по производительности. Сам движок добавляет дополнительную нагрузку — парсинг запроса, проверку схемы и сборку вложенного JSON могут давать ощутимый оверхед по сравнению с простыми REST-обработчиками.
Безопасность
Угрозы для безопасности API в разных архитектурах требуют разных методов защиты.
Угрозы GraphQL и защита:
- Тяжелые вложенные запросы: злоумышленник может написать запрос, где товар ссылается на отзывы, отзывы на авторов, авторы на их товары, зациклив граф так, что база данных ляжет. Защита: включение ограничения максимальной глубины запроса (Query Depth Limit) и подсчет «стоимости» запроса (Query Cost Analysis).
- Утечка структуры API: по умолчанию GraphQL-схема открыта для изучения (интроспекция). Защита: полное отключение интроспекции на продакшен-сервере.
- Сложность авторизации: проверять права нужно не на входе в эндпоинт, а внутри каждого отдельного резолвера (проверка прав на уровне полей).
Угрозы REST и защита:
- Перебор (brute-force) и DDoS: злоумышленник бьет по конкретным URL. Защита: классическое ограничение запросов (Rate Limiting) по эндпоинтам.
- Утечка лишних полей: эндпоинт отдает массив DTO (Data Transfer Objects), в котором случайно затесались приватные поля (например, хэши паролей пользователей в отзывах). Защита: жесткое разграничение DTO-моделей.
Эволюция схемы и инструменты
Версионирование API и сопровождение документации в REST и GraphQL организованы по-разному.
Версионирование
В REST стандартный путь — изменение URL (/v1/products меняется на /v2/products). Приходится параллельно поддерживать два контракта. В GraphQL версионирования в классическом виде нет: новые поля просто добавляются в схему, а старые помечаются директивой @deprecated. Бэкенд мониторит статистику запросов и удаляет старое поле только тогда, когда клиенты перестают его запрашивать.
Документация
В мире REST чаще всего используют спецификация OpenAPI (Swagger), по которой генерируется визуальная страница документации. GraphQL самодокументируемый по своей природе. Благодаря встроенной песочнице GraphiQL, фронтендер может сразу изучать типы, читать комментарии к полям и тестировать запросы в одном окне.
Типобезопасность и кодогенерация
Оба подхода позволяют генерировать типизированных клиентов для фронтенда. В REST это делается из YAML-файла OpenAPI. В GraphQL плагины, например, GraphQL Code Generator, читают схему и сами генерируют TypeScript-типы и React-хуки для конкретных запросов клиента.
Тестирование
Для REST пишутся интеграционные и контрактные тесты. В GraphQL-мире в CI-пайплайн обязательно встраивают инструменты (вроде GraphQL Inspector), которые проверяют каждую новую схему на обратную совместимость, чтобы бэкендер случайно не удалил поле, которое ожидает мобильное приложение.
Что ещё есть кроме REST и GraphQL
Выбор не ограничен двумя инструментами. Рассмотрим другие альтернативы REST, которые закрывают свои специфичные ниши:
| Подход | Ниша | Чем платят |
| gRPC | Внутренняя межсервисная связь (backend-to-backend), потоковая передача | Отсутствие человекочитаемого формата (бинарный Protobuf), сложность отладки |
| tRPC | Монорепозитории на TypeScript (фронт и бэк в одной кодовой базе) | Жесткая привязка исключительно к TypeScript/JavaScript-экосистеме |
| Вебхуки / События | Асинхронный обмен данными (реакция на смену статуса заказа) | Сложность отслеживания потерянных сообщений, гарантии доставки |
| Федерация схем (Apollo) | Объединение множества микросервисов с GraphQL в один общий граф | Высокая архитектурная сложность, наличие единой точки отказа (суперграф-роутера) |
Матрица выбора
Чтобы решить, что выбрать для API, нужно оценить архитектурный контекст.
| Сценарий (тип проекта) | Рекомендация | Обоснование |
| Публичный API для внешних разработчиков (B2B) | REST | Понятный стандарт, легко интегрировать с любым языком без сторонних библиотек |
| Мобильное приложение на медленной сети | GraphQL | Нет проблемы under-fetching, все нужные данные доставляются за один сетевой запрос |
| SPA-приложение со сложными экранами (дашборды) | GraphQL | Фронтенд сам решает, какие данные ему нужны для отрисовки конкретного виджета |
| Отдача статичного контента (медиа, статьи) | REST | Эффективно кэшируется на уровне CDN из коробки |
| Внутренняя связь между микросервисами | gRPC / REST | Для server-to-server GraphQL избыточен (парсинг запросов тратит ресурсы CPU) |
| Монорепозиторий стартапа (всё на TS) | tRPC | Идеальная типизация без генерации схем и лишнего бойлерплейта |
| Продукт с десятком разнородных клиентов (Web, iOS, Android, TV) | GraphQL | Бэкенду не нужно писать новые эндпоинты под специфику каждого нового клиентского приложения |
Перед стартом разработки команда должна ответить на 4 вопроса. Кто конечный потребитель API? Насколько критичен размер передаваемого JSON? Нужен ли строгий HTTP-кэш? Готовы ли бэкендеры тратить время на написание DataLoader для каждого связанного поля?
Как совместить оба подхода
Если у вас уже есть работающий проект, полная миграция на GraphQL с переписыванием бэкенда — это огромный риск. Чаще всего команды используют гибридный подход.
Порядок безопасного переезда выглядит так:
- Бэкенд оставляет старые REST-эндпоинты нетронутыми.
- Поверх них поднимается отдельный Node.js или Go сервис — GraphQL шлюз.
- Шлюз принимает GraphQL-запросы от фронтенда, а под капотом сам делает обычные REST-запросы к старому бэкенду.
- Фронтенд переводит на новую схему только один сложный экран (например, корзину). Команда замеряет разницу в скорости и стабильности.
- Постепенно остальные экраны переносятся на новый формат.
[ Фронтенд ] ---> (GraphQL запрос) ---> [ GraphQL Шлюз ] ---> (REST запросы) ---> [ Микросервисы ]
Если GraphQL кажется слишком сложным для внедрения, архитекторы часто используют паттерн BFF (Backend for Frontend). Вместо одного универсального API разработчики создают отдельные бэкенд-сервисы под каждого клиента (один API для веба, отдельный урезанный API для iOS), которые склеивают данные нужным образом, оставаясь в рамках REST-парадигмы.
Чек-лист перед выкаткой GraphQL в прод
Если вы всё же решили, что вам нужен граф, пройдитесь по этим пунктам до релиза:
- Пакетная загрузка (DataLoader) настроена для всех связанных сущностей (решена проблема N+1).
- Ограничение максимальной глубины запроса (Query Depth) включено.
- Ограничение стоимости запроса (Query Complexity Cost) рассчитано и активировано.
- Интроспекция (возможность выкачать схему) отключена для production-окружения.
- Сохраненные запросы (Persisted Queries) настроены для работы с CDN (если требуется кэширование).
- Авторизация и проверка прав доступа настроены на уровне конкретных полей, а не только на уровне корневого контроллера.
- Инструменты мониторинга парсят JSON-ответы и собирают метрики по конкретным GraphQL-операциям.
- Конвенция по формату ошибок внутри массива errors зафиксирована с фронтенд-командой.
- Проверка обратной совместимости схемы встроена в CI-пайплайн.
Частые вопросы
Умер ли REST?
Нет, REST продолжает оставаться доминирующим архитектурным стилем для 80% систем в мире. Он идеально подходит для микросервисного общения, интеграций между корпоративными системами и отдачи простого плоского контента, уступая место графовым решениям лишь в нагруженных клиентских интерфейсах.
Можно ли отдавать файлы через GraphQL?
По спецификации протокол работает только с текстовыми данными (JSON). Для загрузки и отдачи бинарных файлов разработчики используют либо сторонние спецификации (например, graphql-multipart-request-spec), либо выносят работу с медиа на отдельные классические REST-эндпоинты.
Нужен ли GraphQL небольшому проекту?
Если вы делаете лендинг с формой обратной связи или админку на три справочника, внедрение графовой схемы — это избыточное усложнение. Настройка типов, резолверов и оптимизация батчинга займет больше времени, чем написание пары стандартных контроллеров.
Как GraphQL работает с кэшем на CDN?
Для использования Cloudflare или других CDN применяется механизм Persisted Queries. Клиент не отправляет текст запроса, а шлет его заранее вычисленный хэш через обычный HTTP GET-запрос. Сервер и CDN распознают этот хэш и могут кэшировать ответ на уровне сети.
Что быстрее на медленной мобильной сети?
На нестабильных мобильных сетях (3G/Edge) графовый подход выигрывает безоговорочно. Установка каждого нового TCP/TLS соединения занимает сотни миллисекунд, поэтому забрать все нужные данные за один большой запрос всегда быстрее, чем ждать ответа от трех-четырех разных REST-эндпоинтов.
Заключение
Вопрос «REST или GraphQL» не имеет универсального ответа. Архитектура определяется не трендами, а числом, разнородностью ваших клиентов и структурой данных. Если у вас множество независимых экранов, которые требуют сложной агрегации данных, графовый подход сократит время разработки фронтенда и спасет мобильный трафик. Если вы пишите понятный B2B сервис или публичное API для партнеров — строгие эндпоинты будут лучшим выбором. Главный совет: не переписывайте всю систему просто ради моды. Поднимите промежуточный шлюз, переведите на него один реальный экран, сделайте замеры сети и CPU, и только после этого принимайте решение о масштабировании.
Советуем дополнительно почитать
Что такое API — самое базовое объяснение того, что вообще делает API, если раздел про REST и GraphQL кажется слишком специфичным для старта.
Как тестируют API: разбираемся на примере REST API — практическая сторона того же REST API, которая осталась за скобками статьи про архитектурный выбор.
Создаём API на FastAPI — пошаговое построение собственного REST API с нуля, если после статьи хочется не только выбрать подход, но и сразу его попробовать.
Go-разработчик: кто это, чем он занимается и как им стать — про профессию и стек, который статья называет типичным выбором для gRPC и GraphQL-шлюзов.
Микрофронтенд: что это такое и как работает — тот же вопрос декомпозиции системы, который статья разбирает для бэкенда через микросервисы, но на стороне фронтенда.
Бонус для читателей
Если вам интересно погрузиться в мир IT и при этом немного сэкономить, держите наш промокод на курсы Практикума. Он даст вам скидку при оплате, поможет с льготной ипотекой или безлимитом на маркетплейсах. Ладно, окей, это просто скидка, без остального, но хорошая.
