😺

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

2021/12/05に公開

はじめに

本記事では、TypeScriptとPrismaとNestJSを使ってGraphQLサーバを作ってみます。

最終的には、以下のような記事の取得と作成ができるGraphQLサーバができあがります。

Prismaとは

Prismaは、以下の3つのツールで構成されたNode.jsとTypeScriptのためのORMです。

  • Prisma Client: 自動生成される型安全なデータベースクライアント
  • Prisma Migrate: 宣言的なデータモデリングとカスタマイズ可能なマイグレーション
  • Prisma Studio: データを閲覧・編集するためのモダンなGUI

開発体験が良く、先日のJSConf JPの @qsonaさんの発表 でもあったように最近では新規サービスでの採用事例が増えてきました。

https://www.prisma.io/

NestJSとは

NestJSは、素早くスケーラブルなサーバーサイドアプリケーションを構築するためのNode.jsフレームワークです。

実用的なアーキテクチャをすぐに構築できることを目的としていて、疎結合で、テストや保守しやすく、スケーラブルなアプリケーションを作成できます。
HTTPサーバとしての実装はデフォルトではExpressを利用しており、オプションでFastifyに変更することもできます。
また、TypeScriptをデフォルトでサポートしています。

最近では メルカリShops でも採用され、徐々に国内でも採用事例を聞くようになりました。

https://nestjs.com/

GraphQLサーバを作ってみる

それでは、早速GraphQLサーバを作っていきましょう!

本記事で使用するNestJSとPrismaのバージョンは以下の通りです。

Nest CLI: 8.2.1
NestJS: 8.3.1
Prisma: 3.10.0

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

NestJSのPrismaのドキュメント を参考に、NestJSとPrismaのセットアップをしていきます。

まずはNest CLIをインストールし、NestJSのプロジェクトを作成します。

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

ここで生成されるファイルの詳細を知りたい方は NestJSのドキュメント をご覧ください。

それでは、試しにサーバを起動してみましょう。

$ cd my-app
$ npm run start

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

2. Prismaのセットアップ

続いて、Prismaのセットアップを行います。

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

このコマンドで prisma/schema.prisma.env ファイルが生成されるので、それらを接続したいデータベースに合わせて編集します。

デフォルトではPostgreSQLを使うようになっていますが、今回はMySQLを使うため、dbprovidermysql に変更し、DATABASE_URL もMySQLのURLに書き換えます。

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"
# ex: DATABASE_URL="mysql://root:password@localhost:3306/my_app"

3. テーブルの作成

今回は記事メディアのようなサービスを想定して、タイトルと本文と公開フラグのカラムを持った Post テーブルを作成します。

Post モデルの定義を先程の schema.prisma に追加します。

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

以下のコマンドを実行すると、マイグレーションファイルの生成とマイグレーションの実行が行われます。

$ npx prisma migrate dev --name post

prisma ディレクトリには以下のようなマイグレーションファイルが生成されました。

$ tree prisma
prisma
├── migrations
│   ├── 20220223034555_post
│   │   └── migration.sql
│   └── migration_lock.toml
└── schema.prisma
生成されたマイグレーションファイルの中身
prisma/migrations/20220223034555_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についてさらに詳しく知りたい方は Prismaのドキュメント を参照してみてください。

4. Prisma Studioでデータを追加してみる

PrismaにはPrisma Studioというデータベースに対してCRUDの操作ができるGUIが付いています。

以下のコマンドでPrisma Studioを起動して、レコードを追加してみましょう。

$ npx prisma studio

http://localhost:5555 にアクセスして Post テーブルにレコードを追加してみてください。

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

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

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

ここからGraphQLの実装に入っていきます。

NestJSのGraphQLのページ を参考に必要なパッケージをインストールします。

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

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

NestJSのGraphQLプラグインを有効化すると必要な定型コードの量を減らすことができるので、nest-cli.jsoncompilerOptions を追加して有効化しておきます。

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

詳しくは https://docs.nestjs.com/graphql/cli-plugin に記載されています。

8. Object Typeの作成

GraphQLではコードファーストとスキーマファーストのアプローチがありますが、今回はコードファーストのアプローチを取ります。
コードファーストのアプローチでは、TypeScriptのデコレータを使用してクラス定義からGraphQLのSDLを生成します。

Post のObject Typeを作成してみましょう。
Post クラスを作り、@ObjectType() のデコレータを付けます。

src/posts/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;
}

GraphQLプラグインを有効化していない場合は各プロパティに @Field() を付ける必要がありますが、有効化している場合は各プロパティの情報から自動で判断してスキーマを生成してくれます。

上記のコードから以下のようなGraphQLのスキーマが生成されます。

type Post {
  id: ID!
  title: String!
  content: String!
  published: Boolean!
}

9. Resolverの作成

続いて、GraphQLでデータ操作を行うためのResolverを作成していきます。

今回は、Post の一覧を取得するQueryとレコードを新規作成するMutationを作成します。

Resolverを作るには、Resolverクラスを作成し、@Resolver() のデコレータを付けます。
そして、QueryとMutationの関数には @Query()@Mutation()、引数には @Args() を付けます。
これらの情報からGraphQLのスキーマが生成されます。

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

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

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

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

QueryやMutationの関数の中では、Prisma Clientを使ってレコードの取得や作成を行っています。

10. AppModuleに必要なモジュールとクラスを追加

最後に、AppModuleGraphQLModule と先程作成した PrismaServicePostsResolver を追加します。

src/app.module.ts
 @Module({
-  imports: [],
+  imports: [
+    GraphQLModule.forRoot<ApolloDriverConfig>({
+      driver: ApolloDriver,
+      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
+    }),
+  ],
   controllers: [AppController],
-  providers: [AppService],
+  providers: [AppService, PrismaService, PostsResolver],
 })
 export class AppModule {}

動作確認

これでGraphQLサーバが完成したので動作確認をしてみましょう。

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

$ npm run start

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

Queryで記事の一覧を取得してみます。

query {
  posts {
    id
    title
    content
    published
  }
}

4.で作成した Post の一覧が取得できます。

続いて、Mutationで新しい記事を作成してみます。

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

新しいレコードが追加されたことが確認できます。

おめでとうございます🥳

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

まとめ

本記事では、TypeScriptとPrismaとNestJSを使ってGraphQLサーバを作ってみました。

PrismaとNestJSのおかげで、あっという間にデータの取得と作成のできるGraphQLサーバを作ることができました。

記事が長くなりすぎるので今回は説明しませんでしたが、GraphQLでは1つのQueryで関連テーブルまで取得する際にN+1問題が発生しがちです。
それを防ぐための仕組みがPrismaにはデフォルトで用意されていてとても便利なので、近いうちにその辺りも記事にまとめたいと思います。

Discussion