Поля, вложенность и обход дерева запроса
Запрос GraphQL — это дерево полей. Вложенность позволяет за один запрос пройти по связям между типами и собрать ровно нужное поддерево данных.
Один запрос — одно путешествие по графу. То, на что в REST уходило пять кругов по сети, здесь умещается в одну вложенную выборку.
Базовая единица запроса — поле. Скалярное поле (name, age) — лист дерева: запрашиваешь его — получаешь значение. Объектное поле (author, posts) — ветвь: чтобы его запросить, нужно открыть фигурные скобки и указать, какие поля внутри тебе интересны. Так запрос растёт в глубину, повторяя связи схемы.
Вложенная выборка
query {
user(id: "42") { # объект -> открываем {}
name # лист
posts { # объект-список -> снова {}
title # лист
comments { # ещё глубже
text
author { name } # переход обратно к User
}
}
}
}
За один такой запрос клиент получает пользователя, его посты, комментарии к ним и авторов комментариев — всё связное дерево. В REST это была бы целая серия запросов: сходить за пользователем, потом за его постами, потом за комментариями каждого поста, потом за авторами каждого комментария. Эту лесенку кругов по сети GraphQL схлопывает в одну вложенную выборку. Заметь и обратные переходы по графу: comments.author снова приводит нас к типу User — связи в схеме могут вести в обе стороны и образовывать циклы. Именно поэтому говорят, что клиент «путешествует по графу»: он входит через корневое поле и дальше свободно переходит по рёбрам-связям, собирая ровно ту форму данных, которая нужна конкретному экрану.
Как работает обход
Сервер обходит дерево запроса в глубину (depth-first): полностью резолвит одно поле со всеми вложенными, прежде чем перейти к следующему. Значение родителя передаётся вниз детям — именно через него поле posts узнаёт, чьи посты искать.
query
└─ user(id:42) -> резолвер вернул { id:42, name }
├─ name -> "Аня" (лист)
└─ posts -> [ {id:1}, {id:2} ] (передаём вниз user)
├─ title -> "..." (лист)
└─ comments -> [...] (передаём вниз post)
├─ text
└─ author -> name
Под капотом: рекурсивный обход на JS
Смоделируем сервер, который обходит дерево запроса и собирает ответ из вложенных данных. «Запрос» зададим как объект формы, данные — как граф:
const data = {
user: {
name: "Аня",
posts: [
{ title: "Первый код", comments: [{ text: "круто!" }] },
{ title: "Учу GraphQL", comments: [] }
]
}
};
// форма запроса: true = лист, объект = углубиться
const query = {
user: { name: true, posts: { title: true } }
};
function execute(query, source) {
const out = {};
for (const field in query) {
const sub = query[field];
const val = source[field];
if (sub === true) { out[field] = val; }
else if (Array.isArray(val)) {
out[field] = val.map(item => execute(sub, item));
} else {
out[field] = execute(sub, val);
}
}
return out;
}
console.log(JSON.stringify(execute(query, data), null, 2));
Попробуй сам ▶ — добавь в posts выборку comments: { text: true } и увидишь, как ответ углубится. Это и есть суть GraphQL-движка в миниатюре.
Частые ошибки
- Запрос объекта без вложенных полей.
postsбез{ ... }внутри — ошибка валидации: до скаляров надо дойти. - Слишком глубокая вложенность. Связи бывают циклическими (user -> posts -> author -> posts -> ...), и запрос можно вложить очень глубоко. Это риск для сервера — позже разберём depth limiting.
- Думать, что глубокий запрос «бесплатен». Каждое вложенное поле — это вызов резолвера. Глубокое дерево над списками легко рождает проблему N+1.
Best practices
- Запрашивай только те поля, что реально нужны экрану: в этом весь смысл GraphQL, и это снижает нагрузку на сервер.
- Для повторяющихся наборов полей используй фрагменты (о них — отдельный урок), чтобы не дублировать выборку.
- Держи в голове, что вложенность над списками умножает работу сервера — глубокие списки внутри списков особенно дороги.
Итоги
Запрос — это дерево полей: скаляры-листья и объекты-ветви. Вложенность позволяет за один запрос пройти по связям графа и собрать нужное поддерево, заменяя серию REST-запросов. Сервер обходит дерево в глубину, передавая значение родителя детям. Дальше научимся управлять выборкой через аргументы.