Webrium
Подписаться

GraphQL или REST: что выбрать для своего API

Чем отличаются подходы REST и GraphQL и какие компромиссы ждут при выборе каждого из них

REST возвращает полный объект, GraphQL — только запрошенные поляREST: весь объектGraphQL: выборочно

Как устроены REST и GraphQL: два подхода к API

Когда мы проектируем API, перед нами стоят две фундаментальные задачи: как описать структуру данных и как организовать их передачу между клиентом и сервером. REST и GraphQL — это не просто разные технологии, а принципиально разные подходы к решению этих задач.

REST (Representational State Transfer) строится на идее ресурсов, доступных по уникальным адресам. Каждый ресурс — это отдельная сущность, например, пользователь или заказ. Операции над ресурсами определяются HTTP-методами: GET для получения, POST для создания, PUT для обновления, DELETE для удаления. Клиент запрашивает данные целиком, а сервер возвращает их в заранее определённом формате.

GraphQL — это язык запросов, где клиент сам определяет, какие поля ему нужны. Вместо того чтобы получать весь объект пользователя, клиент может запросить только имя и аватар. Это особенно удобно, когда один и тот же API обслуживает несколько клиентов с разными требованиями к данным.

Задача GraphQL: гибкость за счёт контроля

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

В GraphQL клиент описывает нужные поля в запросе, а сервер возвращает только их. Это сокращает объём передаваемых данных и упрощает поддержку API: вместо множества эндпоинтов — одна схема. Но за такую гибкость приходится платить сложностью реализации и дополнительными ограничениями.

Что теряем с GraphQL

  1. HTTP-кеширование становится сложнее В REST каждый ресурс имеет уникальный адрес, и браузер или CDN может кешировать ответы. Например, если клиент запросил /users/42, то при следующем обращении к этому же адресу ответ может быть взят из кеша. В GraphQL один адрес обслуживает все запросы, поэтому кеширование приходится настраивать вручную. Для HTTP-кеширования GraphQL используют GET-запросы с persisted queries по хешу (например, APQ), где сервер принимает и произвольные запросы. Если нужно ещё и запретить произвольные запросы, включают отдельный режим allowlist: сервер выполняет только заранее зарегистрированные операции. Кроме того, есть кеширование на клиенте и на уровне резолверов.

  2. Стоимость запросов непредсказуема Клиент может запросить слишком много данных или сделать слишком сложный запрос. Например, он может запросить всех пользователей и их заказы, причём заказы с товарами, а товары с отзывами. Чтобы избежать этого, сервер должен ограничивать глубину запросов (сколько уровней вложенности разрешено), их сложность (сколько полей можно запросить за раз) и частоту обращений (сколько запросов в секунду может сделать один клиент).

  3. Авторизация на уровне полей В REST авторизация обычно проверяется на уровне ресурса: либо клиент имеет доступ ко всему объекту, либо нет. В GraphQL доступ может быть разным для каждого поля. Например, только администратор видит email пользователя, а остальные — только имя. Это требует дополнительной логики на сервере и усложняет поддержку.

  4. Лавина обращений к базе В GraphQL каждый запрашиваемый объект или поле может требовать отдельного обращения к базе данных. Если резолверы (функции, которые возвращают данные для полей) написаны неэффективно, один запрос может породить десятки обращений к базе. Например, если клиент запросил список пользователей и для каждого пользователя — его последние заказы, то для каждого пользователя может потребоваться отдельный запрос к базе. Это подробно разбирается в материале про ORM и SQL.

Эволюция схемы: без версий или с версиями

В REST изменения обычно вводят через совместимые дополнения: новые поля добавляются без поломки существующих клиентов. Если без поломки не обойтись, выпускается новая версия API, например, /v2/users. Это удобно для публичных API, где клиенты неизвестны, но требует поддерживать старые версии.

GraphQL тоже поддерживает совместимые изменения, но у него есть преимущество: если собирать статистику использования полей (трассировка операций, schema registry), видно, какие поля ещё запрашиваются. Поле сначала помечают @deprecated(reason: ...), ждут, пока трафик по нему упадёт до нуля у всех версий клиентов, включая старые мобильные сборки, и только потом удаляют. Однако, если схема разрастается и её никто не контролирует, она становится противоречивой и сложной для поддержки.

