Содержание
  1. Учебный экран: два клиента и три заказа
  2. REST: два ресурса и полный обмен
  3. GraphQL: схема, POST и эталон ответа
  4. Проверка данных: Анна и суммы
  5. Сколько HTTP-обращений на выбранных маршрутах
  6. Ошибка неизвестного поля: media type и статус
  7. Request error и field error: почему 2xx не значит успех
  8. Что выбрать для этого экрана
  9. Как разобрать ход проверки и не спутать его с измерением
  10. Частые вопросы
Подборки и сравнения

GraphQL или REST: экран, запросы и обработка ошибок

5 ноября 2026 · 11 минут чтения

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

Учебный экран: два клиента и три заказа

Возьмём один вымышленный экран карточки клиента. Входные данные — весь набор, ничего сверх него не добавляем.

Клиенты:

  • {id:"1", name:"Анна"}
  • {id:"2", name:"Борис"}

Заказы:

  • {id:"1001", clientId:"1", total:1500}
  • {id:"1002", clientId:"1", total:2300}
  • {id:"1003", clientId:"2", total:900}

Экран клиента 1 требует name=Анна и суммы [1500,2300]. Полей email, телефон, адрес, статус, createdAt в этом входе нет, и добавлять их в пример не нужно: они не участвуют в проверке.

Три заказа здесь важны по одной причине. Заказ 1003 принадлежит Борису (clientId:"2"). Если запрос или фильтрация на клиенте захватят его, экран покажет чужую сумму 900 и проверка провалится. Поэтому проверяем не только форму ответа, но и принадлежность: у Анны должны быть ровно 1500 и 2300.

Так выглядит весь вход. Дальше сравниваем, как его отдаёт REST и как его отдаёт GraphQL, и что при этом происходит с ошибками.

REST: два ресурса и полный обмен

Выбранный REST-маршрут этого учебного API — два обращения к двум ресурсам.

Запрос и ответ первого ресурса:

ТЕКСТ3 строки
GET /clients/1
200 application/json
{"id":"1","name":"Анна"}

Запрос и ответ второго ресурса:

ТЕКСТ3 строки
GET /clients/1/orders
200 application/json
[{"id":"1001","clientId":"1","total":1500},{"id":"1002","clientId":"1","total":2300}]

Здесь два обращения. Экрану не нужны id и clientId — они приходят как часть серверного представления ресурса. Это свойство выбранного контракта.

Честная одношаговая альтернатива: составной REST-ресурс GET /client-cards/1, который сразу возвращает {"name":"Анна","totals":[1500,2300]} за одно обращение. Такой ресурс допустим, и выводить преимущество протокола из искусственного запрета на него нельзя.

Сравнение двух REST-вариантов по явным критериям:

Критерий Два ресурса Составной ресурс
Обращения на экран 2 1
Форма ответа Два представления Одно представление
Лишние поля id, clientId нет
Кто задаёт форму Сервер Сервер
Серверная реализация нужна нужна

Оба варианта требуют серверной реализации и прав доступа. Выбор между ними — вопрос контракта, который вы поддерживаете.

GraphQL: схема, POST и эталон ответа

Минимальная полная GraphQL-схема учебного экрана:

GRAPHQL3 строки
type Query { client(id: ID!): Client }
type Client { id: ID!, name: String!, orders: [Order!]! }
type Order { id: ID!, clientId: ID!, total: Int! }

Это схема. Resolver и хранилище должны быть реализованы отдельно: схема описывает типы и поля, а данные для них отдаёт ваш код.

Готовый запрос с заголовками, телом и переменными:

ТЕКСТ5 строк
POST /graphql
Content-Type: application/json
Accept: application/graphql-response+json

{"query":"query ClientCard($id: ID!) { client(id: $id) { name orders { total } } }","variables":{"id":"1"}}

Эталон ответа по нашим данным:

ТЕКСТ2 строки
200 application/graphql-response+json
{"data":{"client":{"name":"Анна","orders":[{"total":1500},{"total":2300}]}}}

