В технологиях наблюдается своеобразный цикл: появляется что-то новое, и вдруг все, кто им не пользуется, чувствуют себя атакованными. GraphQL появился около десяти лет назад, и с тех пор мы наблюдаем этот ажиотаж. «REST мёртв», говорили они. «GraphQL — будущее», провозглашали они. Тем временем REST API незаметно обеспечивали работу 90% интернета и занимались своими делами, не привлекая внимания.

Не поймите меня неправильно — я не говорю, что GraphQL плох. Это действительно полезный инструмент. Но где-то между шумихой и реальностью лежит более скучная, практическая истина: REST всё ещё абсолютно подходит для большинства приложений, и одержимость отрасли GraphQL создала реальные проблемы для реальных команд.

Позвольте мне объяснить почему, опираясь на реальные компромиссы, а не только на ностальгию.

Преимущество REST, о котором никто не говорит: простота

Когда вы создаёте REST API, вы не изобретаете новый язык запросов. Вы не просите свою команду фронтенда изучать проектирование схемы GraphQL. Вы не решаете новые задачи, связанные с кэшированием. Вы делаете что-то, что было испытано десятилетиями.

На практике это означает следующее:

GET /api/users/123
GET /api/users/123/orders
GET /api/posts/456

Ваш фронтенд-разработчик не должен знать о резолверах, анализе сложности запросов или ограничении глубины. Он знает, что:

  • GET означает получение данных;
  • POST означает создание;
  • PUT означает обновление;
  • DELETE означает удаление.

Вот и всё. Каждый в команде, от стажёра до архитектора, сразу это понимает. Нет кривой обучения. Нет неожиданных накладных расходов на резолверы. Есть только базовая семантика HTTP, которую буквально каждый разработчик использует с 2010 года.

Сравните это с GraphQL, где ваш фронтенд-разработчик должен понимать:

  • Введение в схему;
  • Структуру и синтаксис запросов;
  • Композицию фрагментов;
  • Использование псевдонимов;
  • Порядок выполнения резолверов.

Для простого CRUD-приложения это перебор. Это как использовать паяльную лампу, чтобы зажечь свечу.

Слон в комнате: кэширование

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

Cache-Control: max-age=3600
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 18 Feb 2026 12:00:00 GMT

С REST браузеры кэшируют ваши ответы. CDN кэшируют ваши ответы. Прокси кэшируют ваши ответы. Varnish, Nginx, CloudFlare — все они знают, как обрабатывать кэширование HTTP без написания вами ни одной строки пользовательского кода.

GraphQL? Он отправляет POST-запросы к одному эндпоинту. Нет URL для кэширования. Нет семантики HTTP для использования. Теперь вы отвечаете за:

  1. Создание пользовательских уровней кэширования.
  2. Реализация анализа сложности запросов (чтобы клиенты не могли DOS вас сложными запросами).
  3. Сохранение запросов (чтобы заблокировать то, что клиенты фактически могут запросить).
  4. Управление недействительностью кэша на уровне резолвера.

Это инфраструктурные затраты. Реальные инфраструктурные затраты. Реальные расходы. Реальная операционная нагрузка.

Стартапу с 20 сотрудниками такая сложность не нужна. Компании с 200 сотрудниками, которая не одержима инфраструктурой, такая сложность не нужна. Даже компании с 2000 сотрудниками, возможно, будет лучше с простым REST API и CDN.

Проблема N+1 просто переместилась, а не исчезла

Евангелисты GraphQL любят указывать на проблему N+1 в REST. Хотите пользователя и его последние 5 заказов? Это 6 запросов:

// REST: проблема N+1 (на стороне клиента)
const user = await fetch('/api/users/123');
const orders = await fetch('/api/users/123/orders?limit=5');
// 2 запроса для получения связанных данных

GraphQL утверждает, что решает эту проблему с помощью одного запроса:

# GraphQL: один запрос
query {
  user(id: 123) {
    name
    email
    orders(limit: 5) {
      id
      product
      total
    }
  }
}

