NestJS触ってみる(DB)
DB環境構築
Docker で MySQL を使用
$ docker -v
Docker version 20.10.12, build e91ed57
docker-compose.yml
を作成
$ pwd
project/backend # ← プロジェクトのルートディレクトリという意味
$ touch docker-compose.yml
version: '3'
services:
db:
image: mysql
platform: linux/amd64
env_file: .env
ports:
- "3306:3306"
ルートディレクトリに.env
ファイルを作成し MySQL の環境変数を定義
MYSQL_ROOT_PASSWORD="password"
MYSQL_DATABASE="db"
MYSQL_USER="user"
MYSQL_PASSWORD="password"
TZ="Asia/Tokyo"
イメージ作成とコンテナ作成起動
$ docker-compose up -d
コンテナに入ってデータベースが作成できているか確認
$ docker-compose exec db mysql -u user -p
Enter password: # ← MYSQL_PASSWORDを入力
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| db | # ← MYSQL_DATABASEの名前で作成できてれば成功
| information_schema |
+--------------------+
Prismaの導入
ORM に Prisma を使用
$ yarn add prisma --dev
Prisma の初期設定
$ yarn prisma init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
DATABASE_URL="mysql://root:password@localhost:3306/db"
テーブルを作成
schema.prisma
に Item モデルを定義
// --------- 略 ---------
model Item {
id String @id @default(uuid())
name String
price Int
description String
status ItemStatus
created_at DateTime @default(dbgenerated("NOW()")) @db.Timestamp(0)
updated_at DateTime @default(dbgenerated("NOW() ON UPDATE CURRENT_TIMESTAMP")) @db.Timestamp(0)
@@map("items")
}
enum ItemStatus {
ON_SALE
SOLD_OUT
}
現時点で Prisma でタイムゾーンの変更ができない。
created_at
と updated_at
で DBで設定のタイムゾーンから取得して設定するようにしている。
→ こちらの記事を参考
→ Issue
タイムゾーンのことを意識しない場合はこれでいけそう↓
model Item {
// --------- 略 ---------
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// --------- 略 ---------
}
マイグレーションファイル作成とテーブル作成
$ yarn prisma migrate dev --name create_items_table
↪︎ prisma/migrations
にマイグレーションファイルが作成される
↪︎ データベースにテーブルが作成される
データベースにテーブルが作成されているかを確認
$ yarn prisma studio
データベースへCRUD操作
@prisma/clientをインストール
$ yarn add @prisma/client
データベースへの接続を行う prisma.service.ts
を src
ディレクトリに作成
import { Injectable, OnModuleInit } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect()
}
}
items.module.ts
に追加
import { Module } from '@nestjs/common'
import { ItemsController } from './items.controller'
import { ItemsService } from './items.service'
+ import { PrismaService } from '../prisma.service'
@Module({
controllers: [ItemsController],
- providers: [ItemsService],
+ providers: [ItemsService, PrismaService],
})
export class ItemsModule {}
POST
items.service.ts
に create
メソッドを実装
import { Item, Prisma } from '@prisma/client'
import { PrismaService } from 'src/prisma.service'
// --------- 略 ---------
@Injectable()
export class ItemsService {
constructor(private prisma: PrismaService) {}
// --------- 略 ---------
async create(createItemDto: CreateItemDto): Promise<Item> {
const { name, price, description } = createItemDto
const data: Prisma.ItemCreateInput = {
name,
price: Number(price), // ← ここでキャストしないとエラーになる
description,
status: 'ON_SALE',
}
return this.prisma.item.create({ data })
}
// --------- 略 ---------
}
↪︎ DTO クラスで class-validator
を使用して定義したバリデーションを使用
バリデーション方法はこちらの
バリデーション
の通り
itemsコントローラーの@Post
を編集
// --------- 略 ---------
import { Item } from '@prisma/client'
// --------- 略 ---------
@Controller('items')
export class ItemsController {
constructor(private readonly itemsService: ItemsService) {}
// --------- 略 ---------
@Post()
async create(@Body() createItemDto: CreateItemDto): Promise<Item> {
return this.itemsService.create(createItemDto)
}
// --------- 略 ---------
}
POST で DB に保存されるか確認
$ curl -X POST -d "name=Mac&price=100000&description=綺麗です" http://localhost:3000/items | jq
{
"id": "6c6e429a-7606-4b46-9b6a-364a3………",
"name": "Mac",
"price": 100000,
"description": "綺麗です",
"status": "ON_SALE",
"created_at": "2022-04-08T11:06:44.000Z",
"updated_at": "2022-04-08T11:06:44.000Z"
}
↪︎ id
が自動的に uuid で付与されている
↪︎ DateTime が JST になっている
クエリのログが出力されるようにする
prisma.service.ts
を編集
import { Injectable, OnModuleInit, Logger } from '@nestjs/common'
import { PrismaClient, Prisma } from '@prisma/client'
@Injectable()
export class PrismaService
extends PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel>
implements OnModuleInit
{
private readonly logger = new Logger(PrismaService.name)
constructor() {
super({ log: ['query', 'info', 'warn', 'error'] })
}
async onModuleInit() {
this.$on('query', (event) => {
this.logger.log(
`Query: ${event.query}`,
`Params: ${event.params}`,
`Duration: ${event.duration} ms`,
)
})
this.$on('info', (event) => {
this.logger.log(`message: ${event.message}`)
})
this.$on('error', (event) => {
this.logger.log(`error: ${event.message}`)
})
this.$on('warn', (event) => {
this.logger.log(`warn: ${event.message}`)
})
await this.$connect()
}
}
こちらの記事を参考 ↓
GET
items.service.ts
に findAll
メソッドとfindById
メソッドを実装
(全件取得と個別取得)
// --------- 略 ---------
@Injectable()
export class ItemsService {
// --------- 略 ---------
async findAll(): Promise<Item[]> {
return this.prisma.item.findMany()
}
async findById(where: Prisma.ItemWhereUniqueInput): Promise<Item> {
const found = this.prisma.item.findUnique({ where })
if (!found) {
throw new NotFoundException()
}
return found
}
// --------- 略 ---------
}
Controller の@Get
を編集
// --------- 略 ---------
@Controller('items')
export class ItemsController {
// --------- 略 ---------
@Get()
async findAll(): Promise<Item[]> {
return this.itemsService.findAll()
}
@Get(':id')
async findById(@Param('id', ParseUUIDPipe) id: string): Promise<Item> {
return this.itemsService.findById({ id })
}
// --------- 略 ---------
}
PATCH
updateStatus
メソッドを実装
(statusをSOLD_OUTにするメソッド)
// --------- 略 ---------
@Injectable()
export class ItemsService {
// --------- 略 ---------
async updateStatus(where: Prisma.ItemWhereUniqueInput): Promise<Item> {
return this.prisma.item.update({
where,
data: { status: 'SOLD_OUT' },
})
}
// --------- 略 ---------
}
Controller の@Patch
を編集
// --------- 略 ---------
@Controller('items')
export class ItemsController {
// --------- 略 ---------
@Patch(':id')
async updateStatus(@Param('id', ParseUUIDPipe) id: string): Promise<Item> {
return this.itemsService.updateStatus({ id })
}
// --------- 略 ---------
}
DELETE
delete
メソッドを実装
// --------- 略 ---------
@Injectable()
export class ItemsService {
// --------- 略 ---------
async delete(where: Prisma.ItemWhereUniqueInput): Promise<Item> {
return this.prisma.item.delete({ where })
}
// --------- 略 ---------
}
Controller の@Delete
を編集
// --------- 略 ---------
@Controller('items')
export class ItemsController {
// --------- 略 ---------
@Delete(':id')
async delete(@Param('id', ParseUUIDPipe) id: string): Promise<Item> {
return this.itemsService.delete({ id })
}
// --------- 略 ---------
}
ユーザー認証やリレーションについて続き↓