Next.js + GrapqhQLでTodoアプリを構築する(中編)

# はじめに

最近GraphQLについて学びまして、
Next.js + GraphQL + GraphQL CodeGenerator + Prismaの構成でTodoアプリを構築したので
軽〜く解説をしつつ、その記録をここに残します。

![スクリーンショット 2023-04-15 20.58.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/d9c686d4-ca7f-fec9-a91c-1b050cfaafff.png)

# 環境

- Macbook Air
- node
    - v18.13.0
- pnpm
    - 7.27.0

# 目次

- [前編](https://qiita.com/maaaashi/items/fe52db19759ea8a0be31)
    1. Next Create App
    1. GraphQLサーバー構築
    1. Subscription
    1. DB・Prisma
- [中編](https://qiita.com/maaaashi/items/722d75cf8bd25efded88) 👈 今ここ
    1. GraphQL Schema
    1. GraphQL Context
    1. GraphQL Code Generator
    1. GraphQL Resolver
    1. GraphQLサーバー修正
- [後編](https://qiita.com/maaaashi/items/5c63398a34ac3ea8ddad)
    1. フロント側準備
    1. フロント側実装

# 中編

中編はGraphQLフェーズです

## 1. GraphQL Schema

```bash
.
├── src
│   ├── graphql
│   │   ├── typeDefs
│   │   │   ├─ common.graphql
│   │   │   └─ todo.graphql
│   │   │
```

このようなファイル構成で作成します。
`common.graphql`には今後schemaが増えてきた時のために、共通のschemaを定義しようと思います。

- schema
    - Query
        - ListTodos: Todo一覧を取得する
    - Mutation
        - addTodo: contentを元にTodoを作成する
        - updateTodo: idとdoneから、todoの状態を更新する
        - deleteTodo: idから、todoを削除する

- create file
    
    ```bash
    $ mkdir src/graphql src/graphql/typeDefs
    $ touch src/graphql/typeDefs/todo.graphql src/graphql/typeDefs/common.graphql
    ```
- edit file

    ```graphql: todo.graphql
    type Todo {
      id: ID!
      content: String!
      done: Boolean!
      createdAt: DateTime
    }
    
    type Query {
      listTodos: [Todo!]!
    }
    
    type Mutation {
      addTodo(content: String!): Todo!
      updateTodo(id: ID!, done: Boolean!): Todo!
      deleteTodo(id: ID!): Todo!
    }
    
    fragment TodoFragment on Todo {
      id
      content
      done
      createdAt
    }
    
    query ListTodos {
      listTodos {
        ...TodoFragment
      }
    }
    
    mutation AddTodo($content: String!) {
      addTodo(content: $content) {
        ...TodoFragment
      }
    }
    
    mutation UpdateTodo($id: ID!, $done: Boolean!) {
      updateTodo(id: $id, done: $done) {
        ...TodoFragment
      }
    }
    
    mutation DeleteTodo($id: ID!) {
      deleteTodo(id: $id) {
        ...TodoFragment
      }
    }
    ```
    
    ```graphql: common.graphql
    scalar DateTime
    ```

## 2. GraphQL Contextの設定

```bash
.
├── src
│   ├── graphql
│   │   ├── context
│   │   │   ├── index.ts
```

- create file
    
    ```bash
    $ mkdir src/graphql/context
    $ touch src/graphql/context/index.ts
    ```

- edit file
    
    ```typescript: src/graphql/context/index.ts
    import { PrismaClient } from "@prisma/client";
    
    const prisma = new PrismaClient();
    
    export type Context = {
      prisma: typeof prisma;
    };
    
    export const createContext = () => {
      return {
        prisma: prisma,
      };
    };
    ```

## 3. GraphQL Code Generator

https://the-guild.dev/graphql/codegen

```bash
.
├── src
│   ├── graphql
|   |   ├── typeDefs
|   |   |   ├── common.graphql
|   |   |   └── todo.graphql
│   │   ├── context
│   │   │   ├── index.ts
```

:::note info
initのコマンドも用意されていますが、pluginsを入れたりしたいので、マニュアルセットアップしていきます
:::

https://the-guild.dev/graphql/codegen/plugins


- install
    
    ```
    # dependencies
    $ pnpm add graphql @graphql-tools/graphql-file-loader @graphql-tools/load @graphql-tools/schema
    
    # devDependencies
    pnpm add -D @graphql-codegen/cli @graphql-codegen/schema-ast @graphql-codegen/typescript @graphql-codegen/typescript-resolvers @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo ts-node
    ```

- create codegen.yml    
    
    ```bash
    $ touch codegen.yml
    ```
    
    ```yaml: codegen.yml
    generates:
      src/generated/schema.graphql:
        schema:
          - "src/graphql/typeDefs/**/*.graphql"
        plugins:
          - schema-ast
      src/generated/resolvers-types.ts:
        schema:
          - "src/graphql/typeDefs/**/*.graphql"
        plugins:
          - typescript
          - typescript-resolvers
        config:
          contextType: "@/graphql/context/#Context"
          mapperTypeSuffix: Model
          mappers:
            Todo: "@prisma/client#Todo"
          scalars:
            DateTime: string
      src/generated/request.ts:
        schema:
          - "src/graphql/typeDefs/**/*.graphql"
        documents:
          - "src/graphql/typeDefs/**/*.graphql"
        plugins:
          - typescript
          - typescript-operations
          - typescript-react-apollo
        config:
          scalars:
            DateTime: string
    
    ```

- run graphql-codegen
    
    ```bash
    $ pnpm graphql-codegen
    ✔ Parse Configuration
    ✔ Generate outputs
    ```

    実行すると以下のような構成でresolver用の型定義ファイルなどが生成されます
    
    ```bash
    .
    ├── src
    │   ├── generated
    │   │   ├── request.ts
    │   │   ├── resolvers-type.ts
    │   │   └── schema.graphql
    ```

# 4. GraphQL Resolver

```bash
.
├── src
│   ├── graphql
│   │   ├── resolvers
│   │   │   └─ index.ts
│   │   │
```

- create file

    ```bash
    $ mkdir src/graphql/resolvers
    $ touch src/graphql/resolvers/index.ts
    ```

- edit file
    
    **3. GraphQL Code Generator**で作成した`resolvers-types`を利用してresolverを設定していきます。
    
    ```typescript:  src/graphql/resolvers/index.ts
    import { Resolvers } from "@/generated/resolvers-types";
    
    export const resolvers: Resolvers = {
      Query: {
        listTodos: async (_parent, _args, { prisma }) => {
          return await prisma.todo.findMany();
        },
      },
      Mutation: {
        addTodo: async (_parent, { content }, { prisma }) => {
          return await prisma.todo.create({
            data: { content, createdAt: new Date().toISOString() },
          });
        },
        updateTodo: async (_parent, { id, done }, { prisma }) => {
          return await prisma.todo.update({
            where: { id },
            data: { done },
          });
        },
        deleteTodo: async (_parent, { id }, { prisma }) => {
          return await prisma.todo.delete({
            where: { id },
          });
        },
      },
    };
    ```

::: note info
### GraphQL Code Generatorを使うメリット

1. 型定義がされているので、このように補完を利用することができます。
    
    ![スクリーンショット 2023-04-16 10.29.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/edd1b1dc-663b-867c-4184-7c52719e1747.png)

2. 型安全性

    試しにエラーを発生させてみましょう。
    
    Todoを返すはずの`addTodo`で、文字列を返すとこのようにエラーが表示されます。
    ![スクリーンショット 2023-04-16 10.31.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/324f73bf-72ad-4d7f-b03d-8aa327653a87.png)
    
    予定されていない引数を利用しようとすると、このようにエラーが表示されます。
    ![スクリーンショット 2023-04-16 10.32.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/37c1b9b8-aecb-6824-e0f9-ed6a1e3f8aa9.png)

`GraphQL Code Generator`を使わなくても実装はできますが、使う事でより安全にコーディングが進められますし、何か元schemaファイルを修正した際に、影響範囲を型で考えることができます。
:::

## 5. GraphQLサーバー修正

**3. GraphQL Code Generator**及び、**4. GraphQL Resolver**で作成したファイルをインポートしていきます。

```typescript: api/graphql.ts
import { createYoga, createSchema } from "graphql-yoga";
import { readFileSync } from "fs";
import { join } from "path";
import { resolvers } from "@/graphql/resolvers";
import { createContext } from "@/graphql/context";

const path = join(process.cwd(), "src", "generated", "schema.graphql");
const typeDefs = readFileSync(path).toString("utf-8");

const schema = createSchema({
  typeDefs,
  resolvers,
})

const graphqlEndpoint = "/api/graphql";

export default createYoga({
  graphqlEndpoint,
  schema,
  context: createContext,
});
```

[localhost:3000/api/graphql](http://localhost:3000/api/graphql)にアクセスし、Queryを叩いてみましょう。

1. addTodo
    
    ![スクリーンショット 2023-04-15 0.51.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/53308c85-5e89-a283-4e78-06bf0e5e9b6a.png)

2. listTodo
    
    ![スクリーンショット 2023-04-15 0.52.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/8d2d04d6-a4da-7236-6dd8-207f6fef147a.png)

3. deleteTodo
    
    ![スクリーンショット 2023-04-15 0.52.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/f510d440-5201-5ee6-19b3-98af1402733e.png)

4. listTodo
    
    ![スクリーンショット 2023-04-15 0.53.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/4aff7c23-c958-5ea9-e47a-99fca054afd0.png)

動いてそうですね👌
これでGrapQLサーバーの設定が完了です。

# 次回

次はフロント側の設定をしていきます。

https://qiita.com/maaaashi/items/5c63398a34ac3ea8ddad

- 後編
    1. フロント側準備
    1. フロント側実装