GraphQL API in 43 minutes with TypeScript, Prisma and PlanetGraphQL
Tags: graphql, prisma, typescript, planetgraphql
Published: 2022-08-26 07:58:34

## Introduction

If you are reading this article, you are probably familiar with GraphQL and TypeScript (and perhaps [Prisma](https://www.prisma.io/)). However, I doubt that many of you have heard of [PlanetGraphQL](https://github.com/planet-graphql/planet-graphql).

PlanetGraphQL is a library aimed at quickly creating feature-rich and flexible GraphQL APIs when used in combination with TypeScript and Prisma. In this tutorial, I hope to give you an overview of PlanetGraphQL by creating a GraphQL API for a simple TODO app.

I assume readers have a basic understanding of Node.js and TypeScript. I think that there are some of you who have never actually used GraphQL or Prisma, so I will explain these in the tutorial along with PlanetGraphQL.

> The full source code can be found in [this repository](https://github.com/planet-graphql/tutorial)

## Creating a project

> node.js (v16 or higher) must be installed

First, create an empty TypeScript project.

```sh
mkdir tutorial && cd tutorial
npm init --yes
npm install typescript ts-node --save-dev
```

Next, create tsconfig.json and configure as follows:

```sh
touch tsconfig.json
```

```tsconfig.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "lib": ["es2021"],
    "module": "commonjs",
    "target": "es2021",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}
```

Add Prisma and PlanetGraphQL to your dependencies.

```sh
npm install prisma @planet-graphql/core
```

Prepare the Prisma environment with the Prisma initialization command.

```sh
npx prisma init --datasource-provider sqlite
```

This time, we will use sqlite to simplify the explanation and environment construction.
Of course, you can also use PostgreSQL or MySQL.

## Defining the DB schema

In this tutorial, we are going to create an API for a simple TODO application with the following functionality:

- Users can create and edit their own tasks
- Users can list their own tasks
- In addition to the above, admin users can list all users and tasks for each user

This time, let's simply define the User and Task tables.
In Prisma, model definitions are described in the `schema.prisma` file.
Please add the following contents to `prisma/schema.prisma`.

```schema.prisma
// ...

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  tasks     Task[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Task {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  status    String
  dueAt     DateTime 
  user      User     @relation(fields: [userId], references: [id])
  userId    Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
```

## Creation and execution of migration files

Prisma has a DB schema migration feature.
Let's create a migration file with the following command.

```sh
npx prisma migrate dev --name init
```

If the command succeeds, the file `prisma/migrations/${yyyyMMddHHmmss}_init/migration.sql` will be created. This contains the SQL generated by Prisma. At the same time the SQL file is created, it is also applied to the DB, and the User table and Task table are created.

## Adding a generator for PlanetGraphQL

Generator is the feature of Prisma. By using a generator, you can generate the corresponding JSON Schema or TypeScript type definition based on the model definition described in `schema.prisma`.

If you look at `schema.prisma` again, you will notice the following description:

```schema.prisma
generator client {
  provider = "prisma-client-js"
}
```

This is the generator definition for generating the type definitions needed by the `PrismaClient` to call Prisma from TypeScript.
PlanetGraphQL also needs a generator to generate the type definition for TypeScript, the same as PrismaClient. Add the following definition to `schema.prisma`.

```schema.prisma
generator pg {
  provider = "planet-graphql"
}
```

After adding it, execute the following command.
Any generators configured in `schema.prisma` will be re-run to generate the type definitions required by PlanetGraphQL.

```sh
npx prisma generate
```

## Setting up GraphQL API server

Now that the Prisma side is ready, let's implement a GraphQL API server using PlanetGraphQL.
First, convert the Prisma model to a GraphQL object type using PlanetGraphQL. Create an `index.ts` file and fill it as follows.

```sh
touch index.ts
```

```typescript
import { dmmf, getPGBuilder, getPGPrismaConverter, PrismaTypes } from '@planet-graphql/core'

const pg = getPGBuilder<{ Prisma: PrismaTypes }>()
const pgpc = getPGPrismaConverter(pg, dmmf)
const { objects } = pgpc.convertTypes()
```

The `pgpc.convertTypes` method return value `objects` contains User and Task, which are GraphQL object types corresponding to Prisma models. Let's use `objects.User` in it to define a `usersQuery` that returns a list of users. Please add the following contents to index.ts.

```typescript
import { PrismaClient } from '@prisma/client'

// ...

const prisma = new PrismaClient({ log: ['query'] })
const usersQuery = pg.query({
  name: 'users',
  field: (b) =>
    b
      .object(() => objects.User)
      .list()
      .resolve(() => prisma.user.findMany())
})
```

Here we implement the following:

- Get PrismaClient instance with `new PrismaClient()`
- Defines a GraphQL query with the `pg.query` method
- `b.object(() => objects.User)` defines that the return type of UsersQuery is User
- `list()` changes the return type to List type of User
- `resolve(() => prisma.user.findMany())` defines what to do when UsersQuery is actually called. Here, PrismaClient is used to retrieve all user records that exist in the DB.

Let's start up the GraphQL API server now. Since PlanetGraphQL is just a GraphQL Schema Builder, a separate GraphQL Server is required to launch it as an API server. In this tutorial, we will use `graphql-yoga`, the GraphQL Server that is said to be trendy these days. Of course, you can also use other libraries such as `apollo-server`, which is the most popular and well-known.

```sh
npm install @graphql-yoga/node
```

Also add the following to `index.ts`.

```typescript
import { createServer } from '@graphql-yoga/node'

// ...

const server = createServer({
  schema: pg.build([usersQuery]),
  maskedErrors: false,
})
server.start()
```

Let's add the startup script to package.json and run it.

```diff
 {
   "scripts": {
+    "start": "ts-node index.ts",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
 }
```

```sh
npm run start
```

Accessing `http://localhost:4000/graphql` opens an IDE for developers called `graphiql`. Let's call the usersQuery we just implemented on graphiql. Write the following query in the left pane of graphiql and press the execute button.

```graphql
query {
  users {
    id
    name
  }
}
```

Since the DB is empty, the following results would be returned.

```json
{
  "data": {
    "users": []
  }
}
```

## Adding records to DB

If the DB is empty, it is not possible to sufficiently check the operation, so let's create a seed script to put data into the DB. You can execute SQL directly, but since this is a good opportunity, let's create data via PrismaClient. We will install `facker.js` for creating test data.

```sh
npm install @faker-js/faker --save-dev
```

Create a `seed.ts` and add a script that creates users and tasks for each user.

```sh
touch seed.ts
```

```typescript
import { PrismaClient } from '@prisma/client'
import { faker } from '@faker-js/faker'

const prisma = new PrismaClient()

async function clean() {
  await prisma.task.deleteMany()
  await prisma.user.deleteMany()
}

async function createUsersAndTasks(userCount = 100, taskCount = 10) {
  for (let userId of [...Array(userCount).keys()]) {
    const firstName = faker.name.firstName()
    await prisma.user.create({
      data: {
        id: userId,
        name: faker.name.fullName({ firstName }),
        email: faker.internet.email(firstName),
        tasks: {
          create: [...Array(taskCount).keys()].map((taskId) => ({
            id: userId * userCount + taskId,
            title: faker.lorem.sentence(),
            content: faker.lorem.paragraph(),
            status: faker.helpers.arrayElement(['new', 'in_progress', 'done']),
            dueAt: faker.date.future()
          }))
        }
      }
    })
  }
}

async function seed() {
  await clean()
  await createUsersAndTasks()
}

seed()
```

Prisma provides the function to call the seed command set in package.json with `prisma db seed`. Please add the following settings to package.json and execute the command.

```diff
 {
   "scripts": {
     "start": "ts-node index.ts",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
+  "prisma": {
+    "seed": "ts-node seed.ts"
+  },
   "keywords": [],
 }
```

```sh
npx prisma db seed
```

Start up the API server again and run the previous query.
If the seed is successful, you should get a list of users this time.

```sh
npm run start
```

```graphql
query {
  users {
    id
    name
  }
}
```

## Getting Relations

Let's change the previous query so that we can also retrieve tasks for each user at one time.

```graphql
query {
  users {
    id
    name
    tasks {
      id
      title
    }
  }
}
```

When you call the above query, you should get an error `Cannot return null for non-nullable field User.tasks`. Let's change the implementation of usersQuery slightly to eliminate this error.

```diff
 const usersQuery = pg.query({
   name: 'users',
   field: (b) =>
     b
       .object(() => objects.User)
       .list()
-      .resolve(() => prisma.user.findMany())
+      .resolve(() => prisma.user.findMany({
+        include: { tasks: true }
+      }))
 })
```

Restart the server and run the query again, this time you should also get all per-user tasks. By passing `{ include: { tasks: true } }`, the tasks associated with the user are also retrieved from the DB.

We would like to solve all problems with this, but there is a performance problem with always passing `{ include: { tasks: true } }`. This is because tasks are selected from the DB even when they are not needed. Ideally, you should pass `{ include: { tasks: true } }` when you need to retrieve tasks, and not pass it when you don't.

Let's solve this problem using PlanetGraphQL's `prismaArgs`. let's change the implementation of usersQuery a little bit.

```diff
 const usersQuery = pg.query({
   name: 'users',
   field: (b) =>
     b
       .object(() => objects.User)
       .list()
-      .resolve(() => prisma.user.findMany({
-        include: { tasks: true }
-      }))
+      .resolve(({ prismaArgs }) => prisma.user.findMany(prismaArgs))
 })
```

You should still get tasks for each user after the change. The `prismaArgs` received by the `resolve` method callback contains an include clause automatically generated from the GraphQL query.

If your query is like this:

```graphql
query {
  users {
    id
    name
    tasks {
      id
      title
    }
  }
}
```

The value of prismaArgs will be `{ include: { tasks: true } }`. On the other hand, if the tasks field is not specified as follows:

```graphql
query {
  users {
    id
    name
  }
}
```

The value of prismaArgs will be empty.

## Filtering and Sorting

Prisma has filtering and sorting features similar to SQL. For example, if you want to retrieve users whose name contains "e", you can write:

```typescript
const users = await prisma.user.findMany({
  where: {
    name: { contains: "e" }
  }
})
```

If you want to retrieve the 11th through 20th user, you can write:

```typescript
const users = await prisma.user.findMany({
  take: 10,
  skip: 10,
})
```

Let's use PlanetGraphQL to add the offset-based pagination feature to usersQuery. Modify the usersQuery as follows.

```diff
 const usersQuery = pg.query({
   name: 'users',
   field: (b) =>
     b
       .object(() => objects.User)
       .list()
+      .args((b) => ({
+        take: b.int().default(10),
+        skip: b.int().default(0),
+      }))
-      .resolve(({ prismaArgs }) => prisma.user.findMany(prismaArgs))
+      .resolve(({ args, prismaArgs }) => prisma.user.findMany({
+        ...prismaArgs,
+        take: args.take,
+        skip: args.skip,
+      }))
 })
```

After restarting the server, you should be able to call usersQuery passing any `take` and `skip` as follows:

```graphql
query {
  users(take: 10, skip: 10) {
    id
    name
  }
}
```

Let's optimize the previous implementation slightly. Modify it as follows:

```diff
 const usersQuery = pg.query({
   name: 'users',
   field: (b) =>
     b
       .object(() => objects.User)
       .list()
-      .args((b) => ({
+      .prismaArgs((b) => ({
         take: b.int().default(10),
         skip: b.int().default(0),
       }))
-      .resolve(({ args, prismaArgs }) => prisma.user.findMany({
-        ...prismaArgs,
-        take: args.take,
-        skip: args.skip,
-      }))
+      .resolve(({ prismaArgs }) => prisma.user.findMany(prismaArgs))
 })
```

You should be able to call call usersQuery by passing any take and skip as before.

Using the `prismaArgs` method instead of the `args` method simplified the implementation a bit. Whether you use the `args` method or the `prismaArgs` method, the generated GraphQL schema stay s the same. The only difference is whether it is passed as `args` or `prismaArgs` at resolve time.

For values that you want to pass directly to PrismaClient, it may be simpler to use the `prismaArgs` method.

## Introducing PlanetGraphQL ArgBuilder

In the previous example, the args were simple, such as take and skip, so you could define those by yourself. But what if you want to define more complex args? In such a case, the `PGArgBuilder` provided by PlanetGraphQL is very useful.
Try changing the usersQuery as follows and restart the server.

```diff
+ const { args } = pgpc.convertBuilders()

  const usersQuery = pg.query({
    name: 'users',
    field: (b) =>
      b
        .object(() => objects.User)
        .list()
-       .prismaArgs((b) => ({
-        take: b.int().default(10),
-         skip: b.int().default(0),
-       }))
+       .prismaArgs(() => args.findManyUser.build())
        .resolve(({ prismaArgs }) => prisma.user.findMany(prismaArgs))
  })
```

Then a bunch of args will be added to usersQuery. For example, it becomes possible to call usersQuery as follows:

```graphql
query {
  users(
    where: {
      email: { contains: "e" },
      tasks: { some: { status: { equals: "done" } } }
    },
    orderBy: [ { updatedAt: desc }, { id: desc } ],
    take: 10,
  ) {
    id
    name
    tasks {
      status
    }
  }
}
```

By calling the `pgpc.convertBuilders` method, you can receive PGArgBuilders corresponding to PrismaClient's `findManyUser`, `findUniqueTask`, etc. By using PGArgBuilder, it is possible to convert PrismaClient's `findManyUser` or `findUniqueTask` to GraphQL args.

Additionally, PGArgBuilder can adjust its contents using the `edit` method. For example, let's edit as follows:

```diff
 const usersQuery = pg.query({
   name: 'users',
   field: (b) =>
     b
       .object(() => objects.User)
       .list()
       .auth(({ context }) => context.isAdmin, { strict: true })
-      .prismaArgs(() => args.findManyUser.build())
+      .prismaArgs(() => args.findManyUser.edit((f) => ({
+        where: f.where.edit((f) => ({
+          email: f.email.select('StringFilter').edit((f) => ({
+            equals: f.equals,
+            contains: f.contains,
+          })),
+          tasks: f.tasks.edit((f) => ({
+            some: f.some.edit((f) => ({
+              status: f.status
+                .select('String')
+                .validation((schema) => schema.regex(/new|in_progress|done/)),
+            }))
+          })),
+        })),
+        orderBy: f.orderBy,
+        take: f.take.default(10),
+        skip: f.skip.default(0),
+      })).build({ type: true }))
       .resolve(({ prismaArgs }) => prisma.user.findMany(prismaArgs))
```

Here, we have made the following adjustments:

- Only use the part of the args provided in findManyUser that we think is acceptable to expose as GraphQL API
- Validation is set so that values other than `new`, `in_progress`, and `done` are not accepted in the task status filtering conditions
- Set default values for `take` and `skip`

As you can see, you can easily implement sophisticated filtering and sorting functions as a GraphQL API. It is also nice that type completion works and you can fully benefit from TypeScript.

## Customizing Object Types

So far we have used `objects.User` generated from Prisma's schema as is, but let's customize this User. Please add the following contents to index.ts.

```diff
+ const user = pgpc.redefine({
+   name: 'User',
+   fields: (f, b) => ({
+     ...f,
+     taskCount: b.int()
+   }),
+   relations: () => getRelations('User'),
+ })

+ const task = pgpc.redefine({
+   name: 'Task',
+   fields: (f) => {
+     const { user, ...rest } = f
+     return { ...rest }
+   },
+   relations: () => getRelations('Task'),
+ })

+ user.implement((f) => ({
+   taskCount: f.taskCount.resolve((params) => {
+     const user = params.source
+     return prisma.task.count({
+       where: {
+         userId: user.id,
+       }
+     })
+   })
+ }))

- const { objects } = pgpc.convertTypes()
+ const { objects, getRelations } = pgpc.convertTypes({
+   User: () => user,
+   Task: () => task,
+ })
```


This might seem complicated. I will try to explain it one by one.

- Redefining User and Task with `pgpc.redefine` method
    - In User, we added an Int type field `taskCount` to get the number of tasks the user has
    - The `user` field is removed from Task
    - The `relations: () => getRelations('User')` part will be explained later
- Implement the `taskCount` field using the `user.implement` method
    - For fields that do not exist in the DB, it is necessary to set the resolver and implement how to return the value
    - In this case, the count method of PrismaClient is used to get the number of tasks for the target user
- The redefined User and Task are passed to the `pgpc.convertTypes` method in the form of `{ User: () => user, Task () => task }`
    - By doing this, the returned values `objects.User` and `objects.Task` will be the redefined User and Task
    - The `getRelations` method and the `relations` field in the `redefine` method are needed to replace the object type of relations with redefined object types.
    - In this example, it is necessary to replace the Task in the User's `tasks` field with the redefined Task

In summary, the redefine method allows you to add and remove fields from Prisma's schema definition. Please restart the server and make sure you can get taskCount from the usersQuery.

```graphql
query {
  users {
    id
    name
   	taskCount 
  }
}
```

## Resolving the N+1 problem by introducing DataLoader

By adding the taskCount field, we can now get the number of tasks per user. However, there is one problem. The SQL is not optimized and the Select statement to retrieve the number of tasks is being issued for the number of users. This is the so-called N+1 problem.

GraphQL uses a mechanism called DataLoader to solve the N+1 problem, and PlanetGraphQL has built-in DataLoader functionality, so you can easily use dataloader. Modify index.ts as follows:

```diff
 user.implement((f) => ({
   taskCount: f.taskCount.resolve((params) => 
     pg.dataloader(params, async (userList) => {
-    const user = params.source
-    return prisma.task.count({
-      where: {
-        userId: user.id,
-      }
-    })
+    return pg.dataloader(params, async (userList) => {
+      const userIds = userList.map((x) => x.id)
+      const resp = await prisma.task.groupBy({
+        by: ['userId'],
+        _count: { _all: true },
+        where: { userId: { in: userIds } },
+      })
+      return userIds.map((id) => resp.find((x) => x.userId === id)?._count._all ?? 0)
+    })
   )
 }))
```

Try restarting the server and retrieving taskCount from usersQuery. It is clear from the logs that the SQL that was issued for each user has changed to a single SQL as shown below.

```sql
SELECT COUNT(*), `main`.`Task`.`userId` FROM `main`.`Task` WHERE `main`.`Task`.`userId` IN (?,?,?,?,?,?,?,?,?,?) GROUP BY `main`.`Task`.`userId` LIMIT ? OFFSET ?
```

## Introducing Relay

In GraphQL, the most common implementation of pagination is a cursor-based implementation called Relay-style. While cursor-based pagination is easy to use, it can be difficult to implement. However, with the combination of Prisma and PlanetGraphQL, it is very easy to create an API that supports the Relay-style.

Let's try to implement an API to obtain a list of tasks in the form of Relay-style. Please add the following to your index.ts.

```typescript
const tasksQuery = pg.query({
  name: 'tasks',
  field: (b) =>
    b
      .object(() => objects.Task)
      .relay()
      .resolve(({ prismaArgs }) => prisma.task.findMany(prismaArgs))
})
```

Also, add an argument to the `pg.build` method so that the newly defined `tasksQuery` is included in the schema.

```diff
 const server = createServer({
-  schema: pg.build([usersQuery]),
+  schema: pg.build([usersQuery, tasksQuery]),
   maskedErrors: false,
 })
```

This is all that is needed to call tasksQuery in Relay-style.

```graphql
query {
  tasks(first: 10) {
    edges {
      node {
        id
        title
      }
      cursor
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
  }
}
```

You can make a Query or field correspond to Relay-style by simply calling the `relay` method as implemented above. This is because the following processing is implicitly performed in the `relay` method.

- Generate GraphQL object types required for Relay-style
- Set the necessary args (first, last, before, and after) for Relay-style
- Format values retrieved from DB into Relay-style format
- Generate a cursor for each node
- Calculate PageInfo such as hasNextPage and hasPreviousPage

You can also customize the details. By default, tasks are sorted in ascending order by id, but you can change the order using the `relayOrderBy` method.

```diff
 const tasksQuery = pg.query({
   name: 'tasks',
   field: (b) =>
     b
       .object(() => objects.Task)
       .relay()
+      .relayOrderBy({ updatedAt: 'desc' })
       .resolve(({ prismaArgs }) => prisma.task.findMany(prismaArgs))
 })
```

Also, by calling the `relay` method, the `first`, `last`, `before`, and `after` args are set, but the `relayArgs` method can be used to set default values or validations for those.

```diff
 const tasksQuery = pg.query({
   name: 'tasks',
   field: (b) =>
     b
       .object(() => objects.Task)
       .relay()
       .relayOrderBy({ updatedAt: 'desc' })
+      .relayArgs((f) => ({
+        ...f,
+        first: f.first.default(10).validation((schema) => schema.max(100)),
+        last: last: f.last.validation((schema) => schema.max(100)),
+      }))
       .resolve(({ prismaArgs }) => prisma.task.findMany(prismaArgs))
 })
```

In addition, the `relayTotalCount` method can be used to add a field to retrieve the TotalCount.

```diff
 const tasksQuery = pg.query({
   name: 'tasks',
   field: (b) =>
     b
       .object(() => objects.Task)
       .relay()
       .relayOrderBy([{ updatedAt: 'desc' }, { id: 'desc' }])
       .relayArgs((f) => ({
         ...f,
         first: f.first.default(10).validation((schema) => schema.max(100)),
         last: last: f.last.validation((schema) => schema.max(100)),
       }))
+      .relayTotalCount(() => prisma.task.count())
       .resolve(({ prismaArgs }) => prisma.task.findMany(prismaArgs))
 })
```

After restarting the server, you should be able to get the totalCount as follows. You will also see that the default values and validations for `first` and `last` are in effect.

```graphql
query {
  tasks {
    totalCount
    edges {
      node {
        id
      }
    }
  }
}
```

## Context setting and Authorization

Up to this point, we have not mentioned any authorization controls. Revisiting the API requirements listed at the beginning of the tutorial, it appears that the following modifications are necessary for queries we have implemented so far.

- Since tasksQuery returns all tasks, it should return only the calling user's own tasks
- Only the administrator user can get user list, so usersQuery should be called only by the administrator user

To implement authorization control, it is essential to understand a concept called `Context` in GraphQL. The `Context` mainly stores information about the caller of the API. For example, the ID of the user who made the execution, the IP address of the caller, and so on.

We need to make sure that a context is generated with each request, which is the responsibility of the GraphQL Server (GraphQL Yoga or Apollo Server), not PlanetGraphQL (GraphQL Schema Builder). Let's add a process to generate a context for each request in the `createServer` method of GraphQL Yoga.

```diff
 const server = createServer({
   schema: pg.build([usersQuery, tasksQuery]),
   maskedErrors: false,
+  context: ({ req }) => ({
+    userId: Number(req.headers['x-user-id'] ?? 0),
+    isAdmin: Boolean(req.headers['x-is-admin'] ?? false),
+  })
 })
```

Now the `x-user-id` and `x-is-admin` values in the request header will be set in the context and will be available in PlanetGraphQL. Since this is a sample, if there is no value in the header, a fixed value will be set.

Next, to make it easier to handle context in PlanetGraphQL, we pass the context type information to PlanetGraphQL. Modify index.ts as follows.

```diff
+ type TContext = {
+   userId: number
+   isAdmin: boolean
+ }

- const pg = getPGBuilder<{ Prisma: PrismaTypes }>()
+ const pg = getPGBuilder<{ Context: TContext, Prisma: PrismaTypes }>()
```

We are now ready for Context. Let's limit the tasks that can be retrieved by tasksQuery to the caller's own tasks. Modify tasksQuery as follows:

```diff
 const tasksQuery = pg.query({
   name: 'tasks',
   field: (b) =>
     b
       .object(() => objects.Task)
       .relay()
       .relayOrderBy([{ updatedAt: 'desc' }, { id: 'desc' }])
       .relayArgs((f) => ({
         ...f,
         first: f.first.default(10).validation((schema) => schema.max(100)),
         last: f.last.default(10).validation((schema) => schema.max(100)),
       }))
-      .relayTotalCount(() => prisma.task.count())
+      .relayTotalCount(({ context }) => prisma.task.count({
+        where: { userId: context.userId },
+      }))
-      .resolve(({ prismaArgs }) => prisma.task.findMany(prismaArgs))
+      .resolve(({ context, prismaArgs }) => prisma.task.findMany({
+        ...prismaArgs,
+        where: { userId: context.userId },
+      }))
 })
```

We used `userId` in the context to limit the data to be retrieved only to tasks associated with the calling user.

Next, let's modify usersQuery so that it can be called only by the administrator user. Modify usersQuery as follows:

```diff
 const usersQuery = pg.query({
   name: 'users',
   field: (b) =>
     b
       .object(() => objects.User)
       .list()
+      .auth(({ context }) => context.isAdmin, { strict: true })
       .prismaArgs(() => args.findManyUser.edit((f) => ({
 })
```

You can control permissions on a field-by-field basis by returning a boolean of the authorization status in the `auth` method. Restart the server and then call usersQuery. An error should be returned unless you add `{ "x-is-admin": true }` to the request header.

Finally, remove the `{ strict: true }` to better understand the behavior of the `auth` method.

```diff
 const usersQuery = pg.query({
   name: 'users',
   field: (b) =>
     b
       .object(() => objects.User)
       .list()
-      .auth(({ context }) => context.isAdmin, { strict: true })
+      .auth(({ context }) => context.isAdmin)
       .prismaArgs(() => args.findManyUser.edit((f) => ({
 })
```

Restart the server and this time an empty array should be returned instead of an error. The default behavior of the `auth` method is to return a response if there are values that can be returned without authorization. Specifically, the behavior is as follows:

- If the return value is List type, return an empty array if there is no authorization
- If the return value is nullable, return null if there is no authorization
- otherwise an error occurs

## Mutation implementation and copy

Finally, let's implement mutations. The first is a mutation that creates a user.
Please add the following to index.ts.

```typescript
const taskEnum = pg.enum({
  name: 'TaskEnum',
  values: ['new', 'in_progress', 'done']
})

const createTaskInput = pg.input({
  name: 'CreateTaskInput',
  fields: (b) => ({
    title: b.string().validation((schema) => schema.max(100)),
    content: b.string().nullable(),
    status: b.enum(taskEnum),
    dueAt: b.dateTime(),
  })
}).validation((value) => value.title.length > 0 || value.status !== 'new')

const createTaskMutation = pg.mutation({
  name: 'createTask',
  field: (b) =>
    b
      .object(() => task)
      .args((b) => ({
        input: b.input(() => createTaskInput)
      }))
      .resolve(({ context, args }) => prisma.task.create({
        data: {
          ...args.input,
          userId: context.userId
        }
      }))
})
```

Don't forget to add the newly defined `createTaskMutation` to the arguments of the `pg.build` method.

```diff
 const server = createServer({
-  schema: pg.build([usersQuery, tasksQuery]),
+  schema: pg.build([usersQuery, tasksQuery, createTaskMutation]),
   maskedErrors: false,
 })
```

Here, we use `pg.input` to define the Input type of GraphQL without using PGArgBuilder. We also use `pg.enum` to define the Enum type of GraphQL so that we can specify the task status with this enum.

Let's call `createTask` with the following query. A new task associated with the calling user should be created and returned.

```graphql
mutation {
  createTask(input: {
    title: "some title",
    content: null,
    dueAt: "2030-01-01T12:00:00Z",
    status: new
  }) {
    id
    title
    content
    dueAt
    status
  }
}
```

Let's also create a Mutation for updating the task. Add the following to index.ts.

```typescript
const updateTaskInput = createTaskInput.copy({
  name: 'UpdateTaskInput',
  fields: (f, b) => ({
    ...f,
    id: b.int(),
  })
})

const updateTaskMutation = pg.mutation({
  name: 'updateTask',
  field: (b) =>
    b
      .object(() => task)
      .args((b) => ({
        input: b.input(() => updateTaskInput)
      })) 
      .resolve(async ({ context, args}) => {
        await prisma.task.findFirstOrThrow({
          where: {
            id: args.input.id,
            userId: context.userId,
          }
        })
        return prisma.task.update({
          where: {
            id: args.input.id,
          },
          data: args.input
        })
      })
})
```

Remember to add `updateTaskMutation` as an argument to the `pg.build` method.

```diff
 const server = createServer({
-  schema: pg.build([usersQuery, tasksQuery, createTaskMutation]),
+  schema: pg.build([usersQuery, tasksQuery, createTaskMutation, updateTaskMutation]),
   maskedErrors: false,
 })
```

The `copy` method of `CreateTaskInput` is used to create an `UpdateTaskInput` for updating. Since the update requires the ID of the target task, we add an `id` field to receive the ID. Restart the server and make sure that `updateTask` can be called.

```graphql
mutation {
  updateTask(input: {
    id: 0,
    title: "some updated title",
    content: null,
    dueAt: "2030-01-01T12:00:00Z",
    status: new
  }) {
    id
    title
    content
    dueAt
    status
  }
}
```

## File structure

So far, all implementations have been written in index.ts, but in actual applications, it is necessary to consider a more manageable file structure. Here we introduce a recommended file structure as PlanetGraphQL.

- `src/models/*.ts`: Define each model
- `src/resolvers/*.ts`: Define field resolvers for each model, and Query, Mutation, Subscription associated with each model
- `src/builder.ts`: Initialize PlanetGraphQL
- `src/server.ts`: Initialize and start the GraphQL server

Please check [this repository](https://github.com/planet-graphql/tutorial) for the actual source file after the file structure changes.

## In conclusion

We hope that you found it simple to implement GraphQL APIs using PlanetGraphQL and Prisma.
PlanetGraphQL is a young project. If you notice anything that could be improved, please submit an issue on Github! Have a nice GraphQL life!