In the last lesson the data lived in arrays and vanished when the server restarted. Now we connect a real database, PostgreSQL through Prisma, and along the way work out how GraphQL calls resolvers. Picture an assembly line: each worker receives a part from the previous station. The first builds the list of posts, the next ones finish each post with its author and comments. What the previous station passes on is the parent.

The resolver signature

Every resolver is a function (parent, args, contextValue, info):

ParameterWhat it is
parentwhat the parent field's resolver returned (in Post.author it is the post object)
argsthe field's arguments from the query ({ id: '1' } for user(id: "1"))
contextValuethe request's shared object from the context function (for example, { prisma })
infotechnical data: field name, path, schema; rarely needed

A resolver returns a value, null or a Promise. If an object or an array of objects comes back, Apollo runs the resolvers of their nested fields, and so on down to the simplest values.

The resolver tree

A query is executed as a tree. Let us look at { posts { title author { name } } }:

The resolver tree: Query.posts, then Post.title and Post.author for each post, then User.name
The resolver tree: Query.posts, then Post.title and Post.author for each post, then User.name
  • Query.posts is called once and returns the list of posts.
  • Post.title and Post.author are called for each post: with 10 posts Post.author runs 10 times. Remember this for the N+1 lesson.
  • User.name is called for each author; its parent is the user that Post.author returned.
  • A child resolver waits for its parent, while Apollo starts the fields of one level independently of each other.

Default resolvers

You do not need a resolver for every field. If there is none, GraphQL takes the property named like the field from parent, and if it is a function, calls it. That is why Post.title, Post.body and User.name work on their own: such properties are already in the row from the database. The ID scalar itself turns the number Post.id into the string "1". You need your own resolver in three cases:

  • root fields (Query.*): they have no parent, so we take the data from the database;
  • relations (Post.author, User.posts, Post.comments): the row has only authorId, or nothing at all;
  • fields in another format (Post.createdAt): the database has a Date, the schema has a String.
The GraphQL schema and the database: which fields work on their own and which need a resolver
The GraphQL schema and the database: which fields work on their own and which need a resolver

Prisma in context

You set up Prisma ORM 7 in the databases section. Create a new database blog_graphql in the same PostgreSQL container and initialise 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

Describe the models in prisma/schema.prisma, the same entities as in the GraphQL schema (without @map, for brevity), and then run npx prisma migrate dev --name init and npx prisma generate:

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