Единый эндпоинт и анатомия запроса
Весь GraphQL общается через один URL и понимает три типа операций — query, mutation, subscription. А любой ответ всегда укладывается в два ключа: data и errors.
Один адрес, три глагола, предсказуемый конверт ответа — выучив эту анатомию, ты перестанешь путаться в любом GraphQL API.
В REST у тебя десятки URL и набор HTTP-методов: GET, POST, PUT, DELETE. В GraphQL вместо этого один эндпоинт (по соглашению /graphql) и три операции уровня языка:
- query — чтение данных (аналог GET);
- mutation — изменение данных: создать, обновить, удалить (аналог POST/PUT/DELETE);
- subscription — подписка на поток событий в реальном времени (обычно поверх WebSocket).
Технически запросы и мутации почти всегда отправляются HTTP-методом POST с телом, где лежит текст операции. Сам HTTP-метод тут не несёт смысла — смысл несёт ключевое слово операции в теле. Это важный сдвиг мышления: в REST «глагол» действия — это HTTP-метод (GET, POST, PUT), и маршрутизатор сервера смотрит именно на него вместе с путём. В GraphQL транспорт (HTTP) и семантика операции разведены: транспорт почти всегда один и тот же POST, а что именно делать — читать, писать или подписываться — определяет первое слово в теле запроса. Поэтому переходя с REST на GraphQL, перестань искать «правильный URL и метод» под каждое действие: и поиск пользователя, и его создание уходят на один и тот же адрес, отличаясь лишь словом query или mutation.
Разбираем запрос по частям
query GetUser($id: ID!) { # тип операции, имя, переменные
user(id: $id) { # поле с аргументом
name # скалярное поле
posts(first: 3) { # вложенное поле + аргумент
title
likes
}
}
}
Здесь: query — тип операции; GetUser — имя (необязательно, но помогает в отладке и логах); $id: ID! — объявление переменной; user, posts — поля; в скобках — аргументы; name, title — листья дерева (скаляры). Запрос — это всегда дерево полей.
Конверт ответа
Каким бы ни был запрос, ответ — это JSON ровно с двумя возможными ключами верхнего уровня: data (то, что удалось получить) и errors (массив ошибок, если что-то пошло не так). Важная особенность: ответ может содержать и data, и errors одновременно — часть полей вернулась, часть упала.
{
"data": { "user": { "name": "Аня", "posts": null } },
"errors": [
{ "message": "Нет доступа к постам", "path": ["user", "posts"] }
]
}
Как работает под капотом
Сервер получает тело запроса, выбирает корневой тип по ключевому слову операции (Query, Mutation или Subscription), а дальше обходит дерево полей. Каждое поле резолвится своей функцией, результат складывается в data по тому же пути, что и в запросе. Ошибка в конкретном поле не роняет весь ответ — она пишется в errors с указанием path, а на месте поля оказывается null.
Смоделируем разбор простого запроса в JS: распарсим список запрошенных полей и соберём ответ-дерево.
const root = {
user: { name: "Аня", city: "Казань", secret: "пароль123" }
};
// "запрос": какие поля user мы хотим
const ask = { user: ["name", "city"] };
function resolve(ask, root) {
const data = {};
for (const key in ask) {
const obj = root[key];
data[key] = {};
for (const f of ask[key]) data[key][f] = obj[f];
}
return { data };
}
console.log(JSON.stringify(resolve(ask, root), null, 2));
// secret в ответ не попал — мы его не просили
Попробуй сам ▶ — добавь "secret" в массив полей и убедись, что теперь он приедет. Клиент управляет тем, что видит.
Частые ошибки
- Искать «другой URL» под мутацию. Эндпоинт тот же — меняется лишь ключевое слово операции.
- Ждать HTTP-статус 404 или 500 при ошибке. GraphQL почти всегда отвечает 200 OK, а ошибки кладёт в массив
errors. Это сбивает с толку тех, кто привык к REST-статусам. - Запрашивать объектное поле без вложенных полей. Нельзя написать просто
user— у объекта надо указать, какие именно поля внутри тебе нужны.
Best practices
- Всегда давай операциям имена (
query GetUser): это улучшает логи, метрики и трассировку на сервере. - Проверяй оба ключа ответа — и
data, иerrors. Частичный успех — норма для GraphQL. - Выноси значения в переменные (
$id), а не зашивай в текст запроса: так запрос становится переиспользуемым и кэшируемым.
Итоги
GraphQL — это один эндпоинт, три операции (query, mutation, subscription) и предсказуемый конверт ответа из ключей data и errors. Запрос всегда дерево полей с аргументами; ошибки не роняют весь ответ, а помечают конкретный путь. Эту анатомию мы будем наполнять смыслом в следующих разделах — начиная со схемы и типов.