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

Проблема: один экран — много запросов

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

http
GET /users/42             → id, name, email, avatar, createdAt, settings…
GET /users/42/posts       → every post in full: text, tags, dates…
GET /users/42/followers   → the full list of followers

Здесь сразу две проблемы:

  • Under-fetching (недополучение данных) — одного запроса не хватает, приходится делать несколько. На мобильном интернете каждый лишний «поход» к серверу (round trip) стоит сотни миллисекунд.
  • Over-fetching (лишние данные) — сервер возвращает всё, что знает о ресурсе, а экрану нужны 2–3 поля. Лишние килобайты расходуют трафик и батарею.

Обычно эти проблемы «лечат» специальными эндпоинтами вроде /users/42/profile-screen. Но экранов становится больше, у веба и мобильного приложения разные потребности — и бэкенд превращается в зоопарк почти одинаковых маршрутов.

REST: три запроса и лишние поля; GraphQL: один запрос и ровно нужные данные
REST: три запроса и лишние поля; GraphQL: один запрос и ровно нужные данные

Что такое GraphQL

GraphQL — это язык запросов к API и среда выполнения этих запросов на сервере. Его создали в Facebook в 2012 году для мобильных приложений, в 2015 году открыли для всех, а сейчас развитием языка занимается независимая GraphQL Foundation.

Главная идея: клиент сам описывает форму нужных ему данных, а сервер возвращает ровно это — не больше и не меньше. Все запросы идут на один адрес (endpoint), обычно /graphql.

Тот же экран профиля — одним запросом:

graphql
query ProfileScreen {
  user(id: 42) {
    name
    posts(last: 2) {
      title
    }
    followersCount
  }
}

И ответ сервера:

json
{
  "data": {
    "user": {
      "name": "Leyla",
      "posts": [
        { "title": "Hello GraphQL" },
        { "title": "Schemas 101" }
      ],
      "followersCount": 128
    }
  }
}

Обратите внимание: ответ повторяет форму запроса. Запросили name — получили name. Не запросили email — его нет в ответе. Корень ответа всегда — объект data.

Каждое поле запроса находит своё место в ответе
Каждое поле запроса находит своё место в ответе

💡 GraphQL — не база данных и не библиотека для конкретного языка, а спецификация. Серверы GraphQL пишут на JavaScript, Python, Go, Java, PHP и других языках, а данные могут лежать где угодно: в PostgreSQL, MongoDB, другом REST API или кеше.

Три вида операций

ОперацияДля чегоПохоже в REST на
queryчтение данныхGET
mutationсоздание, изменение, удалениеPOST, PUT, PATCH, DELETE
subscriptionсобытия в реальном времениWebSocket или SSE

Запросы (query) подробно разберём в следующих уроках, мутациям и подпискам тоже посвящены отдельные темы.

Схема — контракт между фронтендом и бэкендом

Сервер GraphQL всегда описывает свои данные схемой — набором типов и полей. Схему пишут на языке SDL (Schema Definition Language):

graphql
type User {
  id: ID!
  name: String!
  posts(last: Int): [Post!]!
  followersCount: Int!
}

type Post {
  id: ID!
  title: String!
}

type Query {
  user(id: ID!): User
}

Схема даёт три важных преимущества:

  1. Строгая типизация. Сервер заранее знает, какие поля существуют и какого они типа. Запрос с несуществующим полем отклоняется ещё до выполнения.
  2. Самодокументируемость. Схему можно запросить у самого сервера (это называется introspection), поэтому инструменты вроде Apollo Sandbox показывают документацию и автодополнение.
  3. Контракт. Фронтенд и бэкенд договариваются о схеме и дальше работают параллельно: фронтенд может писать запросы, пока бэкенд ещё реализует их.

Знак ! означает «поле никогда не бывает null». Все типы подробно разберём в следующем уроке.

Как сервер выполняет запрос

Каждый запрос к серверу GraphQL проходит одни и те же четыре этапа:

  1. Parse (разбор) — текст запроса превращается в синтаксическое дерево. Забытая скобка — ошибка уже на этом шаге.
  2. Validate (проверка) — запрос сверяется со схемой: существуют ли поля, правильного ли типа аргументы.
  3. Execute (выполнение) — для каждого запрошенного поля вызывается функция-резолвер, которая достаёт данные: из базы, другого API или кеша.
  4. Respond (ответ) — результаты собираются в JSON той же формы, что и запрос: { "data": …, "errors": … }.
