коротко

GraphQL — это язык запросов к API, где есть один эндпоинт, и клиент сам в запросе описывает, какие поля ему нужны, — а ответ приходит ровно той формы, что и запрос. Он решает две боли REST: over-fetching (сервер прислал кучу лишних полей) и under-fetching (чтобы собрать экран, пришлось дёргать три разных ручки). Контракт задаётся схемой с типами — что вообще можно спросить. Читающие запросы называются query, меняющие — mutation, и все они летят одним POST на /graphql. Цена гибкости: тяжело кэшировать, легко случайно запросить неподъёмно тяжёлый ответ, и у команды появляется отдельная кривая обучения. Грубо: GraphQL — для богатых клиентов с кучей разных экранов и мобайла, REST — для простого CRUD, публичного API и там, где важен кэш.

Однажды я разбирал, почему мобильное приложение «тормозит на экране профиля». Открыл, что оно дёргает: запрос к /users/42 тащил полный профиль на двести полей, из которых экрану нужны были три — имя, аватар и счётчик заказов. А счётчик заказов вообще приходилось добирать вторым запросом к /orders. То есть мы одновременно тащили лишнее и недотаскивали нужное. Тогда я впервые услышал слова over-fetching и under-fetching — и понял, что GraphQL придумали ровно про этот экран, а не про «модную технологию».

Проблема REST, которую решает GraphQL

REST мы подробно разбирали в записи про REST API, здесь — только то, что важно для контраста. В REST у каждого ресурса свой адрес, и сервер сам решает, какой набор полей вернуть на этот адрес. Форма ответа фиксирована: попросил /users/42 — получил то, что заложил бэкенд, ни больше ни меньше. Отсюда две типовые боли.

Over-fetching — «прислали лишнее». Экрану нужны имя и аватар, а /users/42 возвращает двести полей: адреса, настройки, историю. Лишнее едет по сети, грузит мобильный трафик, замедляет. Клиент не может сказать «пришли только три поля» — форму ответа диктует сервер.

Under-fetching — «не хватило, дёргай ещё». Чтобы собрать один экран профиля, нужны данные пользователя (/users/42), его последние заказы (/orders?user=42) и непрочитанные уведомления (/notifications?user=42). Три запроса, три похода по сети, и клиент сам потом склеивает результат. На мобильном это особенно больно — каждый поход по сети это задержка.

В обоих случаях корень один: в REST форму данных определяет сервер, а клиент берёт что дают. GraphQL переворачивает это — форму определяет клиент.

Что такое GraphQL: запрос — это форма ответа

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

Тот самый экран профиля одним запросом выглядит так:

query {
  user(id: 42) {
    name
    avatarUrl
    orders(last: 3) {
      id
      total
    }
    unreadNotifications
  }
}

Прочитаем по строкам. query — мы читаем данные (ничего не меняем). user(id: 42) — берём пользователя с id 42, и в фигурных скобках перечисляем ровно те поля, что нужны: имя, аватар. Внутри тут же запрашиваем связанные сущности — последние три заказа (только их id и сумму) и счётчик непрочитанных уведомлений. В ответ придёт JSON точно такой же структуры: объект user с полями name, avatarUrl, массивом orders и числом unreadNotifications. Один запрос, один поход по сети, ноль лишних полей — то, что в REST потребовало бы трёх ручек и фильтрации руками.

Схема и типы: контракт GraphQL

Откуда клиент знает, какие поля вообще можно спросить? Из схемы. Это контракт GraphQL: строго типизированное описание всего, что у API есть, — какие сущности, какие у них поля, какого типа, что обязательно. Аналог OpenAPI в REST или .proto в gRPC, только тут схема — сердце всего API, без неё запрос не составить.

type User {
  id: ID!
  name: String!
  avatarUrl: String
  orders(last: Int): [Order!]!
  unreadNotifications: Int!
}

type Order {
  id: ID!
  total: Int!
}

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

Здесь описано, что в системе есть тип User с полями (имя — обязательная строка, аватар — необязательная, заказы — список заказов, счётчик уведомлений — обязательное число) и тип Order. Восклицательный знак значит «поле обязательно, не может быть пустым». Блок Query перечисляет, что вообще можно запросить с самого верха — здесь это user(id). Любой запрос клиента сверяется с этой схемой: попросил несуществующее поле — получишь ошибку ещё до похода в базу. Для аналитика схема — это и есть документ, который надо уметь читать и проверять: что доступно, какого типа, что обязательно.

Query и mutation: читаем и меняем

Все операции в GraphQL делятся на два основных вида, и оба отправляются одним POST-запросом на /graphql — не на разные URL, как в REST.

Query — чтение. «Дай мне такие-то данные». Ничего не меняет, аналог GET в REST. Это пример выше с профилем.

Mutation — изменение. «Создай / измени / удали». Аналог POST/PUT/PATCH/DELETE, но тоже отправляется как POST на тот же /graphql, просто с ключевым словом mutation. Полезная деталь: в той же мутации можно сразу попросить, какие поля вернуть после изменения.

mutation {
  createOrder(userId: 42, items: [{ sku: "A-100", qty: 2 }]) {
    id
    status
  }
}

Эта мутация создаёт заказ и тут же просит вернуть только id и статус нового заказа — не весь объект. То есть даже на запись клиент управляет формой ответа.

Один URL вместо множества

Это сбивает с толку тех, кто привык к REST: в GraphQL нет /orders, /users, /notifications. Есть один /graphql, и всё многообразие операций живёт внутри тела запроса. Поэтому привычный приём «посмотреть список эндпоинтов, чтобы понять API» тут не работает — карта API это схема, а не список URL.

