Closed6

TypeScript + PrismaでGraphQLサーバを作ってみる

rincerince

NestJSのサイトを参考にNestJSとPrismaのセットアップをする。
https://docs.nestjs.com/recipes/prisma

1. NestJSプロジェクトの作成

$ npm install -g @nestjs/cli
$ nest new my-app

試しにサーバを起動してみる。

$ cd my-app
$ npm run start

http://localhost:3000/ にアクセスすると Hello World! が表示される。

2. Prismaのセットアップ

$ npm install @prisma/client
$ npm install prisma --save-dev
$ npx prisma init

3. データベース接続

prisma/schema.prisma.env を自分の環境に合わせて編集する。
今回はMySQLを使う。

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

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}
.env
DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE"

※大文字の部分を自分の環境の値に置き換える

rincerince

4. テーブルの作成

今回は記事メディアのようなサービスを想定。
Post モデルの定義を schema.prisma ファイルに追加する。

prisma/schema.prisma
model Post {
  id        Int      @default(autoincrement()) @id
  title     String
  content   String
  published Boolean @default(false)
}

以下のコマンドでマイグレーションファイルを生成する。
post の部分はディレクトリ名になるのでわかりやすい名前を付ければOK。

$ npx prisma migrate dev --name post
$ tree prisma
prisma
├── migrations
│   ├── 20211117140927_post
│   │   └── migration.sql
│   └── migration_lock.toml
└── schema.prisma

マイグレーションも自動で実行される。

生成されたマイグレーションファイルの中身
prisma/migrations/20211117140927_post/migration.sql
-- CreateTable
CREATE TABLE `Post` (
    `id` INTEGER NOT NULL AUTO_INCREMENT,
    `title` VARCHAR(191) NOT NULL,
    `content` VARCHAR(191) NOT NULL,
    `published` BOOLEAN NOT NULL DEFAULT false,

    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Prisma Studioでレコードを追加してみる。

$ npx prisma studio

http://localhost:5555 にアクセスして、GUIからレコードを追加できることを確認。

5. NestJSでPrismaクライアントを使用する

src ディレクトリ内に prisma.service.ts という新しいファイルを作成し、次のコードを追加する。

src/prisma.service.ts
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close();
    });
  }
}
rincerince

6. GraphQL関連のパッケージをインストール

以下のページを参考に必要なパッケージをインストール。
https://docs.nestjs.com/graphql/quick-start

$ npm install @nestjs/graphql graphql graphql-tools apollo-server-express

7. NestJSのGraphQLプラグインを有効化

NestJSのGraphQLプラグインを有効化することで必要な定型コードの量を減らすことができる。

next-cli.json
{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": ["@nestjs/graphql"]
  }
}

詳しくは以下のページ参照。
https://docs.nestjs.com/graphql/cli-plugin

8. ObjectTypeとResolverの作成

PostObjectType を作成。

src/models/post.model.ts
import { Field, ID, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class Post {
  @Field(() => ID)
  id: number;
  title: string;
  content: string;
  published: boolean;
}

PostQueryMutation を作成。

src/resolvers/post.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { Post } from 'src/models/post.model';
import { PrismaService } from '../prisma.service';

@Resolver(() => Post)
export class PostResolver {
  constructor(private prisma: PrismaService) {}

  @Query(() => [Post])
  async posts() {
    return this.prisma.post.findMany();
  }

  @Mutation(() => Post)
  async createPost(
    @Args({ name: 'title', type: () => String }) title: string,
    @Args({ name: 'content', type: () => String }) content: string,
  ) {
    return this.prisma.post.create({ data: { title, content } });
  }
}

9. AppModuleに必要なクラスやモジュールをインポート

AppModuleimportsGraphQLModuleprovidersPrismaServicePostResolver を追加する。

src/app.module.ts
@Module({
  imports: [
    GraphQLModule.forRoot({
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
    }),
  ],
  controllers: [AppController],
  providers: [AppService, PrismaService, PostResolver],
})
export class AppModule {}
rincerince

動作確認

以下のコマンドでサーバを起動。

$ npm run start

以下のエラーが出てサーバを起動できず。。

Error: You must `await server.start()` before calling `server.applyMiddleware()`

ググると以下の記事が見つかった。
https://stackoverflow.com/questions/68354656/unhandledpromiserejectionwarning-error-you-must-await-server-start-before/68354663#68354663

apollo-server-express を3系から2系にダウングレードすると、無事サーバが起動。

http://localhost:3000/graphql にアクセスするとGraphQL Playgroundが表示されました 🎉

Queryで記事を取得。

query {
  posts {
    id
    title
    content
    published
  }
}

Mutationで記事を作成。

mutation {
  createPost(title: "piyo", content: "3記事目の本文です。") {
    id
    title
    content
    published
  }
}

TypeScript + PrismaでGraphQLサーバを作ることができました 🥳

このスクラップは2021/12/05にクローズされました