はじめに
後述の記事を参考にNest.jsのキャッチアップを行いました。
ワンポイント追加として、DockerでDB(Postgres)を構築し、そこに接続するように設定してみました。
私が普段使っているバックエンドフレームワークはLaravelですが、そもそもの設計思想の違いなのかすんなりと理解が進まなかったので備忘録として記載します。
この記事の内容はほとんど下記2記事の内容をなぞったものです。
下記の記事に従って動かないとか、どんなふうに読んでいるんだろう的な時の参考になれば幸いです。
リポジトリ
https://github.com/ryosuke-horie/nest-todo-app/
参考記事
以下の2記事を参考に進めています。
Next.js
Docker postgresql
開発環境
- Windows 10
- WSL2
- node v18.16.0
- nest v10.1.9
インストール
$ npm i -g @nestjs/cli
$ nest new NestTodoApp // npm を使用するように指定する
$ npm run start
初期画面がすごくシンプル。
データベース作成
docker-compose.yml
をルートに作成
version: '3'
services:
db:
image: postgres:14
container_name: postgres
ports:
- 5432:5432
volumes:
- db-store:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=passw0rd
volumes:
db-store:
environmentに必須項目のパスワードのみを指定
DB起動
docker-compose up -d
A5M2(DB接続ツール)を利用して、接続。
- • DBファイルのパス(
/var/lib/postgresql/data
)をvolumeにマウントして永続化しています。
ファイル削除
記事に従う。
必要パッケージのインストール
npm i @nestjs/typeorm typeorm pg @nestjs/config class-validator class-transformer
開発
構成を確認
CLIでモジュールを生成
nest g module [module名]
で自動生成できる
nest g module tasks
CLIでControllerを作成
nest g controller tasks --no-spec
--no-spec
オプションによってテスト用のファイルを生成しないようにしている。
Controllerを修正
記事の通り
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common';
@Controller('tasks')
export class TasksController {
@Get()
getTasks() {
return "getTasks Success!"
}
@Get('/:id')
getTaskById(
@Param('id', ParseIntPipe) id: number) {
return `getTaskById Success! Parameter [id:${id}]`
}
@Post()
createTask(
@Body('title') title: string,
@Body('description') description: string) {
return `createTask Success! Prameter [title:${title}, descritpion:${description}]`
}
@Delete('/:id')
deleteTask(
@Param('id', ParseIntPipe) id: number) {
return `deleteTask Success! Prameter [id:${id}]`
}
@Patch('/:id')
updateTask(
@Param('id', ParseIntPipe) id: number,
@Body('status') status: string ) {
return `updateTask Success! Prameter [id:${id}, status:${status}]`
}
}
-
@Param()
や@Body()
はHTTPリクエストを取得するためのデコレータ -
ParseIntPipe
はパイプという機能の1つ- コントローラのメソッドに値が引き渡される前に変換、もしくは検証を行います。
-
@Param('id', ParseIntPipe)
ではidを数値型へと変換
開発中はnpm run start:dev
を使えば変更が即時反映できる。
DTO・PIPEを作成してPOSTメソッドにバリデーションを追加
記事に従ってDTOとPIPEを作成
Controllerに追記して、POSTメソッドのバリデーションを追加する。
バリデーションの中身は
- statusが'OPEN','PROGRESS','DONE'のいづれかでないとエラーを返す
- TitleとDescriptionの値が空ならエラーを返す
Service作成
nest g service tasks
tasks.module.ts
に自動で追記される。
TypeORMの準備
接続設定 app.module.ts
ここはDockerを使っているので設定が一部変更している部分。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TasksModule } from './tasks/tasks.module';
@Module({
imports: [
TasksModule,
// importsに追加
TypeOrmModule.forRoot({
type: 'postgres', // DBの種類
port: 5432, // 使用ポート
database: 'postgres', // データベース名
host: 'localhost', // DBホスト名
username: 'postgres', // DBユーザ名
password: 'passw0rd', // DBパスワード
synchronize: true, // モデル同期(trueで同期)
entities: [__dirname + '/**/*.entity.{js,ts}'], // ロードするエンティティ
})
],
})
export class AppModule {}
Entitiyの作成
task.entity.tsを作成し、以下を記述
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Task extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
description: string;
@Column()
status: string;
}
この時点でA5M2で確認するとtaskテーブルが作成されている。
環境変数の更新
DBを同期するように設定しているが、本番でこの運用をするとよくないので記事に従って.envファイルを作成
TypeORMのConfigService
DBのパスワードに数字が盛り込まれている影響か、エラーが出たため、.env/default.envの値をシングルクオーテーションで囲んだ。
main.tsを書き替えてみるとDB_SYNCの値が設定されていなかったが、
NODE_ENV=development npm run start:dev
を利用することで反映されていることが確認できました。
Serviceの修正
記事に従って修正
Controllerから受け取った値で実処理するよう構成している。
async getTaskById(id: number): Promise<Task> {
const found = await this.taskRepository.findOne(id);
ここでエラー:型 'number' には型 'FindOneOptions' と共通のプロパティがありません。
が出たので以下のように仮で修正
const found = await this.taskRepository.findOne({
where: {
id: id,
},
})
テストを作成する前にModule & Controller修正に進む
エラーが出るため。
Repositoryのimportがmoduleで不足してエラーが出るため。
テストを記述して実行
passしたので完了。
感想
ふわっとREST APIを実装できました。元記事を作成された方がすごいです。
TypeORMのドキュメントを読み込む必要があるかと思います。
ワンポイント追加でDockerでDBをローカル環境に作成して利用するように設定できました。
個人的にはEntityやRepositoryといったデザインパターン的な部分についても追加で学ぶ必要があるかと思います。