Великолепно! Кроме того… GraphQL просто переместил проблему N+1 на серверную сторону. Теперь ваши резолверы выполняют N запросов. Если плохо написанный резолвер открыт для вашей базы данных, один запрос GraphQL может вызвать сканирование таблицы. Клиент конструирует сложный вложенный запрос, и внезапно вы получаете тысячи запросов к базе данных, отправляемых на ваш сервер.

С REST такой сценарий буквально невозможен. Вы контролируете эндпоинт. Вы контролируете, сколько данных раскрывается. Клиент не может случайно DOS вашу базу данных с помощью некорректного запроса, потому что эндпоинт ещё не существует.

Что безопаснее? REST.

Давайте поговорим о том, что на самом деле решил GraphQL

Справедливости ради, GraphQL действительно решает реальные проблемы — но только специфические:

Проблема 1: несколько клиентов с разными потребностями в данных Если у вас есть веб-приложение, мобильное приложение и сторонний API, они, вероятно, хотят разные поля. REST? Вы создаёте несколько эндпоинтов или возвращаете раздутые ответы. GraphQL? Клиент запрашивает то, что ему нужно.

Проблема 2: глубоко вложенные структуры данных Современные пользовательские интерфейсы сложны. Им нужны связанные данные. GraphQL элегантно справляется с этим в одном запросе.

Проблема 3: версионирование API Версионирование REST раздражает. /v1/users, /v2/users, /v3/users. GraphQL добавляет новые поля, не нарушая существующих запросов.

Это реальные проблемы с реальными решениями. Если они у вас есть, GraphQL имеет смысл.

Но вот в чём дело: у большинства приложений нет этих проблем.

Дерево решений, которому никто не следует

Практические рекомендации по проектированию реальных API:

├─ Это простое CRUD-приложение?
│  └─ REST (серьёзно, просто REST)
│
├─ У вас есть 3+ значительно отличающихся клиентов?
│  └─ GraphQL (теперь у вас есть реальный случай использования)
│
├─ Данные глубоко вложены и сложны?
│  └─ GraphQL (здесь он действительно полезен)
│
├─ Вам нужны двусторонние обновления в реальном времени?
│  └─ gRPC или подписки GraphQL (GraphQL справляется с трудом, gRPC лучше)
│
├─ Кэширование — ваша основная забота?
│  └─ REST (даже не думайте о GraphQL)
│
└─ Вы создаёте это в ближайшие 48 часов?
   └─ REST (GraphQL требует времени на старте)

Обратите внимание, к чему относятся большинство стартапов и небольших команд? К категории REST. И всё равно они создают GraphQL API, потому что думают, что «должны» это делать.

Практический пример: приложение для задач, которому GraphQL не нужен

Позвольте мне показать вам, как выглядит реальное приложение. Не гипотетическое. Не учебное пособие. Реальный производственный API, который обслуживает несколько фронтенд-приложений:

// REST API — чистый, простой, прекрасно кэшируется
app.get('/api/todos', async (req, res) => {
  const page = req.query.page || 1;
  const limit = req.query.limit || 20;
  const todos = await db.todos
    .find({ userId: req.user.id })
    .skip((page - 1) * limit)
    .limit(limit)
    .exec();
  res.set('Cache-Control', 'max-age=300');
  res.json(todos);
});
app.get('/api/todos/:id', async (req, res) => {
  const todo = await db.todos.findById(req.params.id);
  res.set('Cache-Control', 'max-age=600');
  res.json(todo);
});
app.post('/api/todos', async (req, res) => {
  const todo = await db.todos.create({
    title: req.body.title,
    userId: req.user.id
  });
  res.status(201).json(todo);
});
app.put('/api/todos/:id', async (req, res) => {
  const todo = await db.todos.findByIdAndUpdate(
    req.params.id,
    req.body,
    { new: true }
  );
  res.json(todo);
});
app.delete('/api/todos/:id', async (req, res) => {
  await db.todos.findByIdAndDelete(req.params.id