Введение
В этой статье мы рассмотрим, что такое GraphQL и для чего он был создан. Разберёмся, какие задачи сложно решить в REST API, и какую альтернативу предлагает GraphQL.
Согласно официальной документации, GraphQL — это язык запросов для API-интерфейсов и среда, в которой они выполняются. С помощью GraphQL можно получить данные из API и передать их в приложение так же, как и с помощью REST-like API, JSON-RPC, SOAP и т. д.
Однако GraphQL приводит API в определённый — графовый — вид, что позволяет довольно гибко обращаться с данными:
Разные клиенты могут запрашивать только нужные им поля из одного ресурса;
Вложенные структуры описывают сложные многоуровневые объекты в одном запросе;
Избыточные данные исключаются клиентом из ответов сервера.
Такая гибкость позволяет обойти ряд ограничений REST API и других инструментов RPC.
Именно для этого Facebook в 2012 году создал GraphQL. Разработчики социальной сети столкнулись с уникальными вызовами: миллиарды пользователей, терабайты данных и множество клиентских приложений — мобильные версии, веб-интерфейс, десктопные приложения — все они отображают одну и ту же информацию, но требуют разные наборы данных.
Более того, одно и то же приложение во множестве случаев предоставляет одну и ту же информацию, но в разных форматах. Например, данные о пользователе отображаются на его странице, а также когда этот пользователь показан в списках участников группы, чьих-то друзей и т. д. Атрибуты для каждого запроса могут быть разными, но объект или ресурс один и тот же. REST-решения перестали справляться с этими задачами в масштабе Facebook. Так появился GraphQL.
В этой статье мы на примере разберём, c какими проблемами в структуре данных мы сталкиваемся в REST-like API и как такие задачи решает GraphQL. А также поверхностно рассмотрим реализацию в коде и поверх HTTP, и основные ограничения GraphQL.
Ограничения REST API
Как упоминалось выше, GraphQL был разработан для обхода некоторых ограничений REST-like API.
Давайте рассмотрим пример с заказом в интернет-магазине. Если мы хотим сделать запрос, который возвращает нам объект заказа, то в REST-like API это будет выглядеть так:
GET /api/orders/ORD-2024-1234 HTTP/1.1
Host: api.example.com
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache
Date: Sat, 02 Nov 2024 14:30:00 GMT
Content-Length: 541
{
"status": "success",
"data": {
"order": {
"id": "ORD-2024-1234",
"client": {
"id": "CLT-789",
"name": "Иван Петров",
"email": "ivan@example.com",
"phone": "+7 (999) 123-45-67"
},
"date": "2024-11-02T14:30:00Z",
"status": "processing",
"items": [
{
"id": "ITEM-001",
"name": "Смартфон iPhone 13",
"quantity": 1,
"price": "75000.00",
"subtotal": "75000.00"
},
{
"id": "ITEM-002",
"name": "Защитное стекло",
"quantity": 2,
"price": "990.50",
"subtotal": "1981.00"
}
],
"totalAmount": "76981.00",
"currency": "RUB"
}
}
}
Видно, что ответ довольно объёмный, особенно если учесть, что в заказе может быть больше 10 товаров. В большей части вариантов использования запроса параметр items[]
вообще не нужен. Например, когда мы узнаём статус заказа, или когда хотим получить контакты клиента заказа, дату формирования или итоговую сумму заказа. Более того, список товаров в оформленном заказе — довольно статичная информация. И даже когда он нужен, в части случаев он может браться из кэша.
Чтобы сократить объём ответа и упростить логику сервера при сборе данных, мы можем сделать отдельные запросы для получения информации о клиенте и товарах по номеру заказа:
GET /api/orders/ORD-2024-1234/client
GET /api/orders/ORD-2024-1234/items
Тогда, если необходимо получить полную информацию по заказу, нужно сделать сразу 3 запроса. И если у вашего приложения несколько клиентов, и каждому из них нужно своё представление данных о заказе — недовольными останутся все.
Из-за того, что мобильному приложению нужна итоговая сумма и название первого товара в заказе, ему надо делать отдельный запрос. Веб-клиенту нужны дата и статус заказа, названия товаров и телефон клиента — он будет выдергивать по паре полей из каждого запроса и собирать всё это в одну структуру. Внешнему партнеру всегда нужна полная информация о заказе и он вообще не понимает, зачем ему делать эти лишние запросы.
Проблема с пользователями решается разработкой трёх отдельных API, но это требует много ресурсов.
Кроме этого, большое количество запросов генерирует большую нагрузку на ваше приложение, что в случае сложного и разветвлённого API и большого количества пользователей может привести к проблемам с производительностью и отказоустойчивостью.
Итак, мы можем сказать, что при определённых масштабах и специфике домена компании сталкиваются с такими ограничениями REST-like API, как:
Over-fetching — получение избыточных данных;
Under-fetching — недостаток данных, требующий дополнительных запросов;
Множественные запросы для получения связанных данных.
Далее рассмотрим, какое решение этих проблем предлагает GraphQL.
Принцип работы GraphQL
В основе концепции GraphQL лежит довольно очевидное решение — каждый клиент в каждой конкретной ситуации сам выбирает, какой набор данных из объекта он хочет получить. То есть при запросе клиент напрямую указывает, какие поля из объекта order
в данный момент ему нужны.
Так будет выглядеть схема GraphQL-запроса для мобильного приложения:
query GetOrderForMobile($orderId: ID!) {
order(id: $orderId) {
items {
name
}
totalAmount
currency
}
}
И такой для него придёт ответ:
{
"data": {
"order": {
"items": [
{
"name": "Смартфон iPhone 13"
},
{
"name": "Защитное стекло"
}
],
"totalAmount": "76981.00",
"currency": "RUB"
}
}
}
А вот запрос веб-клиента:
query GetOrderForWeb($orderId: ID!) {
order(id: $orderId) {
id
date
status
client {
phone
}
items {
name
}
}
}
И ответ на него:
{
"data": {
"order": {
"id": "ORD-2024-1234",
"date": "2024-11-02T14:30:00Z",
"status": "PROCESSING",
"client": {
"phone": "+7 (999) 123-45-67"
},
"items": [
{
"name": "Смартфон iPhone 13"
},
{
"name": "Защитное стекло"
}
]
}
}
}
То есть клиент в своём запросе может указать любой набор полей из схемы и в ответе вернутся только эти поля.
Общая схема в нашем случае будет выглядеть так:
query GetOrder($orderId: ID!) {
order(id: $orderId) {
id
date
status
client {
id
name
email
phone
}
items {
id
name
quantity
price
subtotal
}
totalAmount
currency
}
}
Но каждый конкретный запрос будет содержать только тот набор полей из общей схемы, который выберет клиент.
GraphQL-запросы строятся на основе схемы — строгого описания типов данных и связей между ними. Например, Order
, Client
и Item
— это типы, которые определяются в схеме и описывают, какие поля у них есть и какого они типа (например, String
, Int
, Float
, ID
и т.д.). Схему мы подробно рассмотрим в следующей статье цикла.
Все запросы к GraphQL обрабатываются через один эндпоинт, что избавляет от необходимости проектировать и поддерживать множество отдельных маршрутов.
В официальной документации есть «живые» примеры, на которых можно потренироваться писать GraphQL-запросы для получения разных полей одного объекта. Либо это можно сделать в тестовых или открытых GraphQL API.
В следующем разделе разберёмся с базовыми принципами реализации такого подхода в коде.
Реализация GraphQL-запроса
Кратко рассмотрим реализацию в коде запросов, возвращающих только те данные, которые указал клиент.
Для получения значений полей из БД в коде приложения на сервере нужны специальные функции — resolvers
. Эти функции пишут разработчики API на выбранном ими языке программирования. Далее в примерах мы будем использовать JavaScript, но на практике это может быть любой язык, у которого есть библиотека для работы с GraphQL.
Для полей, требующих специальной логики получения данных, создаются отдельные резолверы.
Резолверы:
Выполняются только для запрошенных полей;
Имеют доступ к контексту запроса (авторизация, настройки и т.д.);
Могут быть асинхронными;
Могут извлекать или обновлять данные из REST API, БД или любого другого сервиса.
Рассмотрим, какие резолверы могут быть разработаны в примере с заказом в интернет-магазине:
1. Root resolver получает основные данные заказа. Результатом его выполнения может быть такая структура:
{
id: "12345",
date: "2025-04-28",
status: "SHIPPED",
clientId: "C-789",
itemIds: ["ITEM-001", "ITEM-002"],
totalAmount: "76981.00",
currency: "RUB"
}
2. Вложенные резолверы для объектов, которые мы хотим получать только в том случае, если их запросил клиент.
Например, мы можем сделать вложенные резолверы client
и items
:
client: (parent, args, context, info) => {
return {
id: "C-789",
name: "Иван Петров",
email: "ivan@example.com",
phone: "+7 123 456 7890"
};
},
items: (parent, args, context, info) => {
return [
{
id: "ITEM-001",
name: "Смартфон iPhone 13",
quantity: 1,
price: "75000.00",
subtotal: "75000.00"
},
{
id: "ITEM-002",
name: "Защитное стекло",
quantity: 2,
price: "990.50",
subtotal: "1981.00"
}
];
}
Резолверы принимают следующие аргументы:
parent — весь объект ответа родительского резолвера;
args — параметры поля резолвера: сортировка, фильтрация, пагинация;
context — контекст выполнения запроса, например, данные текущего авторизованного пользователя или доступ к базе данных;
info — информация, специфичная для конкретного поля и относящаяся к текущей операции, используется для сложных запросов.
Вложенные резолверы логично выполнять только в случаях, когда клиент запросил одно из полей, которое не вернулось в ответе корневого резолвера.
То есть для запроса ниже будет вызываться корневой резолвер и вложенный резолвер items
:
query GetOrderForMobile($orderId: ID!) {
order(id: $orderId) {
items {
name
}
totalAmount
currency
}
}
Резолвер client
здесь выполняться не будет, так как ни одно из его полей не было запрошено клиентом. Таким образом, не будет ни избыточных данных, ни лишних запросов в БД.

