Nest.js学習にっき3
前回&前々回に引き続き、notiz-dev/nestjs-prisma-starterで始める Nest.js 学習記録にっきだよ。
※画像引用:https://nestjs.com/
今回、学習したいことを列挙しておく。
- Serviceを実装して、prisma でDBとやり取りする
早速やっていこう。
Serviceを実装して、prisma でDBとやり取りする
まずはschema.prismaに掲示板、スレッド、レス、ユーザーのテーブルを追加しよう。
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
// previewFeatures = []
}
generator dbml {
provider = "prisma-dbml-generator"
}
model User {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique
password String
firstname String?
lastname String?
posts Post[]
role Role
}
model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
published Boolean
title String
content String?
author User? @relation(fields: [authorId], references: [id])
authorId String?
}
enum Role {
ADMIN
USER
}
model Bbs {
id String @id @default(uuid()) @db.Uuid
bbsName String @db.VarChar(100)
BbsThread BbsThread[]
}
model BbsThread {
id String @id @default(uuid()) @db.Uuid
bbsId String @db.Uuid
threadName String @db.VarChar(100)
description String @db.VarChar(2000)
bbs Bbs @relation(fields: [bbsId], references: [id])
BbsThreadResponse BbsThreadResponse[]
}
model BbsThreadResponse {
id String @id @default(uuid()) @db.Uuid
bbsThreadId String @db.Uuid
userId String @db.Uuid
postedAt DateTime @default(now())
content String @db.VarChar(2000)
bbsThread BbsThread @relation(fields: [bbsThreadId], references: [id])
user BbsUser @relation(fields: [userId], references: [id])
}
model BbsUser {
id String @id @default(uuid()) @db.Uuid
userName String @db.VarChar(100)
BbsThreadResponse BbsThreadResponse[]
}
スキーマを書いたら、 npm run migrate:dev
でマイグレーションしよう。そうするとschema.prismaに書いたテーブル定義が実際のDBに反映される。このプロジェクトの場合、DBはPostgreSQLだね。
次にServiceを実装しよう。
// ---- src/bbs/bbs.service.ts ----
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'nestjs-prisma';
import { CreateBbsInput } from './dto/create-bbs.input';
import { BbsIdArgs } from './dto/bbs-id.args';
import { Bbs } from './models/bbs.model';
import { BbsThread } from './models/bbs-thread.model';
@Injectable()
export class BbsService {
constructor(private prisma: PrismaService) {}
async bbs(args: BbsIdArgs): Promise<Bbs | null> {
const bbs = await this.prisma.bbs.findUnique({
where: {
id: args.id,
},
select: {
id: true,
bbsName: true,
},
});
if (!bbs) return null;
return {
bbsId: bbs.id,
bbsName: bbs.bbsName,
};
}
async threads(
bbs: Bbs,
limit: number | undefined
): Promise<Omit<BbsThread, 'bbs'>[]> {
const threads = await this.prisma.bbsThread.findMany({
where: {
bbsId: bbs.bbsId,
},
select: {
id: true,
threadName: true,
description: true,
},
take: limit,
});
return threads.map((thread) => {
return {
threadId: thread.id,
threadName: thread.threadName,
description: thread.description,
};
});
}
async createBbs(input: CreateBbsInput): Promise<Bbs> {
const bbs = await this.prisma.bbs.create({
data: {
bbsName: input.bbsName,
},
select: {
id: true,
bbsName: true,
},
});
return {
bbsId: bbs.id,
bbsName: bbs.bbsName,
};
}
}
Service内でPrismaServiceを利用しているけど、これはグローバルに使えるよう事前に登録済みだったよね。
他にもbbs-user.service.ts、bbs-thread.service.ts、bbs-thread-response.service.tsを作ったけど長いから割愛するよ。
Serviceを作ったら、モジュールに登録しよう。出ないと使えないからね。
// ---- src/bbs/bbs.module.ts ----
import { Module } from '@nestjs/common';
import { BbsResolver } from './bbs.resolver';
import { BbsUserResolver } from './bbs-user.resolver';
import { BbsThreadResolver } from './bbs-thread.resolver';
import { BbsThreadResponseResolver } from './bbs-thread-response.resolver';
import { BbsService } from './bbs.service';
import { BbsUserService } from './bbs-user.service';
import { BbsThreadService } from './bbs-thread.service';
import { BbsThreadResponseService } from './bbs-thread-response.service';
@Module({
imports: [],
providers: [
BbsResolver,
BbsUserResolver,
BbsThreadResolver,
BbsThreadResponseResolver,
BbsService,
BbsUserService,
BbsThreadService,
BbsThreadResponseService,
],
})
export class BbsModule {}
さて、あとは登録したServiceを使って、これまでダミー値しか返していなかったQueryやMutationを改修するね。
案の定、長くなるからbbs.resolver.tsだけ書くよ。
// ---- src/bbs/bbs.resolver.ts ----
import {
Args,
Int,
Mutation,
Parent,
Query,
ResolveField,
Resolver,
} from '@nestjs/graphql';
import { Bbs } from './models/bbs.model';
import { CreateBbsInput } from './dto/create-bbs.input';
import { BbsIdArgs } from './dto/bbs-id.args';
import { BbsService } from './bbs.service';
import { BbsThread } from './models/bbs-thread.model';
@Resolver(() => Bbs)
export class BbsResolver {
constructor(private bbsService: BbsService) {}
@Query(() => Bbs, {
description: '掲示板をIDで取得する',
nullable: true,
})
async bbs(@Args() args: BbsIdArgs): Promise<Bbs | null> {
return this.bbsService.bbs(args);
}
@ResolveField()
async threads(
@Parent() bbs: Bbs,
@Args('limit', {
type: () => Int,
description: '取得件数の上限',
nullable: true,
})
limit?: number
): Promise<Omit<BbsThread, 'bbs'>[]> {
return this.bbsService.threads(bbs, limit);
}
@Mutation(() => Bbs, {
description: '新しい掲示板を作成する',
})
async createBbs(@Args('input') input: CreateBbsInput): Promise<Bbs> {
return this.bbsService.createBbs(input);
}
}
こんな感じだね。
ちなみにBbsのthreadsフィールドリゾルバには、ParentだけでなくArgsを引数に持たせることで、必要に応じて「ネストしたクエリ」みたいに使えるようにしているよ。ちなみにこの「ネストしたクエリ」という言葉は、GraphQLにおける正式名称ではなくて、特に表現が思いつかなかったから自分でつけた仮称だよ。正式名称なにかあるのかな?
nest start
でサーバーを起動したら、実装したクエリを試してみよう。
オペレーションの内容は例えばこんな感じ。
query getThreadsFromBbs01 {
bbs(id: "157e19b6-0630-411d-a710-3637494fc979") {
bbsName
threads(limit: 3) {
threadName
}
}
}
この場合には、次のような応答が来るはずだよ。
{
"data": {
"bbs": {
"bbsName": "掲示板その1",
"threads": [
{
"threadName": "TT3"
},
{
"threadName": "TT5"
},
{
"threadName": "TT4"
}
]
}
}
}
もしDBの中身を比較して操作の結果を見たいなら、Prisma Studioも起動しよう。このnpx prisma studioでDBの中身を参照したりいじったりできるブラウザベースのツールが起動するからね。
ちゃんと動いてることが確認できたら、これでPrismaを使ったNest.jsのResolver、Service、そしてDB間の繋ぎこみは完了だね!