Parse → Validate → Execute → Respond
Parse → Validate → Execute → Respond

Технически запрос GraphQL — обычный HTTP POST с JSON-телом. Его можно отправить даже из терминала:

bash
curl -X POST http://localhost:4000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ user(id: 42) { name followersCount } }"}'

Ошибки и HTTP-статусы

В REST об ошибке сообщает статус ответа: 404, 400, 500. В GraphQL сервер обычно отвечает 200 OK, а ошибки кладёт в массив errors — рядом с теми данными, которые удалось получить:

json
{
  "data": { "user": null },
  "errors": [
    {
      "message": "User 42 not found",
      "path": ["user"],
      "extensions": { "code": "NOT_FOUND" }
    }
  ]
}

Поле path показывает, на каком поле запроса произошла ошибка, а extensions.code — машиночитаемый код, по которому клиент решает, что делать.

⚠️ Частая ошибка новичков — проверять только response.ok. В GraphQL ответ может прийти со статусом 200 и при этом содержать ошибки. Всегда проверяйте поле errors.

Запрос из JavaScript

Для старта специальная библиотека не нужна — хватит обычного fetch:

javascript
const query = `
  query ProfileScreen($id: ID!) {
    user(id: $id) {
      name
      followersCount
    }
  }
`;

const res = await fetch('http://localhost:4000/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query, variables: { id: '42' } }),
});

const { data, errors } = await res.json();
if (errors) console.error(errors);
else console.log(data.user.name); // Leyla

Значения передаются отдельно в variables, а не вклеиваются в текст запроса, — так безопаснее и удобнее. Переменные подробно разберём в уроке о запросах.

REST или GraphQL: сравнение

КритерийRESTGraphQL
Адресамного эндпоинтов (/users, /posts)один эндпоинт (/graphql)
Форма данныхопределяет серверопределяет клиент
Лишние и недостающие данныечастая проблемарешается самим запросом
Типы и документацияOpenAPI — по желаниюсхема обязательна
Версионирование/v1, /v2эволюция схемы и @deprecated
HTTP-кешированиепросто: GET и Cache-Controlсложнее: обычно POST, кеш на клиенте
ОшибкиHTTP-статусымассив errors, статус обычно 200
Загрузка файловестественночерез отдельные решения
Порог входанижевыше: схема, резолверы, инструменты

Когда выбирать GraphQL

  • У продукта несколько клиентов (веб, iOS, Android), и им нужны разные срезы одних и тех же данных.
  • Данные сильно связаны (пользователи → посты → комментарии → авторы), и экраны собирают их в разных комбинациях.
  • Фронтенд меняется быстро и не хочет ждать новый эндпоинт под каждый экран.
  • Нужен единый шлюз над несколькими сервисами — паттерн BFF (Backend for Frontend).

Публичные GraphQL API есть, например, у GitHub и Shopify.

Когда REST проще и лучше

  • Простой CRUD и пара форм — GraphQL добавит сложности без выгоды.
  • Публичный API, для которого важны HTTP-кеширование и CDN.
  • Много работы с файлами и бинарными данными.
  • Небольшая команда, которая только осваивает бэкенд.

💡 REST и GraphQL — не враги. Многие компании используют оба: REST — для вебхуков и загрузки файлов, GraphQL — для данных приложения.

Экосистема

  • Apollo Server, GraphQL Yoga — серверы для Node.js. В курсе используем Apollo Server.
  • Apollo Client, urql, Relay — клиенты с кешированием для React и других фреймворков.
  • GraphQL Code Generator — генерирует типы TypeScript из схемы и запросов.
  • Apollo Sandbox, GraphiQL — «песочницы» для запросов с автодополнением и документацией.

Итоги

  • GraphQL — язык запросов к API: клиент описывает нужные поля, сервер возвращает ровно их.
  • Один endpoint /graphql и три вида операций: query, mutation, subscription.
  • Схема описывает типы и служит контрактом между фронтендом и бэкендом.
  • Запрос проходит этапы parse → validate → execute → respond; данные для полей достают резолверы.
  • Ошибки приходят в массиве errors, часто вместе со статусом 200.
  • GraphQL хорош для связанных данных и разных клиентов; для простого CRUD чаще достаточно REST.