Представьте экран профиля в мобильном приложении: имя пользователя, два последних поста и число подписчиков. Простая задача — но если API построен по REST, приложению придётся сделать три запроса к трём разным адресам, и каждый вернёт больше полей, чем нужно экрану. В этом уроке разберём, какую проблему решает GraphQL, как он устроен и когда его стоит (и не стоит) выбирать.
Проблема: один экран — много запросов
В REST каждый ресурс живёт по своему адресу. Чтобы собрать экран профиля, клиент делает так:
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. Но экранов становится больше, у веба и мобильного приложения разные потребности — и бэкенд превращается в зоопарк почти одинаковых маршрутов.
Что такое GraphQL
GraphQL — это язык запросов к API и среда выполнения этих запросов на сервере. Его создали в Facebook в 2012 году для мобильных приложений, в 2015 году открыли для всех, а сейчас развитием языка занимается независимая GraphQL Foundation.
Главная идея: клиент сам описывает форму нужных ему данных, а сервер возвращает ровно это — не больше и не меньше. Все запросы идут на один адрес (endpoint), обычно /graphql.
Тот же экран профиля — одним запросом:
query ProfileScreen {
user(id: 42) {
name
posts(last: 2) {
title
}
followersCount
}
}И ответ сервера:
{
"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):
type User {
id: ID!
name: String!
posts(last: Int): [Post!]!
followersCount: Int!
}
type Post {
id: ID!
title: String!
}
type Query {
user(id: ID!): User
}Схема даёт три важных преимущества:
- Строгая типизация. Сервер заранее знает, какие поля существуют и какого они типа. Запрос с несуществующим полем отклоняется ещё до выполнения.
- Самодокументируемость. Схему можно запросить у самого сервера (это называется introspection), поэтому инструменты вроде Apollo Sandbox показывают документацию и автодополнение.
- Контракт. Фронтенд и бэкенд договариваются о схеме и дальше работают параллельно: фронтенд может писать запросы, пока бэкенд ещё реализует их.
Знак ! означает «поле никогда не бывает null». Все типы подробно разберём в следующем уроке.
Как сервер выполняет запрос
Каждый запрос к серверу GraphQL проходит одни и те же четыре этапа:
- Parse (разбор) — текст запроса превращается в синтаксическое дерево. Забытая скобка — ошибка уже на этом шаге.
- Validate (проверка) — запрос сверяется со схемой: существуют ли поля, правильного ли типа аргументы.
- Execute (выполнение) — для каждого запрошенного поля вызывается функция-резолвер, которая достаёт данные: из базы, другого API или кеша.
- Respond (ответ) — результаты собираются в JSON той же формы, что и запрос:
{ "data": …, "errors": … }.
Технически запрос GraphQL — обычный HTTP POST с JSON-телом. Его можно отправить даже из терминала:
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 — рядом с теми данными, которые удалось получить:
{
"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:
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: сравнение
| Критерий | REST | GraphQL |
|---|---|---|
| Адреса | много эндпоинтов (/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.