nestでGraphQLバックエンドを立てる [2022年6月版]
TL;DR
nestで GraphQL + Fastify + Prisma + RDBなバックエンドを爆速構築して
GraphQL APIを作成するよ!
環境
asdf v0.10.1
node v18.2.0
プロジェクトとgitのrepositoryを作成する
任意のディレクトリ下にnestのプロジェクトを作成します
$ npx nest new nest-graphql-fastify-prisma
そうすると以下の感じでnestのプロジェクトが作成されます
パッケージマネージャーは任意のものを選んでください
今回はnpmにしておきました
~/Code/GitHub
❯ npx nest new nest-graphql-fastify-prisma
⚡ We will scaffold your app in a few seconds..
CREATE nest-graphql-fastify-prisma/.eslintrc.js (631 bytes)
CREATE nest-graphql-fastify-prisma/.prettierrc (51 bytes)
CREATE nest-graphql-fastify-prisma/README.md (3339 bytes)
CREATE nest-graphql-fastify-prisma/nest-cli.json (64 bytes)
CREATE nest-graphql-fastify-prisma/package.json (2016 bytes)
CREATE nest-graphql-fastify-prisma/tsconfig.build.json (97 bytes)
CREATE nest-graphql-fastify-prisma/tsconfig.json (546 bytes)
CREATE nest-graphql-fastify-prisma/src/app.controller.spec.ts (617 bytes)
CREATE nest-graphql-fastify-prisma/src/app.controller.ts (274 bytes)
CREATE nest-graphql-fastify-prisma/src/app.module.ts (249 bytes)
CREATE nest-graphql-fastify-prisma/src/app.service.ts (142 bytes)
CREATE nest-graphql-fastify-prisma/src/main.ts (208 bytes)
CREATE nest-graphql-fastify-prisma/test/app.e2e-spec.ts (630 bytes)
CREATE nest-graphql-fastify-prisma/test/jest-e2e.json (183 bytes)
? Which package manager would you ❤️ to use? npm
✔ Installation in progress... ☕
🚀 Successfully created project nest-graphql-fastify-prisma
👉 Get started with the following commands:
$ cd nest-graphql-fastify-prisma
$ npm run start
Thanks for installing Nest 🙏
Please consider donating to our open collective
to help us maintain this package.
🍷 Donate: https://opencollective.com/nest
cdしてプロジェクト内に移動
$ cd nest-graphql-fastify-prisma
とりあえず おまじないとして 環境のnodeバージョン固定のためasdfの.tool-versions
を作成しておきます
$ asdf local nodejs 18.2.0
ここまでやればとりあえずgitにリポジトリを作成してpushします
任意のリポジトリを作成後、適当に
git remote add origin
して
git add .
, git commit -m 'initial commit'
, git push
しました
GraphQLとかPrismaとかFastifyとかを設定する
Fastifyを導入する
公式ドキュメントを参考にFastifyを導入します
npm i --save @nestjs/platform-fastify
@nestjs/platform-fastify
がpackage.json
に追加されていればOK
使わないので@nestjs/platform-express
は消しましょう
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
- "@nestjs/platform-express": "^8.0.0",
+ "@nestjs/platform-fastify": "^8.4.7",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
公式ドキュメントを見ながらmain.ts
を編集します
import { NestFactory } from '@nestjs/core';
+import {
+ FastifyAdapter,
+ NestFastifyApplication,
+} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
async function bootstrap() {
- const app = await NestFactory.create(AppModule);
- await app.listen(3000);
+ const app = await NestFactory.create<NestFastifyApplication>(
+ AppModule,
+ new FastifyAdapter(),
+ );
+ await app.listen(3000, '0.0.0.0');
}
bootstrap();
これでExpressをFastifyに差し替えることができました
GraphQLを導入する
Fastifyを導入しましたが、Fastify界隈においてGraphQLサーバーの時代はmercurius
なので
mercurius
を公式ドキュメントに従って導入します
$ npm i @nestjs/graphql @nestjs/mercurius graphql mercurius
package.json
を確認してみましょう
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
+ "@nestjs/graphql": "^10.0.16",
+ "@nestjs/mercurius": "^10.0.16",
"@nestjs/platform-fastify": "^8.4.7",
+ "graphql": "^16.5.0",
+ "mercurius": "^9.8.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
いい感じにインストールできているので設定をしていきます
import { Module } from '@nestjs/common';
+import { GraphQLModule } from '@nestjs/graphql';
+import { MercuriusDriver, MercuriusDriverConfig } from '@nestjs/mercurius';
+import { join } from 'path';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
- imports: [],
+ imports: [
+ GraphQLModule.forRoot<MercuriusDriverConfig>({
+ driver: MercuriusDriver,
+ graphiql: process.env.NODE_ENV !== 'production',
+ autoSchemaFile: join(process.cwd(), 'src/generated/schema.gql'),
+ sortSchema: true,
+ }),
+ ],
controllers: [AppController],
providers: [AppService],
})
設定の説明を記載しておきます
graphiql: process.env.NODE_ENV !== 'production'
NODE_ENV === production
以外の場合graphiql
が起動します
graphiql
はApolloのPlayGroundみたいなウェブベースのGraphQL触れるやつ
autoSchemaFile: join(process.cwd(), 'src/generated/schema.gql')
GraphQLのスキーマファイルをsrc/generated
以下に吐き出す設定です
sortSchema: true
自動生成されたGraphQLのスキーマをいい感じにソートしてくれる設定です
以上でGraphQLの設定ができました
Prismaを導入する
Prismaは導入が 面倒臭い 独特なのでハマりがちですがやっていきましょう
Prismaの前にDBを用意する
とりあえず、Prismaが使うDBが欲しいので、適当にdocker-compose.yml
を書いてDBを用意します
dockerを導入していない場合はローカルに任意のDBをインストールしておいてください
+ version: '3'
+
+ services:
+ db:
+ image: postgres:14.3
+ restart: always
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: password
+ POSTGRES_DB: db
+ TZ: Asia/Tokyo
+ ports:
+ - '15432:5432'
+ volumes:
+ - ./docker/postgresql/data:/var/lib/postgresql/data
+ - ./docker/postgresql/initdb:/docker-entrypoint-initdb.d
今回はとりあえず現時点で最新のPostgreSQLにしておきました
作成したらdocker-compose up
してDBを起動しておきます
Prismaの導入と設定
DBが用意できていることを確認後、Prismaを導入していきます
例によって、公式ドキュメントを参考にセットアップを行います
# prismaのインストール
$ npm install prisma --save-dev
"devDependencies": {
"@nestjs/cli": "^8.0.0",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@types/jest": "27.0.2",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.2.5",
"prettier": "^2.3.2",
+ "prisma": "^3.15.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
},
devDependencies
の確認後、prismaの初期化を行います
# prismaの初期化
$ npx prisma init
npx prisma init
すると以下のように出力され、/prisma
ディレクトリが作成されます
✔ Your Prisma schema was created at prisma/schema.prisma
You can now open it in your favorite editor.
warn You already have a .gitignore. Don't forget to exclude .env to not commit any secret.
Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver, mongodb or cockroachdb.
3. Run prisma db pull to turn your database schema into a Prisma schema.
4. Run prisma generate to generate the Prisma Client. You can then start querying your database.
More information in our documentation:
https://pris.ly/d/getting-started
また、この時点で.env
, /prisma/schema.prisma
ファイルが自動生成されます
+ # Environment variables declared in this file are automatically made available to Prisma.
+ # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
+
+ # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
+ # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
+
+ DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
+ // This is your Prisma schema file,
+ // learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+ generator client {
+ provider = "prisma-client-js"
+ }
+
+ datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+ }
ここで.env
に先に用意したDBの接続情報を記載してDBの接続設定を行います
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
- DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
+ DATABASE_URL="postgresql://postgres:password@localhost:15432/db?schema=public"
migrationを実行してDBにテーブルを作成する
schema.prismaにDBデータの定義を作成します
enumを使ったサンプルにしたかったのと、
ユーザーIDをcuidにしたかったためnestのドキュメントとは異なっています
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
+
+ model User {
+ id String @default(cuid()) @id
+ email String @unique
+ name String?
+ gender Gender
+ posts Post[]
+ }
+
+ enum Gender {
+ MALE
+ FEMALE
+ OTHER
+ }
+
+ model Post {
+ id String @default(cuid()) @id
+ title String
+ content String?
+ visible Boolean? @default(false)
+ author User? @relation(fields: [authorId], references: [id])
+ authorId String
+ }
schema.prisma
の編集をしたら、migrationを走らせます
$ npx prisma migrate dev
migrationの名前などを聞かれますが特に考慮せず無指定でもOKです
以下のように出力されればmigrationは成功です
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "db", schema "public" at "localhost:15432"
✔ Enter a name for the new migration: …
Applying migration `20220618200953_`
The following migration(s) have been created and applied from new schema changes:
migrations/
└─ 20220618200953_/
└─ migration.sql
Your database is now in sync with your schema.
Running generate... (Use --skip-generate to skip the generators)
added 2 packages, and audited 811 packages in 3s
85 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
✔ Generated Prisma Client (3.15.2 | library) to ./node_modules/@prisma/client in 120ms
PrismaClientを導入し、nestで開発する準備を整える
ここまでできたら、やっとprismaのクライアントを導入し、実際にnestでprismaを使っていきます
$ npm install @prisma/client
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/graphql": "^10.0.16",
"@nestjs/mercurius": "^10.0.16",
"@nestjs/platform-fastify": "^8.4.7",
+ "@prisma/client": "^3.15.2",
"graphql": "^16.5.0",
"mercurius": "^9.8.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
},
prismaの利用のためにprisma.service.tsを作成します
/src/modules/prisma/prisma.service.ts
を作成します
$ npx nest g s modules/prisma
# 以下が出力されればOK!
CREATE src/modules/prisma/prisma.service.spec.ts (460 bytes)
CREATE src/modules/prisma/prisma.service.ts (90 bytes)
UPDATE src/app.module.ts (787 bytes)
出力されたprisma.service.ts
を以下のように書き換えます
- import { Injectable } from '@nestjs/common';
+ import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
+ import { PrismaClient } from '@prisma/client';
@Injectable()
- export class PrismaService {}
+ export class PrismaService extends PrismaClient implements OnModuleInit {
+ async onModuleInit() {
+ await this.$connect();
+ }
+
+ async enableShutdownHooks(app: INestApplication) {
+ this.$on('beforeExit', async () => {
+ await app.close();
+ });
+ }
+ }
GraphQLの型を自動生成する
ここで、GraphQLの型定義を書くのが面倒くさいため自動生成してくれるツールとその型変換をしてくれるツールを導入します
npm install prisma-nestjs-graphql class-transformer
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/graphql": "^10.0.16",
"@nestjs/mercurius": "^10.0.16",
"@nestjs/platform-fastify": "^8.4.7",
"@prisma/client": "^3.15.2",
+ "class-transformer": "^0.5.1",
"graphql": "^16.5.0",
"mercurius": "^9.8.0",
+ "prisma-nestjs-graphql": "^16.0.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
},
prisma-nestjs-graphqlがインストールできたらschema.prisma
を編集します
generator client {
provider = "prisma-client-js"
}
+ generator nestgraphql {
+ provider = "prisma-nestjs-graphql"
+ output = "../src/@generated/prisma-nestjs-graphql"
+ }
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
めちゃくちゃたくさんのファイルを生成するので、src/@generated
以下は.gitignore
に含めましょう
$ echo "src/@generated" >> .gitignore
ここまで準備するとschema.prismaで定義したエンティティに合わせてGraphQLのスキーマが生成できます
npx prisma generate
以下のようにprisma clientとGraphQLスキーマが生成されれば成功です
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
✔ Generated Prisma Client (3.15.2 | library) to ./node_modules/@prisma/client in 85ms
✔ Generated Prisma NestJS/GraphQL to ./src/@generated/prisma-nestjs-graphql in 422ms
You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client
'''
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
'''
ちょっと横にそれますが、ここでついでにnpm scriptも定義しておきましょう
"scripts": {
...
+ "generate:client": "prisma generate",
+ "generate:migration": "prisma migrate dev",
...
},
ここまでこればあとはnestで実装を進めていくだけです
nestを使ってCRUD用のGraphQL Query, Mutationを作成する
先に定義しているUser, PostのQueryとMutationを実装してみましょう
今回はUserの
- 単数取得
- 複数取得
- 単数登録
を作成してみます
ModuleとResolverの生成
まずはUser, Postに対応するモジュールとリゾルバーを作成していきます
今回は各種エンティティに対応するモジュールをsrc/components
以下に作成していきます
$ npx nest g mo components/user
CREATE src/components/user/user.module.ts (81 bytes)
UPDATE src/app.module.ts (863 bytes)
$ npx nest g r components/user
CREATE src/components/user/user.resolver.spec.ts (456 bytes)
CREATE src/components/user/user.resolver.ts (86 bytes)
UPDATE src/components/user/user.module.ts (158 bytes)
$ npx nest g mo components/post
CREATE src/components/post/post.module.ts (81 bytes)
UPDATE src/app.module.ts (939 bytes)
$ npx nest g r components/post
CREATE src/components/post/post.resolver.spec.ts (456 bytes)
CREATE src/components/post/post.resolver.ts (86 bytes)
UPDATE src/components/post/post.module.ts (158 bytes)
PrismaServiceを登録して利用できるようにする
User, Postの各モジュールにPrismaServiceを登録してResolverからPrismaを利用できるようにしましょう
import { Module } from '@nestjs/common';
+import { PrismaService } from 'src/modules/prisma/prisma.service';
import { UserResolver } from './user.resolver';
@Module({
- providers: [UserResolver]
+ providers: [UserResolver, PrismaService],
})
export class UserModule {}
import { Resolver } from '@nestjs/graphql';
+import { PrismaService } from 'src/modules/prisma/prisma.service';
@Resolver()
-export class UserResolver {}
+export class UserResolver {
+ constructor(private readonly prisma: PrismaService) {}
+}
Postに関しても同様のためpost.module.ts
, post.resolver.ts
に関しての変更は省略します
GraphQLの入出力定義となるInput, OutputのClassを作成する
先に生成しておいたsrc/@generated/prisma-nestjs-graphql
以下からコピペしてきて修正するだけでmodel
, input
を作成できます
実際には親子関係のあるネストしたデータの取得等もできるためもうちょっといろんなフィールドが生成されていますが今回はシンプルにユーザーを作成・取得するだけのため削除しています
import { Field, ObjectType, ID, Int, registerEnumType } from '@nestjs/graphql';
import { Post } from '../../post/interfaces/post.model';
export enum Gender {
MALE = 'MALE',
FEMALE = 'FEMALE',
OTHER = 'OTHER',
}
registerEnumType(Gender, { name: 'Gender', description: undefined });
@ObjectType()
export class User {
@Field(() => ID, { nullable: false })
id!: string;
@Field(() => String, { nullable: false })
email!: string;
@Field(() => String, { nullable: true })
name!: string | null;
@Field(() => Gender, { nullable: false })
gender!: keyof typeof Gender;
}
import { Field, InputType } from '@nestjs/graphql';
import { Gender } from './user.model';
@InputType()
export class UserCreateInput {
@Field(() => String, { nullable: true })
id?: string;
@Field(() => String, { nullable: false })
email!: string;
@Field(() => String, { nullable: true })
name?: string;
@Field(() => Gender, { nullable: false })
gender!: keyof typeof Gender;
}
input, outputが作成できたらResolverでそれらを利用してQueryとMutationを作成してみましょう
Query, Mutationの作成
user.resolver.ts
にQueryとMutationのメソッドを作成します
schema.gql
は自動生成されるため、デコレーターを使ってメソッドに表現を書くだけでGraphQLスキーマが自動生成されます
コードファーストアプローチというやつです
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { PrismaService } from 'src/modules/prisma/prisma.service';
import { UserCreateInput } from './interfaces/user-create.input';
import { User } from './interfaces/user.model';
@Resolver()
export class UserResolver {
constructor(private readonly prisma: PrismaService) {}
@Query(() => User, { description: 'User,ID指定単数取得' })
async getOneUserByID(@Args('userId') input: string): Promise<User> {
return await this.prisma.user.findFirst({ where: { id: input } });
}
@Query(() => [User], { description: 'User, 全件取得' })
async getAllUser() {
return await this.prisma.user.findMany();
}
@Mutation(() => User, { description: 'User, 単数登録' })
async createUser(
@Args('userCreateInput') input: UserCreateInput,
): Promise<User> {
return await this.prisma.user.create({ data: input });
}
}
ここまでできればUserを取得したり登録したりができるGraphQLのAPIが作成できました
それではサーバーを起動してみましょう
サーバー起動、実際にGraphQLを使ってみる
# nestがデフォルトで用意してくれているnpm scriptを使う
$ npm run start:dev
こんなかんじに出力されて起動すればOKです
[4:12:29 PM] Starting compilation in watch mode...
[4:12:31 PM] Found 0 errors. Watching for file changes.
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [NestFactory] Starting Nest application...
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [InstanceLoader] PostModule dependencies initialized +59ms
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [InstanceLoader] UserModule dependencies initialized +1ms
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [InstanceLoader] AppModule dependencies initialized +0ms
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [InstanceLoader] GraphQLSchemaBuilderModule dependencies initialized +1ms
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [InstanceLoader] GraphQLModule dependencies initialized +0ms
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [RoutesResolver] AppController {/}: +5ms
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [GraphQLModule] Mapped {/graphql, POST} route +76ms
[Nest] 22566 - 06/19/2022, 4:12:32 PM LOG [NestApplication] Nest application successfully started +53ms
シレッとデータを作成してあるので実際にcurlで叩いてみましょう
実際にはcurlはAltairというGraphQLクライアントのツールで出力したものです
使いやすいのでオススメ
curl 'http://localhost:3000/graphql' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'Origin: chrome-extension://flnheeellpciglgpaodhkhmapeljopja' --data-binary '{"query":"{\n getAllUser {\n email\n gender\n id\n name\n }\n}\n\n","variables":{}}' --compressed
{"data":{"getAllUser":[{"email":"test1@user.com","gender":"FEMALE","id":"cl4kytmut0000jsq636wgcbvm","name":null},{"email":"test3@user.com","gender":"OTHER","id":"cl4kyuc8k0014jsq648wjyyga","name":null},{"email":"test2@user.com","gender":"MALE","id":"cl4kytzor0007jsq6ahw31p45","name":null}]}}%
では次はユーザーを作成してみましょう
curl 'http://localhost:3000/graphql' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'Origin: chrome-extension://flnheeellpciglgpaodhkhmapeljopja' --data-binary '{"query":"mutation {\n createUser(userCreateInput: { email: \"test-user@user.com\", gender: MALE }) {\n email\n gender\n id\n name\n }\n}\n","variables":{}}' --compressed
{"data":{"createUser":{"email":"test-user@user.com","gender":"MALE","id":"cl4kzlp2y0015jiq6mv9d5m67","name":null}}}%
ID指定やメールアドレス指定での取得までできれば、UpdateやDeleteもできるため、CRUDを作成して実際のAPIとして利用するのも容易だと思います
サクサクバックエンドできたしゴリゴリ開発しよう
とりあえずこれで一通り動くサーバーが構築できたのかなと思います
実際にはdockerfileを書いてデプロイに備えたり、testを書いたりresolverでPrismaを読んで終わりにするのではなくService層でデータを加工してPrismaを呼んだり、トランザクションのある処理を実装しないといけなかったり、GuardやMiddlewareを書いて認証やなんやを処理したりcorsの設定とかをしたりとプロダクションのサーバーとして作らなければいけない部分が多数ありますが、
そういう部分を書き始めると一生完結しないためここで区切って別記事でノウハウか何か扱いで書ければなとおもいます。
Discussion