Как это выглядит во времени

sequenceDiagram
  participant C as Клиент
  participant S as Сервер (/graphql)
  participant DB as Источники данных
  C->>S: POST /graphql {query: user(42){name, orders, ...}}
  S->>S: сверить запрос со схемой
  S->>DB: собрать только запрошенные поля
  DB-->>S: данные
  S-->>C: один JSON ровно нужной формы

Схема показывает путь одного запроса. Клиент шлёт единственный POST на /graphql, в теле — дерево нужных полей. Сервер сначала сверяет запрос со схемой: есть ли такие поля, верные ли типы — если нет, сразу вернёт ошибку. Если всё в порядке, сервер идёт за данными, причём только за теми полями, что попросили (а не за всем подряд), возможно собирая их из нескольких источников за раз. В ответ уходит один JSON ровно той формы, что была в запросе. Главное отличие от REST: форму ответа задаёт клиент в запросе, а сервер подстраивается, и всё это — за один поход по сети.

Цена гибкости

GraphQL не бесплатен — за гибкость платят в трёх местах, и аналитику честно их назвать важнее, чем хвалить технологию.

Сложнее кэшировать. В REST GET /orders/123 — это стабильный адрес, его легко закэшировать на любом уровне (браузер, прокси, CDN): один URL — один ответ. В GraphQL всё идёт POST'ом на один /graphql с разным телом — стандартный HTTP-кэш по URL тут не работает, кэширование приходится строить отдельно и руками (про сам кэш — в записи про кэширование).

Легко выстрелить тяжёлым запросом. Раз клиент сам строит дерево, он может попросить «пользователей → их заказы → товары в заказах → отзывы на товары» вглубь на много уровней — и положить сервер одним запросом. В REST форму ответа ограничивал сервер, в GraphQL это ограничение надо вводить специально (лимиты глубины, сложности запроса).

Своя кривая обучения. Схема, резолверы, инструменты, особенности кэша и безопасности — это отдельный стек, который команде надо освоить. Для простого CRUD это перебор: накладные расходы на освоение не окупятся.

СвойствоRESTGraphQL
ЭндпоинтыМного URL по ресурсамОдин /graphql
Форму ответа задаётСерверКлиент в запросе
Over / under-fetchingЧастая больРешено по дизайну
Сбор экранаНередко несколько запросовОдин запрос
КэшированиеПросто (по URL, из коробки)Сложно, строится руками
КонтрактOpenAPI (желателен)Схема (обязательна, в основе)
Риск тяжёлого запросаНизкий (форму держит сервер)Есть, нужны лимиты
Порог входаНизкийВыше

Когда GraphQL, а когда REST

GraphQL — когда у вас богатые клиенты с множеством разных экранов, каждому нужен свой набор полей, и фронтенд хочет собирать данные сам, не дёргая бэкенд под каждый экран. Особенно хорош для мобайла: один точный запрос вместо трёх экономит трафик и сетевые задержки. Типичный сценарий — большое приложение, где UI часто меняется, а бэкенд не должен переделывать ручки под каждый новый экран.

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

Простое правило выбора

Много разных экранов и мобайл, которым нужен свой набор полей → смотрите в сторону GraphQL. Простой CRUD, публичное API, важен кэш → REST. И не выбирайте GraphQL «потому что современно»: для маленького сервиса с пятью ручками его сложность не окупится. Сомневаетесь — начинайте с REST, на GraphQL переходят осознанно, когда упираются в over/under-fetching.

Где это касается аналитика

Если API на GraphQL, контракт — это схема, и читать её надо уметь: какие типы, какие поля, что обязательно (восклицательный знак), какие query и mutation доступны. Прямой вопрос на приёмке — заданы ли лимиты на глубину и сложность запроса: без них один клиент случайно или нарочно кладёт сервер. Если в требованиях на GraphQL-сервис нет ни схемы, ни слова про лимиты — спека дырявая.

Откуда это взялось

GraphQL придумали в Facebook: внутренне его начали использовать около 2012 года, а в открытый доступ выложили в 2015-м. Родился он из боли мобильных клиентов — медленные сети того времени и слабые телефоны делали лишние поля и лишние походы по сети очень дорогими. Существующий REST-бэкенд заставлял мобильное приложение либо тащить лишнее, либо собирать экран из нескольких запросов, и инженеры Facebook сделали язык, где форму ответа задаёт сам клиент. Поэтому корень GraphQL — не «красивее, чем REST», а конкретная экономия трафика и задержек на мобильных.

Частые вопросы

Что такое GraphQL простыми словами?

GraphQL — это язык запросов к API, где есть один эндпоинт (/graphql), и клиент сам в запросе перечисляет, какие поля ему нужны, а ответ приходит ровно той же формы. Он решает две боли REST: over-fetching (сервер прислал лишние поля) и under-fetching (чтобы собрать экран, пришлось делать несколько запросов). Что вообще можно спросить, описано в схеме с типами — это контракт GraphQL. Читающие операции называются query, меняющие — mutation, и обе летят одним POST на /graphql.

GraphQL или REST — что выбрать?

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

Что такое over-fetching и under-fetching?

Over-fetching — когда сервер присылает больше данных, чем нужно: экрану нужны три поля, а ручка возвращает двести. Лишнее грузит сеть и замедляет, особенно на мобильном. Under-fetching — обратное: одной ручки не хватает, и чтобы собрать экран, приходится делать несколько запросов к разным URL и склеивать результат вручную. Обе боли возникают в REST, потому что форму ответа диктует сервер; GraphQL их закрывает тем, что форму задаёт клиент в запросе.