Один POST в выбранном маршруте. Количество внешних HTTP-вызовов не равно количеству запросов resolver к базе данных и не измеряет задержку. В документации проекта GraphQL описано, что клиент указывает поддерживаемые media types в заголовке Accept, а сервер отвечает типом в Content-type; для POST тело кодируется в JSON с полями query, operationName, variables, extensions, где query обязателен, а operationName также обязателен, если документ содержит несколько операций.

Проверка данных: Анна и суммы

Проверка данных идёт по значениям и принадлежности. Три шага.

Шаг 1. Получить Анну. В обоих маршрутах поле name должно быть равно строке «Анна».

Шаг 2. Получить суммы [1500,2300]. В REST это два элемента массива заказов, в GraphQL — два элемента orders с полем total.

Шаг 3. Исключить чужой заказ. Заказ 1003 с суммой 900 принадлежит Борису и в результат попасть не должен. Если он появился, ошибка в фильтрации по clientId.

Порядок проверки:

Что проверяем REST GraphQL
Имя name = Анна client.name = Анна
Суммы [1500,2300] orders[].total = [1500,2300]
Чужой заказ 1003 отсутствует среди orders[].total отсутствует 900
Лишние поля id, clientId нет

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

Сколько HTTP-обращений на выбранных маршрутах

Число обращений считаем только для выбранных маршрутов, без обещаний скорости.

  • Два заданных REST-ресурса: 2 обращения.
  • Составной REST-ресурс: 1 обращение.
  • GraphQL-пример: 1 POST.

Это не универсальные числа протоколов. Они относятся к конкретным маршрутам этого экрана. Составной REST-ресурс даёт одно обращение так же, как GraphQL, поэтому вывод «GraphQL всегда меньше запросов» из этих чисел не следует.

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

Количество внешних HTTP-вызовов не равно количеству запросов resolver к базе данных. Один POST может породить несколько обращений к хранилищу, и наоборот. Задержку измеряют на своём backend отдельно, после того как данные и ошибки проверены.

Ошибка неизвестного поля: media type и статус

Проверим ошибочный запрос:

ТЕКСТ
{ client(id:"1") { unknownField } }

Для выбранного HTTP-контракта по сохранённому draft GraphQL-over-HTTP с application/graphql-response+json при неизвестном поле ожидается 422, ключ errors и отсутствие data до исполнения resolver. Точный текст ошибки не закреплён.

Страница GraphQL Learn описывает типичный статус 400 для ошибки валидации, а сохранённый снимок draft в разделе Document validation failure рекомендует 422 без исполнения запроса. Здесь выбран второй контракт. Ошибки разбора JSON или GraphQL-документа в этом draft по-прежнему получают рекомендацию 400. Это снимок разрабатываемого документа, поэтому статус нужно закрепить в контракте конкретного сервера.

Отличие legacy application/json: для этого media type статус на такую ошибку может отличаться, в том числе быть 2xx. В документации проекта отмечено, что для legacy-серверов с application/json коды 4xx и 5xx для валидных запросов, не сумевших выполниться, не рекомендуются, и вместо них используется 2xx. Для application/graphql-response+json сервер отвечает 4xx или 5xx, если валидный запрос не выполнился.

Draft не выдаём за обязательное поведение всех GraphQL-серверов. Отдельный учебный REST-контракт для неизвестного клиента отвечает 404; это не следствие отсутствия GraphQL-поля.

Request error и field error: почему 2xx не значит успех

Документация проекта GraphQL различает два вида ошибок.

Request errors возникают до исполнения: синтаксическая ошибка в документе или ошибка валидации против схемы, например запрос несуществующего поля или неверный тип переменной. Ключ data в ответ не включается, потому что сервер возвращает ошибку до запуска resolver. В errors есть message, а locations при наличии указывает место в документе.

Field errors возникают во время исполнения: resolver выбрасывает ошибку или возвращает null для поля с Non-Null типом. GraphQL пытается продолжить исполнение остальных полей; ответ может содержать частичные данные в data вместе с errors.

Отсюда практический вывод: 2xx не означает, что все поля получены. В документации проекта сказано, что ответ с непустым data отдаётся со статусом 2xx даже при наличии errors, поскольку в HTTP нет кода для частичного успеха. Проверять только HTTP-код недостаточно — нужно смотреть data и errors вместе.