У обоих подходов основной путь — совместимые добавления и пометка устаревшего (в GraphQL — deprecated на поле). REST, если без поломки не обойтись, выпускает новую версию целиком.

Когда REST проще: три условия

REST удобнее в следующих случаях:

  1. Публичный API Когда клиенты неизвестны, важно обеспечить предсказуемую стоимость запросов и HTTP-кеширование.

  2. Мало клиентов Если у API всего несколько клиентов, проще поддерживать отдельные эндпоинты для каждого из них, чем сложную схему GraphQL.

  3. Ресурсы хорошо ложатся на адреса Если данные можно естественно разделить на ресурсы (например, пользователи, заказы, товары), REST будет проще и понятнее.

Промежуточные варианты

Иногда используют промежуточные решения между REST и GraphQL:

  • Слой для фронтенда поверх REST Это отдельный сервис, который агрегирует данные из нескольких REST-эндпоинтов и возвращает их в удобном для фронтенда формате.

  • RPC со схемой RPC (Remote Procedure Call) позволяет вызывать методы на сервере, как если бы они были локальными функциями. RPC с описанной схемой методов (например, gRPC со схемой protobuf или JSON-RPC с описанием в OpenRPC) даёт строго типизированные методы с фиксированным ответом.

  • Sparse fieldsets в REST В JSON:API поля ресурса выбирают параметром вида fields[users]=name,avatar, а многие REST API поддерживают упрощённый вариант ?fields=name,avatar.

Тупик: ожидание новых эндпоинтов

Часто GraphQL выбирают, чтобы фронтенд перестал ждать новые эндпоинты от бэкенда. Кажется, что если фронтенд сам может выбирать поля, то бэкенду не нужно создавать новые эндпоинты для каждого экрана. Но на практике это не так просто.

Первое, что пробуют — это дать фронтенду полную свободу в запросах. Фронтенд начинает запрашивать всё, что ему нужно, и сервер быстро сталкивается с проблемами:

  • Непредсказуемая нагрузка Клиент может запросить слишком много данных или сделать слишком сложный запрос.

  • Лавина обращений к базе Как уже упоминалось, один запрос GraphQL может породить десятки обращений к базе.

  • Сложность авторизации Если доступ к разным полям требует разных прав, то сервер должен проверять права для каждого поля.

В итоге ожидание не исчезает — оно переносится в согласование схемы и ограничения стоимости запросов. Если схема разрастается и её никто не контролирует, она становится противоречивой и сложной для поддержки.

Позиция редакции

REST по умолчанию, GraphQL при множестве разнородных клиентов и при условии, что у схемы есть владелец.

Позиция неверна, например, когда клиентов много, но схему никто не ведёт и не ограничивает стоимость запросов: тогда GraphQL проигрывает REST. Или наоборот: клиент один, но выборки у него сильно меняются, и готовая инфраструктура GraphQL обходится дешевле, чем поток новых эндпоинтов.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Разделение подходов: проектирование от экранов и от структуры данныхЭкраны интерфейсаСтруктура данных
Предыстория

REST API: что решить до первого маршрута

Как спроектировать REST API, чтобы не пришлось переписывать его через полгода: структура данных, ошибки и эволюция без поломок.

Ответ из кеша совпадает с источником или разошёлся с нимсовпалоустарело
Усиление

Кеширование по HTTP: заголовки, которые действительно работают

Как правильно настроить HTTP-кэширование, чтобы не путать срок годности с проверкой актуальности — и не сломать API.

Контракт существует, только если записан отдельно от кода обеих сторонполе вне схемы
Практика

JSON как контракт: где он подводит

Как задокументировать JSON-контракт, чтобы API не сломался при изменении схемы.

Три показателя нагрузки: два в норме, один зашкаливает из-за N+1 запросовN+1 запрос
Углубление

ORM или запросы руками: где проходит граница

ORM экономит время на простых запросах, но усложняет оптимизацию — разбираем, где стоит переключиться на SQL и как избежать ловушек абстракции.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

выбрать · Enter открыть · Esc закрыть