概要
Node.jsのバックエンドフレームワークNestJS
とTypeScript界のORMPrisma
を使って
GraphQLサーバーを作ります。
ちなみに、PrismaはREST APIの構築に用いることもできますし、GraphQLに特化しているわけではありません。
しかし、スキーマを定義するPrisma schemaがGraphQLのSDL(Schema Definition Language = スキーマ定義言語)に近いことや、GraphQLの複雑さをPrisma Clientによってカバーできることなど、GraphQLとの親和性はかなり高いと思っています。
導入手順
NestJSのドキュメントにPrismaとGraphQLの項目がそれぞれありますので、基本的にこれに沿って進めていきます。
https://docs.nestjs.com/recipes/prisma
https://docs.nestjs.com/graphql/quick-start
1.NestJS CLIをインストール
$ npm i -g @nestjs/cli
2.NestJSプロジェクトを作成
$ nest new my-app
3.Prisma CLIとPrisma Clientをインストール
$ cd my-app
$ npm install @prisma/client
$ npm install --save-dev prisma
4.Prismaのセットアップ
$ npx prisma init
このコマンドを実行すると、ルートディレクトリ直下にprisma/schema.prisma
と.env
が生成されます。
5.データベースと接続
prisma/schema.prisma
と.env
を自分で用意するデータベースに合わせて編集します。
私の場合はDockerでMysqlコンテナを立てたので以下のようになりました。
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
DATABASE_URL="mysql://root:password@db/development"
※@の後ろのdbはコンテナ名です。
※developmentはテーブル名なので、適当な名前に変えても問題ありません。
version: '3.8'
services:
server:
build: .
volumes:
- ./:/usr/app
ports:
- '3000:3000'
tty: true
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
ports:
- '3306:3306'
command: --default-authentication-plugin=mysql_native_password
volumes:
- ./prisma/mysql:/var/lib/mysql
公式ドキュメントに記載の通り、手軽に実行するならsqliteを使うのが一番楽です。
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
DATABASE_URL="file:./dev.db"
6.スキーマを定義
schema.prisma
にスキーマを定義します。ここでは例としてUser
を作成します。
(↑に追加)
model User {
id Int @id @default(autoincrement())
registeredAt DateTime?
updatedAt DateTime?
email String?
name String
}
7.スキーマをデータベースに適用
6で定義したスキーマをデータベースに適用させるのには二つの方法があります。
Create migrations from your Prisma schema, apply them to the database, generate artifacts (e.g. Prisma Client)
$ prisma migrate dev --preview-feature
Push the Prisma schema state to the database
$ prisma db push --preview-feature
migrateを実行した場合は、prisma
ディレクトリ下にmigrations
ディレクトリが生成され、さらにその下にマイグレーション毎にディレクトリが生成されます。
migration.sql
にはスキーマを適用するために発行するSQLが書かれています。
migrateにはresetやstatusなど別のコマンドも用意されていますので、詳しくは以下をご覧ください。
https://www.prisma.io/docs/reference/api-reference/command-reference#prisma-migrate-preview
├── prisma
│ ├── migrations
│ │ └── 20201220094108_
│ │ └── migration.sql
db pushを実行した場合には、何もファイルは生成されずにDBにそのままスキーマが適用されます。
個人的には、Railsでmigrationよりもridgepoleが好まれていることを考えると、db pushを使って良いような気がしています。
8. PrismaClientを扱うPrismaServiceを作成
NestJSでPrisma Client APIを扱うために、src以下にprisma.service.ts
を作成します。
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient
implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
9. GraphQL関連のパッケージをインストール
npm i @nestjs/graphql graphql-tools graphql apollo-server-express
※fastifyを使う場合はapollo-server-express
の代わりにapollo-server-fastify
をインストールします。加えて、src/main.ts
のappでFastifyを扱うように置き換えます。
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
10. Object typeとResolversを作成
GraphQLの実装にはCode firstとSchema firstの2種類の手法があります。今回はコードからスキーマを作成するCode firstの手順を用いました。
import { ObjectType, Field, Int, HideField } from '@nestjs/graphql';
@ObjectType()
export class User {
@Field((type) => Int)
id: number;
@Field({ name: 'registeredAt' })
createdAt?: Date;
updatedAt?: Date;
email: string;
@HideField()
password: string;
name?: string;
}
import { Resolver, Query } from '@nestjs/graphql';
import { User } from '../models/user.model';
import { PrismaService } from '../prisma.service';
@Resolver((of) => User)
export class UserResolver {
constructor(private prisma: PrismaService) {}
@Query((returns) => [User])
async users() {
return this.prisma.user.findMany();
}
}
この時、nest-cli.json
のcompilerOptions
を追加してGraphQL pluginを効かせておくと、ある程度コード量を減らすことができて便利です。
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/graphql/plugin",
"options": {
"typeFileNameSuffix": [".input.ts", ".model.ts"]
}
}
]
}
また、6で作成したschema.prisma
からNestJS用のコードを自動生成するGeneraterがありますが、公式でのサポートを受けているわけでもなく、まだなかなか採用するには難しいように感じています。
- https://github.com/EndyKaufman/typegraphql-prisma-nestjs-example
- https://github.com/wSedlacek/prisma-generators/tree/master/libs/nestjs
- https://github.com/unlight/prisma-nestjs-graphql
11.AppModuleでPrismaService, GraphQLModule, UserResolverをインポートする
8で作成したPrismaService
, 9でインストールしたGraphQLModule
, 10で作成したUserResolver
をAppModuleでインポートします。
@Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
debug: true,
playground: true,
}),
],
controllers: [AppController],
providers: [AppService, PrismaService],
})
動作確認
ここまで作成したら、npm run start
でNestサーバーを起動します。
http://localhost:3000/graphql にアクセスするとGraphQLプレイグラウンドが表示されます。
また、この時autoSchemaFile
に定義したように、スキーマ定義ファイルであるsrc/schema.gql
が自動的に生成されます。
最後に
Nexus1.0のリリースノートを見てから、なんとかNexusを組み込めないか考えてみましたが、現状のNest+PrismaにNexusを使う意味は薄そうというのが私の所感です。
https://www.prisma.io/blog/announcing-the-release-of-nexus-schema-v1-b5eno5g08d0b
本記事の間違っている箇所や、他に良さげなプラクティスがあればご指摘いただけますと幸いです。
ここまで読んでくださり、ありがとうございました。