7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-04-16

はじめに

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

スクリーンショット 2023-04-15 20.58.42.png

環境

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

目次

  • 前編
    1. Next Create App
    2. GraphQLサーバー構築
    3. Subscription
    4. DB・Prisma
  • 中編 👈 今ここ
    1. GraphQL Schema
    2. GraphQL Context
    3. GraphQL Code Generator
    4. GraphQL Resolver
    5. GraphQLサーバー修正
  • 後編
    1. フロント側準備
    2. フロント側実装

中編

中編はGraphQLフェーズです

1. GraphQL Schema

.
├── 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

    $ mkdir src/graphql src/graphql/typeDefs
    $ touch src/graphql/typeDefs/todo.graphql src/graphql/typeDefs/common.graphql
    
  • edit file

    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
      }
    }
    
    common.graphql
    scalar DateTime
    

2. GraphQL Contextの設定

.
├── src
│   ├── graphql
│   │   ├── context
│   │   │   ├── index.ts
  • create file

    $ mkdir src/graphql/context
    $ touch src/graphql/context/index.ts
    
  • edit file

    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

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

initのコマンドも用意されていますが、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

    $ touch codegen.yml
    
    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

    $ pnpm graphql-codegen
    ✔ Parse Configuration
    ✔ Generate outputs
    

    実行すると以下のような構成でresolver用の型定義ファイルなどが生成されます

    .
    ├── src
    │   ├── generated
    │   │   ├── request.ts
    │   │   ├── resolvers-type.ts
    │   │   └── schema.graphql
    

4. GraphQL Resolver

.
├── src
│   ├── graphql
│   │   ├── resolvers
│   │   │   └─ index.ts
│   │   │
  • create file

    $ mkdir src/graphql/resolvers
    $ touch src/graphql/resolvers/index.ts
    
  • edit file

    3. GraphQL Code Generatorで作成したresolvers-typesを利用してresolverを設定していきます。

    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 },
          });
        },
      },
    };
    

GraphQL Code Generatorを使うメリット

  1. 型定義がされているので、このように補完を利用することができます。

    スクリーンショット 2023-04-16 10.29.17.png

  2. 型安全性

    試しにエラーを発生させてみましょう。

    Todoを返すはずのaddTodoで、文字列を返すとこのようにエラーが表示されます。
    スクリーンショット 2023-04-16 10.31.30.png

    予定されていない引数を利用しようとすると、このようにエラーが表示されます。
    スクリーンショット 2023-04-16 10.32.37.png

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

5. GraphQLサーバー修正

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

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にアクセスし、Queryを叩いてみましょう。

  1. addTodo

    スクリーンショット 2023-04-15 0.51.43.png

  2. listTodo

    スクリーンショット 2023-04-15 0.52.17.png

  3. deleteTodo

    スクリーンショット 2023-04-15 0.52.53.png

  4. listTodo

    スクリーンショット 2023-04-15 0.53.15.png

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

次回

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

  • 後編
    1. フロント側準備
    2. フロント側実装
7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?