Про кэш: GraphQL не объявляется некэшируемым. GET-запросы для query-операций поддерживаются на усмотрение сервера, а для длинных документов применяются persisted documents, automatic persisted queries или trusted documents — клиент отправляет короткий идентификатор вместо полного текста. Личные данные при этом нельзя делать публичными ради CDN.

Что выбрать для этого экрана

Условный выбор по нашим данным.

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

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

Сравнение по критериям:

Критерий REST из двух ресурсов GraphQL из примера
Обращения на экран 2; составной ресурс даёт 1 1
Форма Заданные сервером представления Поля запроса в рамках схемы
HTTP-кэш Возможен для выбранных GET Возможен для поддерживаемых GET-query
Ошибки Статус и тело по контракту Статус, media type, data/errors
Стоимость Зависит от endpoint и backend Зависит от полей и resolver
Эволюция Версия URL или совместимые изменения Совместимая эволюция схемы

Приёмку сначала привязывают к данным и ошибкам, затем измеряют задержку на своём backend. Ни одна политика эволюции не обязательна для всех API.

Как разобрать ход проверки и не спутать его с измерением

Разбор удобно вести как последовательность решений. Сначала читатель фиксирует, что именно должен показать экран: имя Анны и две суммы. Затем выбирает маршрут и только после этого проверяет, что маршрут действительно отдал нужную проекцию. Такой порядок важен потому, что проверка формы ответа и проверка значений — разные операции, и вторая без первой не имеет смысла.

Возможная ошибка на этом шаге — принять синтаксически корректный JSON за правильный результат. Ответ может быть валидным, содержать ожидаемые ключи и при этом включать заказ Бориса. Если проверяющий сверяет только структуру, чужой заказ с суммой 900 пройдёт незамеченным, а экран покажет неверные данные. Поэтому принадлежность записей проверяется отдельно от их наличия: сначала убеждаемся, что записей ровно две, затем — что обе относятся к клиенту 1.

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

Третья ошибка касается ошибок. Проверяющий может ограничиться HTTP-кодом и счесть ответ успешным, если код попал в диапазон 2xx. При частичном выполнении это неверно: данные и ошибки могут прийти вместе, и часть полей окажется пустой. Действие читателя — смотреть на оба ключа одновременно и различать ошибку до исполнения и ошибку во время исполнения. Для неизвестного поля выбранный контракт по сохранённому draft с media type application/graphql-response+json ожидает 422, ключ errors и отсутствие data; для legacy application/json статус может отличаться.

Отдельно стоит проговорить границу вывода. Схема описывает типы и поля, но не является сервером: resolver и хранилище реализуются отдельно, и без них запрос не выполнится. Сравнение в примере опирается на документально проверенную схему и ручную приёмку; серверы и модели при подготовке не запускались, сетевой тайминг не измерялся. Поэтому числа обращений и ожидаемые статусы — это условный вход для проверки.

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

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

GraphQL или REST API — что выбрать для одного экрана?

Смотрите на форму данных и на число проекций. Для стабильного экрана с готовым составным REST-ресурсом достаточно REST. GraphQL подходит, когда разные клиенты регулярно запрашивают разные связанные поля и вы готовы сопровождать схему, resolver, права и ограничения стоимости.

Сколько HTTP-запросов делает GraphQL по сравнению с REST?

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

Почему GraphQL вернул 200, а данных нет?

Проверяйте тело ответа. Legacy-сервер с application/json может вернуть 2xx при ошибке до исполнения; тогда data отсутствует, а errors описывает причину. При ошибке поля data может присутствовать вместе с errors. HTTP-код сам по себе не подтверждает получение всех данных.

Какой статус у GraphQL при ошибке неизвестного поля?

Для выбранного контракта по сохранённому draft с application/graphql-response+json при неизвестном поле ожидается 422, ключ errors и отсутствие data. Для legacy application/json статус может отличаться, в том числе быть 2xx.

Можно ли кэшировать GraphQL-запросы через CDN?

Да, для поддерживаемых GET-query и persisted queries при подходящей политике доступа. Личные данные при этом нельзя делать публичными ради CDN.

Читайте также

3 материала