Если мы знаем, что заказ без имени, телефона или email покупателя запрашивают крайне редко, то мы можем обойтись без отдельного резолвера client
и доставать данные о покупателе сразу в корневом резолвере. Также это будет иметь смысл, если нам выгоднее один раз загрузить все данные: например, если запрос для заказа и так их вытягивает из БД, данные можно кешировать и пр.
В GraphQL значение каждого поля в запросе достается с помощью резолверов, но не каждое поле обязательно обрабатывается своим резолвером.
Фактически работает следующий механизм:
Выполняется резолвер родительского объекта.
Если родительский резолвер уже вернул данные, содержащие значения для дочерних полей, то GraphQL сначала попытается использовать эти значения.
Резолвер для конкретного поля вызывается только если:
Данные для этого поля не были возвращены родительским резолвером;
Для поля явно определён специальный резолвер, который должен выполнить дополнительную логику.
Это называется «механизмом разрешения по умолчанию» (default field resolution) и является важной оптимизацией в GraphQL. То есть при грамотном использовании резолверы оптимизируют нагрузку на БД и не запрашивают ненужные данные.
В следующем разделе мы рассмотрим, как GraphQL работает поверх HTTP.
GraphQL и HTTP
GraphQL не привязан к конкретному транспортному протоколу, но на практике почти всегда используется поверх HTTP.
Как правило, все GraphQL-запросы отправляются с POST и сейчас станет понятно, почему. Так будет выглядеть GraphQL-запрос от веб-клиента, отправленный через HTTP:
POST /graphql HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
Content-Length: 183
{
"query": "query GetOrderForWeb($orderId: ID!) { order(id: $orderId) { id date status client { phone } items { name } } }",
"variables": {
"orderId": "ORD-2024-1234"
}
}
Как можно заметить, все параметры и схема ответа отправляются в теле запроса.
В какой-то момент объём запроса может стать проблемой. Несмотря на возможности GraphQL, делать очень длинные схемы со множеством вложенных массивов не рекомендуется.
Естественно, это не единственные проблемы и сложности, с которыми приходится сталкиваться разработчикам GraphQL API. Ниже коротко обсудим основные ограничения этой технологии.
Ограничения GraphQL
Мы уже рассмотрели ряд преимуществ GraphQL, но у этой технологии есть и свои ограничения, которые важно учитывать при проектировании API:
N+1 проблема
При работе с вложенными данными один корневой запрос может породить множество последовательных обращений к базе данных (по одному на каждый элемент списка). Это может серьёзно повлиять на производительность. Поэтому часто необходимо оптимизировать запросы, например, с помощью специальной библиотеки DataLoader или батчинга.Сложность кеширования
Из-за того, что все GraphQL-запросы отправляются на один эндпоинт, невозможно использовать кеширование на уровне URL, как в REST. Для реализации кеша приходится использовать дополнительные решения: например, прокси, кастомные ключи кеша или клиентские библиотеки вроде Apollo Client с нормализацией.Повышенные риски DoS-атак
GraphQL позволяет делать очень глубокие и широкие запросы, включая фрагменты и рекурсивные структуры. Без ограничения глубины или сложности запроса можно легко перегрузить сервер. Поэтому стоит применять лимиты (depth, complexity) и валидаторы.Отсутствие встроенной авторизации на уровне схемы
GraphQL не управляет доступом к данным по умолчанию. Все проверки прав доступа должны быть реализованы вручную в резолверах, что усложняет поддержку и увеличивает риск ошибки.Трудоёмкость внедрения на бэкенде
Несмотря на удобство для клиента, разработка схем, резолверов, безопасных механизмов и системы логирования требует времени и опыта. Это может быть избыточным для простых API.
Более подробно мы рассмотрим эти ограничения в следующих статьях цикла — о безопасности и о возможностях и ограничениях GraphQL. Пока можно сделать общий вывод: технология предоставляет широкие возможности для гибкой работы с API, но вместе с этим требует значительных усилий на этапе внедрения и последующей поддержки.
Заключение
GraphQL был создан как ответ на ограничения REST-подхода — особенно в условиях, когда количество клиентов растёт, структура данных усложняется, а гибкость становится критически важной. GraphQL позволяет клиенту запрашивать именно те данные, которые ему нужны, избегая избыточности и необходимости делать множество отдельных запросов.
В то же время, подход GraphQL требует осознанного проектирования: схемы, резолверов, безопасности и кеширования. Он даёт разработчику большую гибкость, но требует больше ответственности.
В этой статье мы разобрали основные принципы работы GraphQL, его преимущества и ключевые ограничения. В следующей части мы подробно рассмотрим схему GraphQL: какие бывают типы и операции, как они описываются и как формируют основу всей системы API.
Об авторе

Татьяна Сальникова
Продуктовый архитектор, автор воркшопов
Ведущий системный аналитик
Создавала системы для ритейла, маркетинга, строительства, финансового сектора. Основные направления: проектирование интеграций, API, архитектуры микросервисов