В прошлом уроке данные лежали в массивах и пропадали при перезапуске сервера. Теперь подключим настоящую базу — PostgreSQL через Prisma — и заодно разберёмся, как GraphQL вызывает резолверы. Представьте конвейер: на каждой станции рабочий получает деталь от предыдущей. Первый собирает список постов, следующие дорабатывают каждый пост — добавляют автора, комментарии. То, что передала предыдущая станция, и есть parent.

Сигнатура резолвера

Любой резолвер — функция (parent, args, contextValue, info):

ПараметрЧто это
parentто, что вернул резолвер родительского поля (в Post.author — объект поста)
argsаргументы поля из запроса ({ id: '1' } для user(id: "1"))
contextValueобщий объект запроса из функции context (например, { prisma })
infoслужебные данные: имя поля, путь, схема; нужен редко

Резолвер возвращает значение, null или Promise. Если вернулся объект или массив объектов, Apollo запускает резолверы их вложенных полей — и так до самых простых значений.

Дерево резолверов

Запрос выполняется как дерево. Разберём { posts { title author { name } } }:

Дерево резолверов: Query.posts, затем Post.title и Post.author для каждого поста, затем User.name
Дерево резолверов: Query.posts, затем Post.title и Post.author для каждого поста, затем User.name
  • Query.posts вызывается один раз и возвращает список постов.
  • Для каждого поста вызываются Post.title и Post.author: для 10 постов Post.author сработает 10 раз — запомните это для урока про N+1.
  • Для каждого автора вызывается User.name; его parent — пользователь, которого вернул Post.author.
  • Дочерний резолвер ждёт родителя, а поля одного уровня Apollo запускает независимо друг от друга.

Резолверы по умолчанию

Резолвер для каждого поля не нужен. Если его нет, GraphQL берёт у parent свойство с именем поля, а если это функция — вызывает её. Поэтому Post.title, Post.body и User.name работают сами: такие свойства уже есть в строке из базы. Число Post.id превратит в строку "1" сам скаляр ID. Свой резолвер нужен в трёх случаях:

  • корневые поля (Query.*): у них нет родителя, данные берём из базы;
  • связи (Post.author, User.posts, Post.comments): в строке только authorId или вовсе ничего;
  • поля с другим форматом (Post.createdAt): в базе Date, а в схеме String.
Схема GraphQL и база данных: какие поля работают сами, а каким нужен резолвер
Схема GraphQL и база данных: какие поля работают сами, а каким нужен резолвер

Prisma в context

Prisma ORM 7 вы настраивали в разделе про базы данных. Создайте новую базу blog_graphql в том же контейнере PostgreSQL и инициализируйте Prisma:

bash
npm install @prisma/client@7 @prisma/adapter-pg@7 pg
npm install -D prisma@7 dotenv
npx prisma init --datasource-provider postgresql --output ../src/generated/prisma --no-skills

Опишите в prisma/schema.prisma модели — те же сущности, что и в схеме GraphQL (атрибуты @map для краткости опущены), — а затем выполните npx prisma migrate dev --name init и npx prisma generate:

prisma
model User {
  id       Int       @id @default(autoincrement())
  name     String
  email    String    @unique
  posts    Post[]
  comments Comment[]
}