Что такое резолвер: четыре аргумента
Резолвер — это функция, которая знает, где взять данные для одного конкретного поля. У неё четыре аргумента: parent, args, context и info.
Схема говорит «что можно спросить», резолверы говорят «где это взять». Без резолверов схема — красивый, но мёртвый контракт.
Схема описывает форму данных, но сама по себе ничего не возвращает. За каждое поле отвечает резолвер — обычная функция. Сервер обходит дерево запроса и для каждого поля вызывает его резолвер. Сигнатура одинакова для всех полей и принимает до четырёх позиционных аргументов:
resolver(parent, args, context, info)
| Аргумент | Что это |
|---|---|
parent | Результат резолвера родительского поля (для вложенных полей) |
args | Аргументы, переданные этому полю в запросе |
context | Общий объект на весь запрос: текущий пользователь, БД, загрузчики |
info | Метаданные о самом запросе (путь, какие поля запрошены) |
Карта резолверов
Резолверы группируют по типам, повторяя структуру схемы. Серверный код (например на Apollo) выглядит так:
const resolvers = {
Query: {
user: (parent, args, ctx) => ctx.db.findUser(args.id),
},
User: {
// parent здесь — это объект user из резолвера выше
fullName: (parent) => parent.firstName + " " + parent.lastName,
posts: (parent, args, ctx) => ctx.db.postsByAuthor(parent.id),
},
};
Заметь: чтобы зарезолвить user.posts, движок берёт parent (объект пользователя) и через его id ищет посты. Так данные «текут» сверху вниз по дереву. Эта структура неслучайна — карта резолверов повторяет карту схемы: для каждого типа из схемы есть свой объект в resolvers, а внутри — функции под имена полей. Благодаря такому соответствию по любому полю схемы легко найти, где оно резолвится, и наоборот. И ещё одна важная мысль: резолвер поля posts совершенно не обязан знать, как был получен сам пользователь. Он принимает готовый parent и делает свою маленькую работу. Эта изоляция — то, что позволяет собирать сложные графы данных из множества крошечных независимых функций.
Как работает под капотом
Если для поля нет явного резолвера, GraphQL применяет дефолтный резолвер: просто берёт у parent свойство с тем же именем (parent.name для поля name). Поэтому скалярные поля часто вообще не нужно писать вручную. Смоделируем движок с дефолтным резолвером и кастомными:
const resolvers = {
Query: { user: (_p, args, ctx) => ctx.db[args.id] },
User: {
// кастомный резолвер: вычисляемое поле
fullName: (parent) => parent.first + " " + parent.last
}
};
const ctx = { db: { "42": { id: 42, first: "Аня", last: "К." } } };
function resolveField(typeName, field, parent, args) {
const custom = resolvers[typeName] && resolvers[typeName][field];
if (custom) return custom(parent, args, ctx);
return parent[field]; // дефолтный резолвер
}
const user = resolveField("Query", "user", null, { id: "42" });
console.log(resolveField("User", "first", user, {})); // "Аня" (дефолт)
console.log(resolveField("User", "fullName", user, {})); // "Аня К." (кастом)
Попробуй сам ▶ — добавь в User резолвер initials: p => p.first[0] + p.last[0] и вызови его. Так появляются вычисляемые поля, которых нет в исходных данных.
Частые ошибки
- Класть бизнес-логику прямо в резолвер. Резолвер должен быть тонким: достать данные и вернуть. Сложную логику выноси в сервисный слой.
- Игнорировать parent. Для вложенных полей именно
parentподсказывает, чьи именно данные брать (посты этого пользователя). - Складывать состояние между запросами. Резолверы должны быть без общего изменяемого состояния; всё «на запрос» живёт в
context.
Best practices
- Группируй резолверы по типам, повторяя схему — так их легко находить и сопоставлять с контрактом.
- Полагайся на дефолтные резолверы для простых скаляров; пиши вручную только связи и вычисляемые поля.
- Держи резолверы тонкими: достать данные через сервис/загрузчик из
contextи вернуть — никакой тяжёлой логики внутри.
Итоги
Резолвер — функция, поставляющая данные для одного поля, с аргументами parent, args, context и info. Они группируются по типам и образуют карту резолверов; скалярные поля часто покрывает дефолтный резолвер. Данные текут по дереву через parent. Дальше посмотрим, как собрать полноценный сервер на Apollo Server 4.