👋

4. Prisma Clientを使ってGraphQL APIの作成 Prisma 入門ワークショップ(2021)

2021/06/25に公開

ゴール

このレッスンの目的は、Prisma Clientについて学んだばかりの知識を使って、Apollo Serverを使ってGraphQL APIのリゾルバを実装することです。

レッスン

0. Prisma 入門ワークショップ(2021)
1. Prismaのセットアップ
2. Prisma Clientを使ってみる
3. Prisma Clientを使ってREST APIの作成
4. Prisma Clientを使ってGraphQL APIの作成←ここ

リソース

Prismaとは?
Node.js & TypeScript向けの完璧なORM

英語のドキュメント
https://www.notion.so/A-Practical-Introduction-to-Prisma-2021-ccf00a066ef4432caeb03da179e38302

Zoom ウェビナー
https://www.youtube.com/watch?v=80CYmde9z9c

GitHub
https://github.com/nikolasburk/prisma-workshop

セットアップ

レッスン1で設定したのと同じ prisma-workshop プロジェクトで作業を続けることができます。ただし、このレッスンのスターターは、クローンしたレポの graphql-api ブランチにあります。

そのブランチに切り替える前に、プロジェクトの現在の状態をコミットする必要があります。簡単にするために、stashコマンドを使ってこれを行うことができます。

git stash

このコマンドを実行したら、graphql-api ブランチに切り替えて、現在の migrations ディレクトリと dev.db ファイルを削除することができます。

git checkout graphql-api
rm -rf prisma/migrations
rm prisma/dev.db

次に、npmの依存関係を一掃し、package.jsonにある新しい依存関係を考慮して再インストールします。

rm -rf node_modules
npm install

ここで使用するデータモデルは、先ほどのREST APIのレッスンで使用したものと似ています。

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

model Post {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String
  content   String?
  published Boolean  @default(false)
  viewCount Int      @default(0)
  author    User?    @relation(fields: [authorId], references: [id])
  authorId  Int?
}

Prismaのセットアップを最初からやり直すので、データベースとそのテーブルを再作成する必要があります。以下のコマンドを実行して、これを行います。

npx prisma migrate dev --name init

最後に、prisma/seed.tsファイルで指定されたサンプルデータをデータベースにシードします。このseedスクリプトは、以下のコマンドで実行できます。

npx prisma db seed --preview-feature

以上で、タスクの準備が整いました。

タスク

このレッスンのタスクは、TODOと書かれたsrc/index.tsファイルの中にあります。目標は、各GraphQLリゾルバに対して、適切なPrisma Clientのクエリを挿入することです。

サーバーを起動し、http://localhost:4000にあるGraphQL Playgroundを開くことで、自分の実装をテストすることができます。

Query.allUsers: [User!]!

すべてのユーザーを取得します。

答え
allUsers: (_parent, _args, context: Context) => {
  return context.prisma.user.findMany()
},
GraphQLのサンプルクエリ
{
  allUsers {
    id
    name
    email
    posts {
      id
      title
    }
  }
}

Query.postById(id: Int!): Post

idでPostを取得する。

答え
postById: (_parent, args: { id: number }, context: Context) => {
  return context.prisma.post.findUnique({
    where: { id: args.id }
  })
},
GraphQLのサンプルクエリ
{
  postById(id: 1) {
    id
    title
    content
    published
    viewCount
    author {
      id
      name
      email
    }
  }
}

Query.feed(searchString: String, skip: Int, take: Int): [Post!]!

公開されているすべての投稿を取得し、検索文字列がタイトルやコンテンツに含まれているかどうかをチェックして、ページ分割やフィルタリングを行います。

答え
feed: (
  _parent,
  args: {
    searchString: string | undefined;
    skip: number | undefined;
    take: number | undefined;
  },
  context: Context
) => {
  const or = args.searchString
    ? {
        OR: [
          { title: { contains: args.searchString as string } },
          { content: { contains: args.searchString as string } },
        ],
      }
    : {};

  return context.prisma.post.findMany({
    where: {
      published: true,
      ...or,
    },
    skip: Number(args.skip) || undefined,
    take: Number(args.take) || undefined,
  });
},
GraphQLのサンプルクエリ
{
  feed {
    id
    title
    content
    published
    viewCount
    author {
      id
      name
      email
    }
  }
}

Query.draftsByUser(id: Int!): [Post]

特定のユーザーの未公開のPostを取得します。

答え
draftsByUser: (_parent, args: { id: number }, context: Context) => {
  return context.prisma.user.findUnique({
    where: { id: args.id }
  }).posts({
    where: {
      published: false
    }
  })
},
GraphQLのサンプルクエリ
{
  draftsByUser(id: 3) {
    id
    title
    content
    published
    viewCount
    author {
      id
      name
      email
    }
  }
}

Mutation.signupUser(name: String, email: String!): User!

新しいユーザーを作成します。

答え
signupUser: (
  _parent,
  args: { name: string | undefined; email: string },
  context: Context
) => {
  return context.prisma.user.create({
    data: {
      name: args.name,
      email: args.email
    }
  })
},
GraphQLのサンプルクエリ
mutation {
  signupUser(
    name: "Nikolas"
    email: "burk@prisma.io"
  ) {
    id
    posts {
      id
    }
  }
}

Mutation.createDraft(title: String!, content: String, authorEmail: String): Post

新しいPostを作成します。

答え
createDraft: (
  _parent,
  args: { title: string; content: string | undefined; authorEmail: string },
  context: Context
) => {
  return context.prisma.post.create({
    data: {
      title: args.title,
      content: args.content,
      author: {
        connect: {
          email: args.authorEmail
        }
      }
    }
  })
},
GraphQLのサンプルミューテーション
mutation {
  createDraft(
    title: "Hello World"
    authorEmail: "burk@prisma.io"
  ) {
    id
		published
    viewCount
    author {
      id
      email
      name
    }
  }
}

Mutation.incrementPostViewCount(id: Int!): Post

投稿の表示回数を1回増やすことができます。

答え
incrementPostViewCount: (
  _parent,
  args: { id: number },
  context: Context
) => {
  return context.prisma.post.update({
    where: { id: args.id },
    data: {
      viewCount: {
        increment: 1
      }
    }
  })
},
GraphQLのサンプルミューテーション
mutation {
  incrementPostViewCount(id: 1) {
    id
    viewCount
  }
}

Mutation.deletePost(id: Int!): Post

Postを削除します。

答え
deletePost: (_parent, args: { id: number }, context: Context) => {
  return context.prisma.post.delete({
    where: { id: args.id }
  })
},
GraphQLのサンプルミューテーション
mutation {
  deletePost(id: 1) {
    id
  }
}

User.posts: [Post!]!

指定したユーザーのPostを返します。

答え
User: {
  posts: (parent, _args, context: Context) => {
    return context.prisma.user.findUnique({
      where: { id: parent.id }
    }).posts()
  },
},

Post.author: User

指定されたPostのAuthorを返します。

答え
Post: {
  author: (parent, _args, context: Context) => {
    return context.prisma.post.findUnique({
      where: { id: parent.id }
    }).author()
  },
},

Discussion