Lesson 3
DBとの連携
目次
- Chapter1 初期設定(main.ts)
- Chapter2 module
- Chapter3 entity
- Chapter4 controller
- Chapter5 service
- Chapter6 repository
- Chapter7 request
- Chapter8 response
- Chapter9 MySQLとの接続
- Chapter10 DBから取得したデータを表示する(SELECT)
- Chapter11 画面から入力した内容を登録する(INSERT)
- Chapter12 画面から入力した内容で更新する(UPDATE)
- Chapter13 選択したデータを削除する(DELETE)
- Chapter14 ファイルを添付する
Lesson 3
Chapter 1
初期設定(main.ts)
NestJSの初期設定が行われている「main.ts」を中心に見ていきます。まずNestJSを立ち上げた直後の初期状態としてのディレクトリ構造は以下のようになっています。「main.ts」は「src」配下に格納されています。これから開発を行うときは主に「src」配下のファイルを触ります。
NestJSのディレクトリ構造
NestJSの基本アーキテクチャ
「main.ts」を見ていく前にNestJSの基本アーキテクチャについて学習しましょう。
NestJSの基本要素
NestJSの基本要素は以下3つの柱で成り立ちます。これらがNestJSの開発のコアとなるもっとも基本的な要素です。原則これらが揃って1つの機能ができ上がります。
- Controller
- Service
- Module
NestJSの基本構成
NestJSは画像のような構成で動きます。その中でも「main.ts」はアプリケーションのエントリーポイント即ちスタート地点と言えます。このファイルを始まりとしてNestJSアプリケーションが動作します。次に「app.module.ts」が存在します。このファイルは「ルートモジュール」と呼ばれます。ルートモジュールは開発した機能を登録して、アプリケーション内で使えるようにする役割を持ちます。反対にルートモジュールへモジュールを登録しなければアプリケーション内で使用できないため注意が必要です。次に「Featureモジュール」が存在します。「Feature」は機能や特徴という意味です。「a.module.ts」や「b.module.ts」等がそれにあたります。これらは実装者が直接実装していくモジュールになります。そのモジュールの下に「service」と「controller」があります。この2つをメインに機能を開発しFeatureモジュールに登録を行います。
以上より基本的な開発の流れとしては以下のようになります。
- FeaturesServiceとFeatureControllerを実装
- FeatureModuleに開発した機能を登録
- ルートモジュールに開発したモジュールを登録
NestJSの基本構成
main.ts
本題となるmain.ts
を見ていきます。nest new ~
のコマンドでプロジェクトを作成した人はすでにmain.ts
ができあがっているかと思います。ファイルの中身を確認してみます。main.ts
内ではNestFactory.create
というメソッドにルートモジュールを引数として渡すことによってNestJSのインスタンスを生成してくれます。インスタンスを生成することにより自動でHTTPリクエスト待機状態になります。また、await app.listen(3000);
でPORT番号の指定ができます。デフォルトは3000で引数を変えることで他のPORT番号を使用できます。
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
また、main.ts
はグローバルなモジュールを扱いたいときにそれを登録する役割を持ちます。たとえば「Pipe」と呼ばれるバリデーションモジュールをグローバルに扱いたいときは以下のように記述します。
注意
※以下は例なので実際に追加する必要はありません。
main.ts
...省略
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 追加
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
まとめ
main.ts
はそこまでたくさん触るファイルではありません。最初は初期設定のまま、必要があれば随時記述を追加します。復習としてmain.ts
の役割は下記2点です。
- アプリケーションのエントリーポイント。アプリケーションのインスタンスを生成してHTTPリクエストの待機状態を作る。
- グローバルなモジュールや設定を使用する際に追記する。

Lesson 3
Chapter 2
module
module
は「関連するControllerやServiceなどをまとめ、アプリケーションとして利用できるようにNestJSに登録する」役割を持ちます。図のようにNestJSアプリケーションには必ず1つ以上のルートモジュールと、0個以上のFeatureモジュールが前提となっています。
NestJSの基本構成
moduleの詳細
DIについて
今後「モジュールを他モジュールへと適用すること」を「DI」と呼称します。DIはDependency Injection
の略です。DIは日本語で「依存性の注入」と呼ばれます。DIは疎結合なモジュールを開発するためのデザインパターンでクラス設計の際にとくに用いられます。DIについて詳しくは本講の最後「Lesson4
Chapter10 Injectableの意味(依存性の注入/他クラスに関数を提供できる)」にて解説していますので気になる方は先にそちらをお読みください。
moduleの詳細を見ていきます。モジュールは@Module
デコレーターの宣言によりクラスとして定義されます。NestJSでは複数のFeatureモジュール
を作成しながらアプリケーションを開発します。「Feature」とつく通り機能ごとにmoduleを切っていくイメージです。極論を言えばNestJSはルートモジュールだけでの開発が可能です。1箇所にロジックをすべて書いて行けばいいからです。しかしそれは望ましいとされていません。NestJSの公式もFeatureモジュールを切って機能ごとに集まりを作り機能群をカプセル化しながら開発していくことを推奨しています。
デコレーター
デコレーターを簡単に説明すると「対象となるクラスや関数に追加効果を付与できる関数」です。@MyFunc()
形式で宣言されます。NestJSはデコレーターを用いて開発を行います。最初は記法に慣れないかもしれませんが、書きながら覚えていきましょう。
デコレーターについて
デコレーターはまだTypeScriptの実験的な機能とされており、今後仕様変更の可能性があります。
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
moduleのプロパティ
moduleで使用されるプロパティを見ていきます。
プロパティ名 | 説明 |
---|---|
providers | @Injectable デコレーターが付与されたmodule内でDIの対象となるものを記述 |
controllers | @Controller デコレーターを付与したコントローラークラスを記述 |
imports | 外部モジュールを記述 |
exports | 外部で扱いたいservice などを記述 |
providers
@Injectable
デコレーターが付与されたmodule内でDIの対象となるものを記述します。今後登場しますが、service
やrepository
などが挙げられます。
controllers
@Controller
デコレーターを付与したコントローラークラスを記述します。Controller
はルーティングの機能を担います。
imports
module内で扱いたい外部モジュールを記述します。たとえばDBと接続したい場合、DBモジュールを登録します。また、Featureモジュールをルートモジュールに登録する際はimports
に記述します。
exports
imports
もしくはproviders
に記述した機能のうち外部モジュールでも扱いたいものを記述します。認証機能などモジュールを跨いで横断的に使用したいものはexports
に記述します。
ディレクトリの整理
Todo moduleを作成する前にディレクトリを整理します。まずは不要なファイルを削除します。「app」系のファイルはapp.module.ts
のみ使用するためそれ以外を削除します。
~/src
rm app.controller.spec.ts app.controller.ts app.service.ts
また、上記ファイル群を削除したためapp.module.ts
の内容を修正します。下記は修正後の内容です。
app.module.ts
import { Module } from '@nestjs/common';
@Module({
imports: [],
controllers: [],
providers: [],
})
export class AppModule {}
次に「modules」ディレクトリを作成し、app.module.ts
を格納します。
~/src
mkdir modules
mv app.module.ts modules
また、「app.module.ts」ファイルの場所を移動したため、「main.ts」を以下に修正します。
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './modules/app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
最終的に「src」の内容が下記画像のようなディレクトリ構造になっていれば準備完了です。
/src内構造
Todo moduleの作成
「Todo module」を作成します。moduleを作成するには2通り方法があります。1つがファイルを自分で作成して1から実装していく方法です。もう1つがNestCLI
を利用してコマンドで作成する方法です。本講座は後者を用いて作成します。下記コマンドでTodo moduleを作成しましょう。
~/nest-todo
nest g module modules/todo
ディレクトリ構造について
作成するTODOアプリのディレクトリ構造については「Lesson1 Chapter5本講における実装方針」にて解説しています。
「g」は「generate」の略です。このコマンドだけでmoduleが作成できます。「src」配下を見て見ましょう。画像のように「modules/todo/todo.module.ts」ができていれば成功です。
Todo moduleの内容
作成したmoduleはプロパティがまだ存在しません。こちらは後でController
やService
を作成した際に登録します。
todo.module.ts
import { Module } from '@nestjs/common';
@Module({})
export class TodoModule {}
Todo moduleの登録
moduleを作成したのでルートモジュールに登録する必要があります。しかし、「app.module.ts」の内容を見るとすでに登録されているのが確認できます。NestCLI
を使うことで作成したmoduleの登録まで行なってくれます。
app.module.ts
import { Module } from '@nestjs/common';
import { TodoModule } from './todo/todo.module';
@Module({
imports: [TodoModule],
controllers: [],
providers: [],
})
export class AppModule {}
まとめ
これでmodule
については以上になります。module
は機能を作成するときの基本単位となることを意識しながら開発を行なっていきましょう。

Lesson 3
Chapter 3
entity
このChapterではTodo Entityの作成を通してEntity
について学びます。
Entity(エンティティ)
プログラミングの世界で、エンティティとは対象とする「実体」に対する「抽象概念」として定義づけられます。「実体」とはRDBの「テーブル」そのものです。プログラミングの世界でエンティティはRDBの文脈で用いられることがほとんどです。そしてプラグラミングの世界で「抽象化」といえば「型」や「クラス」のことです。つまりエンティティは「テーブルの型やクラス」のことを指します。
エンティティの役割は以下2点です。
- 「実体」を生成する
- 「参照」に使用する
エンティティは「実体」である「テーブル」を作成するときの元情報となります。テーブル名とカラム名(属性名)が定義されたクラスを元にテーブル(実体)が生成されます。
「参照」に使用するプログラムからテーブル情報を参照したい時エンティティとして定義されているクラスを参照します。クラスはあくまで「抽象概念」なので実際にテーブルにアクセスするための具体的なプログラムは裏で動きます。
Entity with NestJS
NestJSではEntity
クラスを定義することでRDBのテーブル定義と同等のことを行えます。「抽象概念」として定義したtodo.entity.ts
をもとにテーブルが作成されます。また、todo.entity.ts
はJavaScriptのオブジェクトとRDBのデータ構造をマッピングしてくれるクラスとしても働きます。マッピングしてくれることでJavaScriptのオブジェクトを扱うようにRDBを参照・操作できるようになります。
NestJSとエンティティ
Todo Entityの作成
Todo Entityを作成しRDBのテーブル定義を行なってみましょう。「src」ディレクトリの配下に「entities」ディレクトリを作成します。「entities」ディレクトリ配下に「todo.entity.ts」ファイルを作成します。コマンドも載せておきます。
~/nest-todo/src/
mkdir entities && cd entities
touch todo.entity.ts
Todo Entity
「todo.entity.ts」ファイルでTodo Entityを定義します。まずは@Entity
デコレーターを付与したTodoクラス
を定義します。クラス名がテーブル名に反映されます。
todo.entity.ts
import { Entity } from 'typeorm';
@Entity()
export class Todo {}
カラムの定義
次にTodoテーブルのカラムとなるプロパティを定義します。カラムは「型」と「デコレーター」によって成り立ちます。
todo.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Todo {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 128, nullable: false })
title: string;
@Column({ default: false, nullable: false })
isCompleted: boolean;
@Column({ nullable: false })
createdAt: string;
@Column({ nullable: false })
updatedAt: string;
}
@PrimaryGeneratedColumn('uuid')
主キーとして定義するカラムに付与するデコレーターです。引数に何も付与しない場合number型の自動採番になります。今回はUUID型
としています。
自動採番
「1, 2, 3...」と前の数字にしたがって自動で増加していく数字のことです。
uuid
uuidとは全世界でいっさい被らないとされている一意なIDのことです。
@Column()
このデコレーターを付与することでEntityにカラムであることを認識させることができます。主キー以外には必須となります。
Entity(テーブル)の内容
カラム名 | 説明 |
---|---|
id | Todoごとの一意なID・識別子。参照や削除の際に使用する。 |
title | Todoのタイトル。最高128文字。 |
isCompleted | Todoの完了フラグ。デフォルトはfalse 。
|
createAt | Todoの作成日。 |
updatedAt | Todoの更新日。 |
エンティティのbooleanとテーブルのtinyint(1)
RDBのテーブルでは真偽値は「0/1」で表されます。これをMySQLではtinyint(1)
型と呼びます。0がfalse
で1がtrue
です。
まとめ
Entity
の作成まで行うことができました。今後このEntity
を使用しながら開発を行なっていきます。また本格的にRDBを使用する際にはEntity
をもとにテーブルを作成することを行います。

Lesson 3
Chapter 4
controller
このChapterではTodo Controllerの作成を通してcontroller
について学びます。
controller
controller
は「Lesson1 Chapter4 MVCモデル」でModel
とView
に指示を出したりデータを渡すための司令塔であり、「ルーティング」を主に担当すると説明しました。また、3層アーキテクチャのプレゼンテーション層としてクライアントからの情報をビジネス層(Service)に渡します。ルーティングは、「どのコントローラーがどのリクエストを受信するか」を制御します。多くの場合、各コントローラーごとに異なるパスを担当し異なるアクションを実行できます。
Todo Controller
早速Todo Controllerを作成します。以下コマンドを実行しましょう。自動で「controllers/todo/todo.controller.ts」ができあがります。--no-spec
オプションでテストファイル作成を回避できます。現時点では必要ないのでオプション付与で作成しましょう。
~/src
nest g controller controllers/todo --no-spec
moduleへの登録
controller
を作成したらmodule
に登録する必要があります。「todo.module.ts」に下記のように登録します。
todo.module.ts
import { Module } from '@nestjs/common';
import { TodoController } from 'src/controllers/todo/todo.controller';
@Module({
// 追加
controllers: [TodoController],
})
export class TodoModule {}
Controller 初期状態
Todo Controllerの初期ファイルを見てみます。
todo.controller.ts
import { Controller } from '@nestjs/common';
@Controller('todo')
export class TodoController {}
NestJSからController
をimportします。@Controller
というデコレーターを付与することでコントローラークラスを定義できます。@Controller
は任意で引数を取ることができます。引数はパスを表します。Todo
Controllerは「/todo」でリクエストパスがグループ化されます。
レスポンスを返す
「/todo」へのGETリクエストに対して「Hello Todo!」という文字列を返すコードを書いてみます。書き上げたらnpm run start:dev
でサーバーを起動し「http://localhost:3000/todo/」へアクセスしましょう。「Hello
Todo!」とレスポンスが返れば成功です。
todo.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller('todo')
export class TodoController {
@Get()
hello() {
return 'Hello Todo!';
}
}
constructor
次章でService
を作成するため下記コードはあくまで例になります。実際のコードに記述する必要はありません。
constructor
constructor(private readonly appService: AppService) {}
constructor
へはDI(依存性の注入)を行いたいService
等を記述します。Service
のインスタンスではなくService
クラスを渡していることがポイントです。DIにより疎結合な関係を維持しています。
DIについて
今後「モジュールを他モジュールへと適用すること」を「DI」と呼称します。DIはDependency Injection
の略です。DIは日本語で「依存性の注入」と呼ばれます。DIは疎結合なモジュールを開発するためのデザインパターンでクラス設計の際にとくに用いられます。DIについて詳しくは本講の最後「Lesson4
Chapter10 Injectableの意味(依存性の注入/他クラスに関数を提供できる)」にて解説していますので気になる方は先にそちらをお読みください。
@Get()
@Get()
@Get()
hello() {
return 'Hello Todo!';
}
@Get
デコレーターを使用することでコントローラー内のメソッドをGETリクエストに対応させることができます。このときのルートパスは@Controller
の引数に指定した値です。本コントローラーの場合「http://localhost:3000/todo」へGETリクエストが来た場合hello
を実行します。
また、@Get
の引数により@Controller('todo')
の「/todo」配下でパスを自由に生成できます。「http://localhost:3000/todo/hello」にリクエストを対応させたい場合下記になります。
http://localhost:3000/todo/hello
@Get('hello')
hello() {
return 'Hello Todo!';
}
動的なパラメーター
「/todo」配下で動的なパラメーターを作成できます。たとえば取得したいTODOを絞りたい時に使えます。以下は「http://localhost:3000/todo/hoge」や「http://localhost:3000/todo/foo」といった「/todo」配下を動的なパラメーターに対応させることができます。「http://localhost:3000/todo/hoge」とリクエストを送るとレスポンスが確認できます。
todo.controller.ts
...省略
@Get(':id')
hello() {
return 'Hello Todo!';
}
...省略
まとめ
以上がcontroller
の基本的な動きになります。今後もservice
等を導入したり、アプリを作成しながらcontroller
の仕組みを学んでいきましょう。

Lesson 3
Chapter 5
service
このChapterではTodo Serviceの作成を通してservice
について学びます。
service
service
は「Lesson1 Chapter4 MVCモデル」でModel
に該当しビジネスロジックを担当すると説明しました。ビジネスロジックとはアプリケーション独自の要件を実装するコアロジックのことです。また、3層アーキテクチャのビジネス層としてプレゼンテーション層のcontroller
から受け取った情報を元にビジネスロジックを構築したり、データアクセス層のrepository
を操作してDBからの情報を処理します。
controller, service, repositoryの関係図
Todo Service
早速Todo Serviceを作成します。以下コマンドを実行しましょう。自動で「services/todo/todo.service.ts」ができあがります。
~/src
nest g service services/todo --no-spec
moduleへの登録
service
を作成したらmoduleに登録する必要があります。「todo.module.ts」に下記のように登録します。providers
へはrepository
等も後で登録します。
todo.module.ts
import { Module } from '@nestjs/common';
import { TodoController } from 'src/controllers/todo/todo.controller';
import { TodoService } from 'src/services/todo/todo.service';
@Module({
controllers: [TodoController],
// 追加
providers: [TodoService],
})
export class TodoModule {}
Service 初期状態
Todo Serviceの初期ファイルを見てみます。
todo.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class TodoService {}
NestJSからInjectable
をimportします。@Injectable
というデコレーターを付与することで他クラスにDI
可能なクラスとして定義づけできます。service
はControllerから参照します。そのためControllerにDI
可能なように@Injectable
デコレーターを付与します。@Service()
デコレータ等は使用せず、service
であることをファイル名から判断します。
@Service()
ではないことに注意
controllerへの登録
service
をcontroller
から使用ためには2つのステップがあります。
module
へ登録controller
へ登録(DI
)
module
(todo.module.ts)に登録することでcontroller
へのDI
の仕組みを利用できるようになります。2.
controller
のconstructor
の引数へ代入しDI
します。1は「moduleへの登録」で実装済みであるため、2を下記コードで記述します。
serviceとDI
service
を扱う上でDI
はとても重要な概念であるためこの講座の最後に詳しく解説します。DI
の説明の前に一度処理の流れを実装してその流れを元に解説を行います。
todo.controller.ts
import { Controller, Get } from '@nestjs/common';
import { TodoService } from 'src/services/todo/todo.service';
@Controller('todo')
export class TodoController {
// 追加
constructor(private readonly todoService: TodoService) {}
...省略
}
private修飾子
private修飾子を付与したプロパティはクラスの中でのみ利用できるプロパティとして登録できます。
readonly修飾子
readonly修飾子を付与したプロパティは中身を変更することはできず参照のみとなります。
serviceのビジネスロジック
service
にビジネスロジックを記述します。
todo.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class TodoService {
hello(): string {
return 'Hello todo with service!';
}
}
serviceのビジネスロジックをcontrollerから利用
次にservice
のビジネスロジックをcontroller
から利用します。今回はcontroller
に元々書いていた処理をservice
に移しただけになります。この状態で「http://localhost:3000/todo/」へGETリクエストを飛ばすと「Hello
todo with service!」という文字列が返ります。
todo.controller.ts
import { Controller, Get } from '@nestjs/common';
import { TodoService } from 'src/services/todo/todo.service';
@Controller('todo')
export class TodoController {
constructor(private readonly todoService: TodoService) {}
@Get()
// 変更
hello(): string {
return this.todoService.hello();
}
}
serviceとcontrollerそれぞれの責務
今回のビジネスロジックは呼ばれると文字列を返すだけの簡単なものでした。かつcontroller
から処理をそっくりそのまま移しただけでした。このことからservice
に書くロジックはcontroller
へ直接書くことができます。しかし、controller
の責務は「ルーティングを行うこと」でした。責務を無視してcontroller
にビジネスロジックを書いてしまうとcontroller
の中身がファットになりいずれコードは崩壊してしまいます。崩壊を逃れるために3層アーキテクチャのように層に分けてアプリケーションを考える設計が存在します。「保守性・拡張性」に優れたコードへするために層の責務はしっかり意識しながらコーディングしていきましょう。
Dependency Injection(依存性の注入)
度々登場しているDI(Dependency Injection)
について解説します。DI
は日本語で「依存性の注入」と訳されます。DI
の目的は「疎結合なモジュール」を実現することです。「疎結合」とは「変更が依存先に影響しないこと」を指します。今回の実装を元に解説します。
controllerからserviceを利用
controllerからserviceを利用するには、constructorの引数としてserviceを代入していました。TodoService
は型として利用しています。それをメソッドの中で参照しています。
todo.controller.ts
constructor(private readonly todoService: TodoService) {}
@Get()
hello(): string {
// 利用
return this.todoService.hello();
}
これは下記のようにしても同じ動きが再現できます。「http://localhost:3000/todo/」へGETリクエストを飛ばすと「Hello todo with service!」の返却が確認できます。
todo.controller.ts
@Controller('todo')
export class TodoController {
todoService: TodoService;
constructor() {
this.todoService = new TodoService();
}
@Get()
hello(): string {
return this.todoService.hello();
}
}
controllerとserviceが密結合な状態
「インスタンス化するTodoService
を変更したい」という要望があるとどうなるでしょうか?たとえば本番用のTodoService
とテスト用のTodoService
が別れている場合、環境に合わせてcontrollerの中身を変更する必要が出てきます。環境に合わせてクラスを変更する理由としては本番環境独自のロジック(課金等)が混ざっているパターンがあるからです。
todo.controller.ts(テスト用)
constructor() {
this.todoService = new TodoServiceForTest();
}
todo.controller.ts(本番用)
constructor() {
this.todoService = new TodoServiceForProduction();
}
controller内で環境に合わせたif文を書いてインスタンス化する対象を変えればいいかもしれません。しかしそれではcontrollerの関心事が増えてしまいます。controllerは「ルーティング」に集中させるべきです。環境の違いを知る必要はありません。controllerやserviceはアプリケーションを支えるコアな機能です。であればアプリケーションを動かすための本質的な処理に集中させるべきです。境の違いはmoduleやそれを統括する「main.ts」等が気にするべきことです。
このように要件の変更等によって「依存している対象を変更する必要が出てくる」ような状態を「密結合」といいます。今回の場合はcontrollerとserviceが依存しているため「controllerとserviceは密結合な状態である」と言えます。
Dependency Injection(DI)による疎結合化
この問題を解決するためのデザインパターンがDependency Injection(DI)
です。DIを用いると依存先を疎結合に注入できます。
controllerのconstructorに着目します。constructorは引数でTodoServiceのインスタンスを受け取るようになっています。つまり型が同じであれば異なるインスタンスを受け取ることができる状態にあります。インスタンスがテスト用か本番用か気にする必要はありません。このようにインスタンスを引数で渡して疎結合な状態を作ることをDependency Injection
と呼びます。
todo.controller.ts
constructor(private readonly todoService: TodoService) {}
DIコンテナ
疑問に思うことが1つあります。controllerとserviceのインスタンスはどこで作られているのでしょうか?どこにもnew
しているような記述がありません。通常下記のようにしてインスタンスを生成するかと思います。controllerをインスタンス化してそこにインスタンス化したserviceを渡します。しかしそのコードはどこにも見当たりません。ではなぜDI機構が使用できているのでしょうか?
例
const todoController = new TodoController(new TodoService())
答えは「その詳細をNestJSがラップしてくれているから気にする必要はない」です。「todo.module.ts」を見てみます。インスタンス化はしていませんがcontrollerやserviceを登録しています。moduleに登録することでNestJSが必要に応じてDIしてくれます。これはDIを採用しているフレームワークによくみられる手法で「DIコンテナ」と呼ばれます。NestJSのフレームワークの仕組みの恩恵により私たちはDIをとても簡単に実現することができています。
todo.module.ts
@Module({
controllers: [TodoController],
providers: [TodoService],
})
まとめ
このChapterでは「serviceとは」、「controllerでの使い方」、「Dependency Injection(依存性の注入)」(DI)について説明しました。serviceの処理の詳細は簡単なものでしたがDBとの連携を行う際にビジネスロジックをたくさん書いていきます。また、DIは今後頻繁に登場する用語になります。次はデータアクセス層である「repository」について解説します。

Lesson 3
Chapter 6
repository
このChapterではTodo Repositoryの作成を通してrepositoryについて学びます。
repository
repositoryはserviceとDBとの仲介役を行うクラスのことです。DBを参照し、データをserviceに返します。repositoryはEntityを元に生成されます。Entityの情報をもとにDBを操作するためです。repositoryを作成する際はEntityがかならず必要です。repositoryは3層アーキテクチャの中で「データアクセス層」を担当します。
repositoryの定義
repositoryを作成します。もっとも簡単なのはserviceにそのままDIすることです。他にはrepository用のファイルを作成して定義したカスタムrepositoryクラスをserviceにDIすることもできます。まず、前者をコードで見てから後者を実装します。
repositoryの定義 serviceに直接編
まずはrepository用のファイルを作成せずserviceで定義してみます。InjectRepository
を@nestjs/typeorm
からimportします。そしてconstructor
の中でInjectRepository
を用いてrepositoryをDIします。
todo.repository.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Todo } from 'src/entities/todo.entity';
import { Repository } from 'typeorm';
@Injectable()
export class TodoService {
constructor(
@InjectRepository(Todo) private todoRepository: Repository<Todo>,
) {}
hello(): string {
return 'Hello todo with service!';
}
}
...省略
次にrepositoryを使用するための新しいメソッドを定義します。Todoの一覧を取得するメソッドを追加します。serviceの中ではDIしたtodoRepository
をthis
で参照しながら開発します。
todo.service.ts
...省略
@Injectable()
export class TodoService {
constructor(
@InjectRepository(Todo) private todoRepository: Repository<Todo>,
) {}
hello(): string {
return 'Hello todo with service!';
}
getAllTodos() {
return this.todoRepository.find();
}
}
...省略
ここでgetAllTodos
にホバーしてみましょう。すると返り値の型が表示されます。repositoryはDB操作をラップしてくれているためあらかじめ複数のメソッドが定義されています。find
はすべての一覧を取得するメソッドです。
findの返り値型
他のメソッドが気になる方はRepository<Todo>
のRepositoryにカーソルを当ててF12を押すことで定義されているメソッドの型を見ることができます。Entity
は今回だとTodo
に該当します。
Repository.d.ts(findメソッドの型定義の例)
/**
* Finds entities that match given find options.
*/
find(options?: FindManyOptions <Entity>): Promise<Entity[]>;
返り値の方は明示的に書いたほうがわかりやすいため追記しておきましょう。またこの場合Promise
を返すためasync/await
を付与して同期的に扱いやすいようにします。
todo.service.ts
...省略
@Injectable()
export class TodoService {
...省略
async getAllTodos(): Promise<Todo[]> {
return await this.todoRepository.find();
}
}
...省略
custom repository
なぜカスタムrepositoryを作成するのか
目的はメソッドのオーバーライドです。オーバーライドとは上書きのことです。アプリケーションを作成する中で元あるメソッドの処理に手を加えたい時が出てきます。そのためにカスタムrepositoryを作成します。
カスタムrepositoryは元のRepositoryクラスを継承するため元のRepositoryと同様の振る舞いが可能です。そのため定義したからといって無理にメソッドを定義する必要はありません。必要なものだけオーバーライドします。また、DBとの連携をまだ行なっていないため今回はカスタムrepositoryを定義するだけになります。メソッドのオーバーライドはDBとの連携を行う際改めて説明します。
カスタムrepositoryファイルの作成
カスタムrepositoryは別ファイルに切り分けることで定義できます。下記コマンドで作成します。
~/src
mkdir repositories && cd repositories
touch todo.repository.ts
todo.repository.ts
作成したカスタムrepositoryを編集します。
@Injectable()
デコレーターを付与してDI可能クラスを生成します。- TodoRepositoryをクラス宣言しRepositoryクラスを継承しRepositoryのプロパティへアクセス可能にします。
constructor
で元のRepositoryクラスをDIし、このカスタムrepositoryクラスをrepositoryとして振る舞うようにします。constructor
でsuper
を呼び出して継承元のRepositoryクラスのconstructor
関数を実行します。
todo.repository.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Todo } from 'src/entities/todo.entity';
import { Repository } from 'typeorm';
@Injectable()
export class TodoRepository extends Repository<Todo> {
constructor(@InjectRepository(Todo) repository: Repository<Todo>) {
super(repository.target, repository.manager, repository.queryRunner);
}
}
todo.module.ts
作成したカスタムrepositoryを他クラスにDIできるようにproviders
へ登録します。
todo.module.ts
import { Module } from '@nestjs/common';
import { TodoController } from 'src/controllers/todo/todo.controller';
// TodoRepositoryインポート
import { TodoRepository } from 'src/repositories/todo.repository';
import { TodoService } from 'src/services/todo/todo.service';
@Module({
controllers: [TodoController],
// TodoRepository追加
providers: [TodoService, TodoRepository],
})
export class TodoModule {}
todo.service.ts
実際にserviceでDIします。あとは通常のrepositoryのようにTodoRepository
を扱います。
todo.service.ts
import { Injectable } from '@nestjs/common';
import { TodoRepository } from 'src/repositories/todo.repository';
@Injectable()
export class TodoService {
// カスタムrepositoryをDI
constructor(private todoRepository: TodoRepository) {}
hello(): string {
return 'Hello todo with service!';
}
async getAllTodos() {
return await this.todoRepository.find();
}
}
repository使用時の注意点
repositoryはDBとの連携を行うために使用されます。現在はDBとの連携処理を書いていないため現状のソースコードでサーバーを立ち上げるとエラーとなってしまいます。DBとの連携は「Lesson3 Chapter12 MySQLとの接続」で行うため現場追加したコードはコメントアウトしておきましょう。
todo.module.ts
import { Module } from '@nestjs/common';
// コメントアウト
// import { TypeOrmModule } from '@nestjs/typeorm';
import { TodoController } from 'src/controllers/todo/todo.controller';
// コメントアウト
// import { Todo } from 'src/entities/todo.entity';
// コメントアウト
// import { TodoRepository } from 'src/repositories/todo.repository';
import { TodoService } from 'src/services/todo/todo.service';
@Module({
imports: [],
controllers: [TodoController],
// コメントアウト
// providers: [TodoService, TodoRepository],
providers: [TodoService],
})
export class TodoModule {}
todo.service.ts
import { Injectable } from '@nestjs/common';
// コメントアウト
// import { TodoRepository } from 'src/repositories/todo.repository';
@Injectable()
export class TodoService {
// コメントアウト
// constructor(private todoRepository: TodoRepository) {}
hello(): string {
return 'Hello todo with service!';
}
// コメントアウト
// async getAllTodos() {
// return await this.todoRepository.find();
// }
}
まとめ
今回はカスタムrepositoryの定義まで行いました。今後要件に応じて元のRepositoryのメソッドをオーバーライドするタイミングが出てきます。DBとの連携を行うときにまた詳しく説明します。

Lesson 3
Chapter 7
request
このChapterではクライアントから渡されるリクエストパラメーターやリクエストBodyをNestJSではどう扱うかについて学んでいきます。Chapterの流れとしては下記になります。
- リクエストパラメーター
- リクエストBody
- DTOを使ったリクエストBody
DTOについて
DTOについては扱う箇所で詳細に解説します。
リクエストパラメーター
クライアントからパスやクエリなどどのように受け取るか学びます。
クエリパラメーター
クエリパラメーターは@Query()
デコレーターを付与することで取得できます。以下のように書いてみてnpm run start:dev
でサーバーを立ち上げたら「http://localhost:3000/todo?page=10」でリクエストを飛ばしてみましょう。コンソールに「10」と表示されます。
todo.controller.ts
...省略
@Controller('todo')
export class TodoController {
constructor(private readonly todoService: TodoService) {}
@Get()
findAll(@Query('page') page?: string) {
console.log(page);
}
}
次にオブジェクトでも取得してみましょう。下記のようにコードを変更してください。{ page: 10 }
がコンソールに表示されていることが確認できます。
todo.controller.ts
@Get()
findAll(@Query() query: { page?: string }) {
console.log(query);
}
また、複数のプロパティでも取得できます。下記のように変更してみましょう。「http://localhost:3000/todo?page=10&id=aaa」でリクエストを送ると{ page: 10, id: 'aaa' }
がコンソールに表示されていることが確認できます。
todo.controller.ts
@Get()
findAll(@Query() query: { page?: string, id?: string }) {
console.log(query);
}
クエリパラメーターはオプショナルパラメーター
クエリパラメーターはオプショナルなパラメーターなため{ page?: string }
のような型定義を行います。オプショナルとは「任意の」という意味です。オブジェクトにプロパティが含まれていなくても問題ありません。
パスパラメーター
パスパラメーターは@Param()
デコレーターを付与することで取得できます。新しくTodo詳細取得用のfindById
メソッドを定義します。「http://localhost:3000/todo/aaa」でリクエストを送ると「aaa」という文字列がターミナルに表示されます。
todo.controller.ts
@Get(':id')
findById(@Param('id') id: string) {
console.log(id);
}
クエリと同じようにオブジェクトでも取得します。
todo.controller.ts
@Get(':id')
findById(@Param() param: { id: string }) {
console.log(id);
}
パスパラメーターは必須パラメーター
クエリパラメーターがオプショナルであることに対してパスパラメーターは必須パラメーターです。データを取得するために必須のパラメーターをパスに含めます。
リクエストBody
リクエストBodyは@Body()
デコレーターを付与することで取得できます。新しくPOSTでcreate
メソッドを作成します。
todo.controller.ts
@Post()
create(@Body('title') title: string) {
console.log(title);
}
Thunder ClientのBody/Form-encodeに「title」というフィールドを追加して好きな値を追加し「http://localhost:3000/todo/」へリクエストを送りましょう。「title」を含んだオブジェクトが取得できます。
Thunder Client POST リクエスト
複数指定したいときは下記のようにできます。
todo.controller.ts
@Post()
create(
@Body('title') title: string,
@Body('isCompleted') isCompleted: boolean,
) {
console.log(title);
console.log(isCompleted);
}
オブジェクトの指定も可能です。
todo.controller.ts
@Post()
create(@Body() body: { title: string; isCompleted: boolean }) {
console.log(body);
}
DTOを用いたリクエストBodyのリファクタリング
ここからリクエストBodyにDTOを適用します。
DTO
DTO(Data Transfer Object)はソフトウェア開発において「データの受け渡しのために使われるオブジェクト」のことを言います。「Transfer」は日本語で「転送・転移」を意味する英単語です。ソフトウェア開発において「データの受け渡し」は「リクエストとレスポンス」にて行われます。
DTOの構造
DTOはクラスで定義します。デコレーターでバリデーションの機能を付与したいからです。DTOは「型定義+バリデーション」の構造で成り立ちます。このChapterでは型定義のみを行います。
バリデーション
バリデーションとは「形式チェック」のことです。たとえばメールアドレスは「メールアドレスの形式(@が含まれているか等)」であるか、パスワードは英数字8文字以内かなど入力値の最低限の形式を満たしているかチェックをすることをバリデーションと呼びます。
DTOを定義する
TODO作成時のリクエストDTOを作成します。今回はバリデーションがついていない純粋な型定義のみのDTOを定義します。「src」配下に「requests」というディレクトリを作りそこに格納します。また、TODO用のDTOを定義するため「requests」配下に「todo」ディレクトリを切ってそこでファイルを管理します。
~/src
mkdir requests && cd requests
mkdir todo && cd todo
touch create.dto.ts
DTOを定義します。TODOのタイトルは必須ですが完了未完了は任意としてます。完了未完了がない場合は未完了(false)で定義するようにします。
create.dto.ts
export class CreateTodoRequestDTO {
title: string;
isCompleted?: boolean;
}
DTOルートファイルの作成
DTOを一括でエクスポートする用のルートファイル作成します。ルートファイルのメリットとしてはインポート元がひとつになる、インポート元がひとつになることでインポート箇所の記述がシンプルになる、格DTOファイル名の変更がルートファイルのみで済む等です。デメリットはルートファイルでエクスポートするファイルが互いに参照し合わないように気をつけないといけない点です。循環参照でエラーとなってしまうためです。実際に使用して確かめてみましょう。
~/src/requests/todo
touch index.ts
~/src/requests/todo/index.ts
export * from './create.dto';
DTOの適用
ControllerにDTOを適用しServiceに接続します。「todo.service.ts」にcreate
メソッドは存在しないため型エラーが出ますが次で作成するためそのままで大丈夫です。
todo.controller.ts
...省略
import { CreateTodoRequestDTO } from 'src/requests/todo';
...省略
@Post()
create(@Body() createDto: CreateTodoRequestDTO) {
this.todoService.create(createDto);
}
Serviceでもcreate
メソッドを定義してDTOを受け取ります。今回はconsoleに表示しましょう。この状態で「http://localhost:3000/todo」へPOSTリクエストを送りましょう。送った内容がconsoleに表示されればOKです。
todo.service.ts
...省略
import { CreateTodoRequestDTO } from 'src/requests/todo';
...省略
@Injectable()
export class TodoService {
// constructor(private todoRepository: TodoRepository) {}
hello(): string {
return 'Hello todo with service!';
}
// async getAllTodos() {
// return await this.todoRepository.find();
// }
// 追加
create(createDto: CreateTodoRequestDTO) {
console.log(createDto);
}
}
まとめ
今回はDTOについて解説しました。DTOにはバリデーションも含まれるのですがそれは「Lesson4 Chapter4 バリデーションの実装」にて解説しています。次はResponseにもDTOを定義します。

Lesson 3
Chapter 8
response
responseにもDTOを定義していきましょう。
レスポンスDTOを作成する
ファイルの作成
TODO作成時のレスポンスDTOを作成します。リクエストと同じくバリデーションがついていない純粋な型定義のみのDTOを定義します。前回同様ディレクトリとファイルを作成します。
~/src/
mkdir response && cd response
mkdir todo && cd todo
touch create.dto.ts
touch index.ts
DTOの定義
TODOを作成したときのレスポンスは作成したTODOをそのまま返すようにします。
~/src/response/create.dto.ts
export class CreateTodoResponseDTO {
id: string;
title: string;
isCompleted: boolean;
createdAt: string;
updatedAt: string;
}
ルートファイルにも記述を追加します。
~/src/response/index.ts
export * from './create.dto';
レスポンスDTOの適用
serviceとcontrollerにそれぞれDTOを適用します。
serviceへの適用
のちのDBとの接続時にはPromiseを返しますが、現在はシンプルな形を保ちたいので下記のように定義しておきましょう。
~/src/services/todo/todo.service.ts
...省略
import {
CreateTodoResponseDTO,
FindAllTodoResponseDTO,
FindOneTodoResponseDTO,
} from 'src/response/todo';
...省略
create(createTodoDto: CreateTodoRequestDTO): CreateTodoResponseDTO {
return {
id: 'test id',
title: createTodoDto.title,
isCompleted: createTodoDto.isCompleted,
createdAt: 'test createdAt',
updatedAt: 'test updatedAt',
};
}
controllerへの適用
controllerへも同様に適用します。レスポンス型を返すようにしたらserviceを返すようにしましょう。
~/src/services/todo/todo.controller.ts
@Post()
create(@Body() createDto: CreateTodoRequestDTO): CreateTodoResponseDTO {
return this.todoService.create(createDto);
}
まとめ
レスポンスDTOの適用は以上になります。今後DBとの接続でserviceやcontrollerが増えていきますのでそれ必要に応じてDTOを増やします。

Lesson 3
Chapter 9
MySQLとの接続
このChapterではDB(MySQL)とNestJSを接続します。DBとの接続完了をこのChapterではゴールとします。DBとの接続は設定項目が多く大変ですが頑張りましょう。
パッケージのインストール
環境変数周りの設定に必要なパッケージをインストールします。
~/nest-todo
npm i @nestjs/config
コンフィグの設定
「ConfigModuleの設定」では環境変数を設定し、ConfigModuleの設定を完了させることを目標とします。
環境変数
アプリケーション(以下App)の実行は開発、本番など異なる環境で行われます。その際DBへの接続情報は異なることが多いです。DBへの接続情報はサーバー内へ変数として配置することでソースコード等から外部に公開できない仕組みとし、それをAppから参照するという方法をよく取られます。それを実現する仕組みが環境変数です。NestJSでは.env
ファイルをプロジェクト配下に配置することで環境変数をサーバーに公開・利用できます。
環境変数の設定
環境変数設定の用のファイルを作成します。今回は開発用環境変数になるため名前を「.development.env」としています。
~/test-todo
touch .development.env
git利用時の注意点
gitを利用している場合、そのままだとenvファイルがGithub等に公開されてしまいます。公開されないように「.gitignore」にenvファイルを含める必要があります。今回は開発用のenvファイルなので公開しても構いませんが本番用のenvファイルが公開されないように*.env
のように記述を追加しましょう。
DBへの接続情報を「.development.env」へ追加します。
.development.env
// ローカルでDBを立ち上げている人はlocalhost, docker等使用している方はdocker-composeファイル等で指定したホスト名
DB_HOST=localhost
// 他と被らないport番号
DB_PORT=3310
// DBログインユーザー
DB_USER=root
// DBログイン用パスワード
DB_PASSWORD=password
// 今回使用するDB名(任意)
DB_NAME=nesttodo
configurationファイルの作成
初めにインストールした@nestjs/config
は環境変数をNestJSで扱いやすくするパッケージです。CoNfigModule
をパッケージからインポートしてDIする必要があります。また、今回カスタムconfigファイルを作成しそこから環境変数の値を取得します。
~/src
mkdir config && cd config
touch configuration.ts
カスタムconfigファイルは環境変数を意味のあるまとまりとして管理するため使用します。下記のように記述することで通常configService.get('DB_HOST')
とアクセスするところをconfigService.get('db.host')
のようにアクセスできるようになります。今回は環境変数の種類が少ないためメリットを感じにくいかもしれませんが、このように管理することでコードを見る人に意図を伝えやすく実装上のミスも起きにくくなります。
~/src/config/configuration.ts
export default () => ({
db: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 3306,
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'password',
name: process.env.DB_NAME,
},
});
ConfigModule 設定
ConfigModuleを使う準備が整ったので「app.module.ts」にて設定を行います。ConfigModule設定の最小構成は下記になります。forRoot
メソッドの引数に設定を追加します。
~/src/modules/app.module.ts
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [ConfigModule.forRoot()],
})
envFilePath
プロパティで環境変数格納ファイルを指定し、
load
プロパティに先ほど作成した「configuration.ts」を指定してカスタムコンフィグファイル経由で環境変数アクセスを可能にします。
~/src/modules/app.module.ts
...省略
import { ConfigModule } from '@nestjs/config'; // 追加
import configuration from 'src/config/configuration'; // 追加
...省略
@Module({
imports: [
TodoModule,
// 追加
ConfigModule.forRoot({
envFilePath: '.development.env',
load: [configuration],
}),
]
})
DB接続設定(TypeORMModule)
DB接続のための準備は整ったのでTypeORMModuleの設定を行いDBへ接続します。下記は現在の「app.module.ts」の内容です。
TypeOrmModule.forRootAsync
メソッドの引数にDBへの接続情報を渡します。また、TypeORMModuleの中でConfigModuleを使用するためimports: [ConfigModule]
の指定をし、ConfigServiceをinject: [ConfigService]
でDIしています。
プロパティ名 | 説明 |
---|---|
type | DBのタイプを指定。今回はMySQLを使用。 PostgreSQLを使用するときは type: 'postgres'
|
host | 環境変数にて設定したDBのhost |
port | 環境変数にて設定したDBのport |
username | 環境変数にて設定したDBのusername |
password | 環境変数にて設定したDBのpassword |
database | 環境変数にて設定したDBのdatabase |
entities | table作成の元となるentityファイルを指定。ビルド後に吐き出されるjsファイルを指定。指定したファイルを元にテーブルが作成される。今回はtodoテーブルのみ作成される。 |
synchronize | true 指定でentityファイルの変更を即座にDBに反映 |
本番環境ではsynchronize
オプションはfalse
にすること
ファイル保存によって即座にDBへ反映されてしまうため、意図しない変更によりデータの損失につながる可能性があります。開発時は便利なので指定して利用しても良いですが、検証環境や本番環境ではfalseにするのを忘れてないようにしましょう。falseにすることでコマンドが必要になるため能動的にDBを更新できます。
~/src/modules/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config'; // 追加
import { TypeOrmModule } from '@nestjs/typeorm'; // 追加
import configuration from 'src/config/configuration';
import { TodoModule } from './todo/todo.module';
@Module({
imports: [
TodoModule,
ConfigModule.forRoot({
envFilePath: '.development.env',
load: [configuration],
}),
// 追加
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => {
return {
type: 'mysql',
host: configService.get('db.host'),
port: configService.get('db.port'),
username: configService.get('db.user'),
password: configService.get('db.password'),
database: configService.get('db.name'),
entities: ['dist/**/*.entity.js'],
synchronize: true,
};
},
inject: [ConfigService], // 追加
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
まとめ
設定が完了したらサーバーを一度終了して立ち上げ直しましょう。ログにLOG [NestApplication] Nest application successfully started
のように表示されればDB接続が成功しています。
上手く接続できない人は下記を試してみましょう。接続できたら次のChapter「DBから取得したデータを表示する」へ進みましょう。
- NestJSサーバーのログから原因箇所を探す
- MySQLの設定が環境変数に間違いなく反映できているか調べる
@nestjs/typeorm
や@nestjs/config
等インストールされているか確かめる- 「configuration.ts」の環境変数参照の仕方が合っているか見直す
- dockerを使用している方はDBの接続情報が環境変数と一致している見直す

Lesson 3
Chapter 10
DBから取得したデータを表示する
前回DBへの接続が完了したため次はDBからデータを取得します。画面の実装は行わないため、Thunder ClientでDBのデータを参照できたらOKとします。
テストデータのインサート
データの参照にはテストデータが必要なので下記コマンドでデータをインサートします。
MySQLへの接続
MySQLへテストデータをインサートするにはターミナルにてmysql -u root -p
等を使いMySQLコンソールへ接続するか、MySQLクライアント(MySQL Workbench
etc...)を使用します。
INSERT
mysql
INSERT INTO todo VALUES
(UUID(),'test1', 0, NOW(), NOW()),
(UUID(),'test2', 0, NOW(), NOW()),
(UUID(), 'test3', 1, NOW(), NOW());
SELECT
先ほどのデータがDBに存在するか下記コマンで確認しておきます。3レコード分データが存在していればOKです。
mysql
SELECT * FROM todo;
Todo一覧の取得
この章ではTodo一覧の取得をゴールとします。
Todo Module
「todo.module.ts」内でRepositoryに関わる箇所をコメントアウトしていたためコメントアウトを解除します。またTodoテーブルのデータを扱えるようTypeORMModuleをimportに追加します。
~/src/modules/todo/todo.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TodoController } from 'src/controllers/todo/todo.controller';
import { Todo } from 'src/entities/todo.entity';
import { TodoRepository } from 'src/repositories/todo.repository';
import { TodoService } from 'src/services/todo/todo.service';
@Module({
imports: [TypeOrmModule.forFeature([Todo])],
controllers: [TodoController],
providers: [TodoService, TodoRepository],
})
export class TodoModule {}
Todo Service
TodoModuleと同じくRepositoryに関わる部分のコメントアウトを解除します。
~/src/services/todo/todo.service.ts
...省略
import { TodoRepository } from 'src/repositories/todo.repository'; // コメントアウト解除
@Injectable()
export class TodoService {
constructor(private todoRepository: TodoRepository) {} // コメントアウト解除
...省略
Todo一覧取得用のメソッドを実装します。RepositoryによってEntity経由でDBからデータを取得します。また、返り値型を修正します。
Repositoryの返り値型
Repository組み込みのfindメソッドの返り値はPromise<Entity[]>
型です。
~/src/services/todo/todo.service.ts
...省略
@Injectable()
export class TodoService {
constructor(private todoRepository: TodoRepository) {}
// 変更
async findAll(): Promise<FindAllTodoResponseDTO> {
const todos = await this.todoRepository.find();
return { todos };
}
...省略
}
Todo Controller
Serviceに合わせてControllerも修正します。
~/src/controllers/todo/todo.controller.ts
...省略
@Controller('todo')
export class TodoController {
constructor(private readonly todoService: TodoService) {}
@Get()
// 修正
async findAll(): Promise<FindAllTodoResponseDTO> {
return await this.todoService.findAll();
}
...省略
}
おさらい:3層アーキテクチャ
「Lesson1 Chapter4 MVCモデルとは」にて3層アーキテクチャを学習しました。データはこのアーキテクチャに基づいて流れるため改めて図で復習しておきましょう。
3層アーキテクチャ図
Todo一覧の取得
実際にTodo一覧を取得しましょう。サーバーを落としている方はサーバーの再起動が必要です。サーバーの起動後「http://localhost:3000/todo」へGETリクエストをThunder Clientで飛ばしましょう。下記のような形でTodo一覧が取得できれば成功です。
ポート番号について
実際のポート番号は環境によって異なる可能性があります。NestJSのポート番号は「main.ts」で設定可能なのでそちらを参照・変更しリクエストを飛ばしてください。たとえばawait app.listen(3090);
のように設定することで「http://localhost:3090/todo」へリクエストを可能になります。
Thunder Client
{
"todos": [
{
"id": "4c10a231-8a80-11ed-be75-0242c0a8e003",
"title": "test1",
"isCompleted": false,
"createdAt": "2023-01-02 09:31:55",
"updatedAt": "2023-01-02 09:31:55"
},
{
"id": "8a7745e6-8a80-11ed-be75-0242c0a8e003",
"title": "test2",
"isCompleted": false,
"createdAt": "2023-01-02 09:33:40",
"updatedAt": "2023-01-02 09:33:40"
},
{
"id": "8a77deb0-8a80-11ed-be75-0242c0a8e003",
"title": "test3",
"isCompleted": true,
"createdAt": "2023-01-02 09:33:40",
"updatedAt": "2023-01-02 09:33:40"
}
]
}
Todo詳細の取得
一覧が取得できたのでTodo詳細取得の実装を行います。
Todo Service
serviceのfindOneメソッドを変更します。find同様Promiseを返します。またfindOneBy
を使用してidをキーに一意なTodoを特定します。
~/src/services/todo/todo.service.ts
...省略
constructor(private todoRepository: TodoRepository) {}
async findAll(): Promise<FindAllTodoResponseDTO> {
const todos = await this.todoRepository.find();
return { todos };
}
// 変更
async findOne(id: string): Promise<FindOneTodoResponseDTO> {
const todo = await this.todoRepository.findOneBy({ id });
return todo;
}
...省略
Todo Controller
同じくControllerも下記のように修正します。
~/src/controllers/todo/todo.controller.ts
...省略
constructor(private readonly todoService: TodoService) {}
@Get()
async findAll(): Promise<FindAllTodoResponseDTO> {
return await this.todoService.findAll();
}
// 修正
@Get(':id')
async findOne(
@Param() { id }: FindOneTodoRequestDTO,
): Promise<FindOneTodoResponseDTO> {
return await this.todoService.findOne(id);
}
...省略
Todo詳細の取得
準備が整ったのでTodo詳細をリクエストを飛ばして取得します。たとえば下記のように取得できていたらOKです。
1. まずはキーになるidをTodo一覧の中からコピーします。
2. 取得したidを元に「http://localhost:3090/todo/:id」へGETリクエストを送信します。「:id」へは1でコピーしたidを貼り付けます。
Thunder Client Todo詳細取得
まとめ
以上でDBからのデータ取得の基礎は完了です。データ取得のための最低限の実装を終えることができました。次は新しいデータを作成するDBへのインサートを実施します。

Lesson 3
Chapter 11
画面から入力した内容を登録する
新規Todoを登録し、そのデータを参照できることをゴールとします。
新規Todo登録処理の流れ
- クライアントからPOSTリクエスト
- Controllerでリクエストを受け取り、Serviceを呼び出す
- ServiceからRepositoryを呼び出す
- Repositoryにてデータを登録し、Serviceに結果を渡す
- ServiceからControllerへレスポンスデータを渡す
- Controllerからレスポンスをクライアントへ返す
新規Todo登録
入力画面は用意していないためThunder ClientからPOSTリクエストを送信し、新規TodoをDBへ登録します。
カスタムRepositoryの作成
RepositoryはDB操作のためのメソッドを数多く所持していますが、それらのメソッドを上書きしてオリジナルのDB操作用メソッドを作成できます。DTOに含まれないパラメーターをRepositoryで設定する必要があるため、今回はTodo登録用のメソッドを上書きします。
TodoのidにUUID
を設定する必要があるためインストールします。
~/nest-todo
npm i uuid
-
create
というメソッドにオブジェクトを引数で渡すことで新しいTodoを作成できます。createTodoRequestDTO
からtitleとisCompletedを、idと日付に関する項目はそれぞれ設定したものを引数に渡します。 -
save
メソッドを呼ぶことで実際にDBに登録されます。save
を呼ばない場合ステータス201で正常にレスポンスが返るもののDBにデータの登録はされないので、忘れないように注意が必要です。
repositories/todo/todo.repository.ts
import { Repository } from 'typeorm';
// 追加
import { v4 as uuidv4 } from 'uuid';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Todo } from 'src/entities/todo.entity';
import { CreateTodoRequestDTO } from 'src/requests/todo';
@Injectable()
export class TodoRepository extends Repository<Todo> {
constructor(@InjectRepository(Todo) repository: Repository<Todo>) {
super(repository.target, repository.manager, repository.queryRunner);
}
// 追加
async createTodo(createTodoRequestDTO: CreateTodoRequestDTO): Promise<Todo> {
const { title, isCompleted } = createTodoRequestDTO;
const newTodo = this.create({
id: uuidv4(),
title,
isCompleted: !!isCompleted,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
await this.save(newTodo);
return newTodo;
}
}
ServiceからRepositoryの利用
ServiceからカスタムRepository内で作成したメソッドを呼び出します。
/services/todo/todo.service.ts
...省略
// 変更
async createTodo(
createTodoDTO: CreateTodoRequestDTO,
): Promise<CreateTodoResponseDTO> {
return await this.todoRepository.createTodo(createTodoDTO);
}
...省略
ControllerからServiceの利用
ControllerからServiceを呼び出します。
/controllers/todo/todo.controller.ts
...省略
// 変更
@Post()
async createTodo(
@Body() createTodoDTO: CreateTodoRequestDTO,
): Promise<CreateTodoResponseDTO> {
return await this.todoService.createTodo(createTodoDTO);
}
...省略
リクエストを送信する
実装が完了したため実際にリクエスト送信して確認します。Thunder ClientからDTOにしたがってパラメーターを送信しましょう。
/requests/todo/create.dto.ts
export class CreateTodoRequestDTO {
title: string;
isCompleted?: boolean;
}
画像のようにリクエストボディを設定しましょう。ポート番号は3090で設定していますが、「main.ts」で指定している自分のポート番号でリクエストしてください。準備ができたら「Send」を押下しましょう。
Todo新規登録
レスポンスの一覧の中に作成されたTodoが含まれているのを確認できればOKです。
まとめ
Todoを新規登録しそのデータを参照するところまで完了しました。次は登録したデータに更新をかけていきます。

Lesson 3
Chapter 12
画面から入力した内容で更新する
新規登録したTodoを更新し、参照できることをゴールとします。
Todo更新処理の流れ
- クライアントからPUTリクエスト
- Controllerでリクエストを受け取り、Serviceを呼び出す
- ServiceからRepositoryを呼び出す
- Repositoryにてデータを更新し、Serviceに結果を渡す
- ServiceからControllerへレスポンスデータを渡す
- Controllerからレスポンスをクライアントへ返す
Todo更新
入力画面は用意していないためThunder ClientからPUTリクエストを送信し、既存のTodoデータを更新します。
更新リクエスト用DTO作成
Todo更新リクエスト用とレスポンス用のDTOを作成します。
requests/todo/update.dto.ts
export class UpdateTodoRequestDTO {
title?: string;
isCompleted?: boolean;
}
response/todo/update.dto.ts
export class UpdateTodoResponseDTO {
id: string;
title: string;
isCompleted: boolean;
createdAt: string;
updatedAt: string;
}
ルートファイルからどちらも吐き出すようにします。
requests/todo/index.ts
// 追加
export * from './update.dto';
export * from './create.dto';
export * from './find.dto';
response/todo/index.ts
// 追加
export * from './update.dto';
export * from './create.dto';
export * from './find.dto';
カスタムRepositoryの作成
Todo更新用のカスタムRepositoryを作成します。PUTによるリクエストを行うためパスパラメーターに該当Todoのidを含みます。そのためidはDTOとは別に受け取ります。
- DTOからtitle, isCompletedを取得
- 該当のTodoをDBから取得
- 更新済みのTodoオブジェクトを作成。その際にupdatedAtを更新
update
メソッドにより既存のTodoを上書き- 更新済みのTodoを返却
repositories/todo/todo.repository.ts
// 追加
async updateTodo(
id: string,
updateTodoDTO: UpdateTodoRequestDTO,
): Promise<Todo> {
const { title, isCompleted } = updateTodoDTO;
const currentTodo = await this.findOneBy({ id });
const updatedTodo: Todo = {
...currentTodo,
title,
isCompleted: !!isCompleted,
updatedAt: new Date().toISOString(),
};
await this.update(id, updatedTodo);
return updatedTodo;
}
ServiceからRepositoryの利用
ServiceからカスタムRepository内で作成したメソッドを呼び出します。
/services/todo/todo.service.ts
...省略
// 追加
async updateTodo(
id: string,
updateTodoDTO: UpdateTodoRequestDTO,
): Promise<Todo> {
return this.todoRepository.updateTodo(id, updateTodoDTO);
}
...省略
ControllerからServiceの利用
ControllerからServiceを呼び出します。PUTメソッドかつidをパスパラメーターに含めるため@Put(':id')
デコレーターを使用します。
/controllers/todo/todo.controller.ts
...省略
@Put(':id')
async updateTodo(
@Param() param: { id: string },
@Body() updateTodoDTO: UpdateTodoRequestDTO,
): Promise<UpdateTodoResponseDTO> {
return await this.todoService.updateTodo(param.id, updateTodoDTO);
}
...省略
リクエストを送信する
実装が完了したため実際にリクエスト送信して確認します。Thunder ClientからDTOにしたがってパラメーターを送信しましょう。たとえば以下のようになります。準備ができたら「Send」を押下してリクエストを飛ばしましょう。200レスポンスが返り、Todo一覧取得用のAPI等で更新が確認できればOKです。
Todo更新
まとめ
Todoを更新しそのデータを参照するところまで完了しました。次は登録したデータの削除を実施します。

Lesson 3
Chapter 13
選択したデータを削除する
「Chapter6 DBから取得したデータを表示する」でTodoの詳細を取得する方法を学びました。その際はTodoのIDより一意なTodoを参照しました。DELETEも同じ方法でTodoを特定し削除します。すでに登録済みのTodoのIDを用いてTodoを削除することをゴールとします。
Todo削除処理の流れ
- クライアントからDELETEリクエスト
- Controllerでリクエストを受け取り、Serviceを呼び出す
- ServiceからRepositoryを呼び出す
- Repositoryにてデータを削除し、Serviceに結果を渡す
- ServiceからControllerへレスポンスデータを渡す
- Controllerからレスポンスをクライアントへ返す
Todo削除
入力画面は用意していないためThunder ClientからDELETEリクエストを送信し、既存のTodoデータを削除します。
削除リクエスト用DTO作成
Todo削除リクエスト用とレスポンス用のDTOを作成します。リクエストはTodoのid
をキーにして削除します。また、Responseとして削除後のTodo一覧を返します。
requests/todo/delete.dto.ts
export class DeleteTodoRequestDTO {
id: string;
}
requests/todo/index.ts
export * from './delete.dto';
export * from './update.dto';
export * from './create.dto';
export * from './find.dto';
response/todo/delete.dto.ts
import { Todo } from 'src/entities/todo.entity';
export class DeleteTodoResponseDTO {
todos: Todo[];
}
requests/todo/index.ts
export * from './delete.dto';
export * from './update.dto';
export * from './create.dto';
export * from './find.dto';
ServiceからRepositoryの利用
ServiceからRepositoryメソッドを呼び出します。今回はデータに関する特別な操作がないのでカスタムRepositoryを作成する必要はありません。また、データを削除した後に一覧のデータを返すためdelete
メソッドの後にfind
メソッドを実行しています。
services/todo/todo.service.ts
...省略
async deleteTodo(id: string): Promise<DeleteTodoResponseDTO> {
await this.todoRepository.delete({ id });
const todos = await this.todoRepository.find();
return { todos };
}
...省略
ControllerからServiceの利用
ControllerからServiceを呼び出します。今回は物理削除を行うためDELETEメソッドを利用します。また、idをパスパラメーターに含めるため@Delete(':id')
デコレーターを使用します。
物理削除と論理削除
物理削除は実際にDBからデータ(レコード)を削除します。データのバックアップを取ってない限り削除したデータを戻すことはできません。クエリ文はDELETE
を使用します。
論理削除は実際にDBからデータ(レコード)を削除しません。実際に削除しないので簡単に削除前の状態に戻せます。削除したかどうかを判定する真偽値型のカラムを用意して値を反転して削除済かどうか判断します。クエリ文はUPDATE
を使用します。
...省略
@Delete(':id')
async deleteTodo(
@Param() param: DeleteTodoRequestDTO,
): Promise<DeleteTodoResponseDTO> {
return await this.todoService.deleteTodo(param.id);
}
...省略
リクエストを送信する
実装が完了したため実際にリクエスト送信して確認します。Thunder ClientからDTOにしたがってパラメーターを送信しましょう。たとえば以下のようになります。準備ができたら「Send」を押下してリクエストを飛ばしましょう。200レスポンスが返り、データ削除ずみのTodo一覧返却が確認できればOKです。
Todo削除
まとめ
これでNestJSによる基本的なCRUD操作を一通り学習しました。今回行った内容はNestJSによるCRUD操作の基礎中の基礎になります。最低限の構成で行うとこのようになるよというものを紹介しました。今後はこれを基盤に実装を加えていくので理解が怪しい方はしっかりと復習してから臨みましょう。

Lesson 3
Chapter 14
ファイルを添付する
このChapterではTodoのイメージ画像をクライアントから送信して、プロジェクト直下の「files/」ディレクトリに保存する流れを実装します。今回の実装の目的はファイルを送信して、保存、保存先のURLをクライアントに返すというファイルをアップロードする際の流れを学習することです。実際の現場ではAWSのS3等のストレージサービスに保存するのが一般的に用いられますが今回の目的とは逸れるため保存先は簡易的に実装します。大きな処理の流れは下記になります。
処理順
- 画像ファイルをクライアントから送信する
- サーバーサイドで受け取った画像ファイルを「プロジェクト/files/」に保存する
- 画像の保存先URLをクライアントに返却する
実装のための準備
実装のための準備として以下のことを行います。
- @types/multerインストール
- Todo EntityにimgUrlを追加
@types/multerインストール
NestJSでファイルのアップロードを実装するためにMulter
というモジュールを用います。このモジュールは公式でも推奨されており広く利用されています。MulterをTypeScriptで利用するために型定義ファイルをインストールします。
nest-todo/
npm i -D @types/multer
Todo EntityにimgUrlを追加
画像の保存先URLをDBに格納する必要があるためTodoテーブルにカラムを追加します。テーブルにカラムを追加するためにはTodoEntityクラスにプロパティの追加が必要です。以下のように追加しましょうEntityの更新はTypoORMモジュールのsynchronize: true
により自動的にDBに反映されます。
~/src/entities/todo.entity.ts
@Entity()
export class Todo {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 128, nullable: false })
title: string;
@Column({ default: false, nullable: false })
isCompleted: boolean;
// 追加
@Column({ length: 256, nullable: true })
imgUrl: string;
@Column({ nullable: false })
createdAt: string;
@Column({ nullable: false })
updatedAt: string;
}
MySQLを起動し、以下のコマンドでimgUrl
カラムが追加されているか確認しましょう。
MySQL
SHOW COLUMNS FROM todo;
画像アップロード処理実装
実際のアップロード処理を実装していきます。画像アップロード処理の流れは下記になります。
- controllerで画像をストレージに保存、保存先URLをserviceに渡す
- serviceからrepositoryに保存先URLを渡す
- repositoryで保存先URLをDBに保存
controllerで画像を保存
画像を保存するにはMulterモジュールとNestJSのInterceptorを用いる必要があります。Interceptorは「Lesson4 Chapter5 共通エラー処理の実装」で詳しく解説しています。今回は趣旨とはズレるため詳しくは解説せず簡単な説明に抑えます。
Multerモジュール
Multerモジュールはmultipart/form-data
形式でポストされたファイルデータを参照して任意の処理ができるモジュールです。とても使いやすく設計されておりアプリケーションの要件に合わせて動作を調整することができます。
Interceptor
InterceptorはNestJSの機能で、リクエストとレスポンスの間に共通処理を挟むことができます。今回はFileInterceptor
というNestJSが標準で持つInterceptor用のメソッドを用いて実装します。
実装に入っていきましょう。「todo.controller.ts」内createTodo
を下記のように変更しましょう。新たに使用するモジュール群をimportします。また、メソッドに対して@UseInterceptors
デコレーターを用いてFileInterceptor
を適用し、ファイルを抽出します。ファイルを抽出後、MulterからdiskStorage
メソッドを使って宣言的にファイルの保存先とファイル名の指定が可能です。destination: './files'
でファイルの保存先、filename: ~
メソッドでファイル名を指定し、filenameのコールバック関数の第2引数に文字列を渡すことでファイル名が決定します。controllerの引数に保存済みのfileオブジェクトが渡されるため@UploadedFile
デコレーターにより取得します。
todo.controller.ts
...省略
// 追加のimport
import { Request } from 'express';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import {
...省略
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
...省略
@Post()
// Interceptor追加
@UseInterceptors(
FileInterceptor('file', { // ファイルの保存処理
storage: diskStorage({
destination: './files', // 保存先を指定
filename: ( // ファイル名の指定
req: Request,
file: Express.Multer.File,
cb: (error: Error | null, filename: string) => void,
) => cb(null, file.originalname),
}),
}),
)
async createTodo(
@Body() createTodoDTO: CreateTodoRequestDTO,
@UploadedFile() file: Express.Multer.File, // 保存済みのファイルを取得
): Promise<CreateTodoResponseDTO> {
console.log(file);
return await this.todoService.createTodo(createTodoDTO);
}
POSTリクエストで画像を送信する
POSTリクエストで画像を送信します。下記のように画像ファイルを指定し、リクエストしてみましょう。画像は好きなものを画像ファイル形式はpngやjpg等を送信してみましょう。
postmanより画像をPOSTリクエスト
例えば以下のようなレスポンスが返却されれば画像の送信は成功しています。
コンソール
{
fieldname: 'file',
originalname: 'arch_lesson1_ch4-1.png',
encoding: '7bit',
mimetype: 'multipart/form-data',
destination: './files',
filename: 'arch_lesson1_ch4-1.png',
path: 'files/arch_lesson1_ch4-1.png',
size: 76313
}
また、「./files」配下に画像が保存されていることも確認できます。
controller処理の切り分け
Interceptorのファイル名のロジック等切り分けれるものを別ファイルに切り出しファイル内の見通しを良くしていきます。ひとつはファイル保存先のディレクトリへのパスです。現在'./files'
と記述していますが他のファイルからもこの値を参照したいときや将来的に変わる可能性があるかもしれません。その時に変更範囲を抑えることができるように定数に切り出します。具体的には「src」配下に「constants」というディレクトリを切りその中に格納します。
~/src/
mkdir constants && cd constants
touch index.ts
ディレクトリとファイルを作成したので値を追加します。
~/src/constants/index.ts
export const TODO_IMAGE_FILE_PATH = './files';
実際にcontrollerで使用します。
~/src/controllers/todo.controller.ts
...省略
import { TODO_IMAGE_FILE_PATH } from 'src/constants'; // 追加
...省略
storage: diskStorage({
destination: TODO_IMAGE_FILE_PATH,
...省略
)}
...省略
次にfilenameのロジックを切り出します。切り出す理由としてはcontrollerにfilename決定のロジックの責務を持たせたくないからです。切り出してモジュールに担当させましょう。後のChapterでも使用しますが「src」配下に「interceptors/todo」ディレクトリを切ってその中でモジュールを作成しましょう。
~/src/
mkdir interceptors && cd interceptors
mkdir todo && cd todo
touch image-file.interceptor.ts
ファイルの作成後、処理を記述しましょう。また、controllerに直接記述していたときは送信されたファイル名を直接使用していましたが今後被る可能性が高いため被らないようにファイル名を生成するロジックを追加しました。
~/src/interceptors/todo/image-file.interceptor.ts
import { Request } from 'express';
import { extname } from 'path';
export const generateFilename = (
req: Request,
file: Express.Multer.File,
cb: (error: Error | null, filename: string) => void,
) => {
const name = file.originalname.split('.')[0];
const fileExtName = extname(file.originalname);
const randomName = Array(4)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
cb(null, `${name}-${randomName}${fileExtName}`);
};
controllerに作成したモジュールを適用しましょう。Interceptor部分がかなりスッキリして見やすくなりました。また、ファイル名のロジックなどはもうジュールに隠蔽されてcontrollerからは気にする必要がなくなりました。
todo.controller.ts
...省略
@Post()
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: TODO_IMAGE_FILE_PATH,
filename: generateFilename,
}),
}),
)
async createTodo(
...省略
DBに画像のURLを保存する
画像を保存することができたので画像のURLをDBに保存する処理を追加しましょう。controllerとserviceとrepositoryの処理を追記していきます。controllerにはserviceに画像のURLを渡す処理を追加しました。また、BASE_URLとして保存先までのベースURLをconstantsに定義して使用しています。
constants/index.ts
export const BASE_URL = 'http://localhost:3090'; // 追加
todo.controller.ts
...省略
import { BASE_URL // 追加, TODO_IMAGE_FILE_PATH } from 'src/constants';
...省略
async createTodo(
@Body() createTodoDTO: CreateTodoRequestDTO,
@UploadedFile() file: Express.Multer.File,
): Promise<CreateTodoResponseDTO> {
// 追加
const imagePath = `${BASE_URL}/${file.path}`;
return await this.todoService.createTodo(createTodoDTO, imagePath);
}
...省略
serviceでも受け取るための処理を追加します。
todo.service.ts
...省略
async createTodo(
createTodoDTO: CreateTodoRequestDTO,
imagePath: string, // 画像のURL取得
): Promise<CreateTodoResponseDTO> {
// 第二引数でrepositoryへ渡す
return await this.todoRepository.createTodo(createTodoDTO, imagePath);
}
...省略
repository内で画像URlの保存処理を実行します。
todo.repository.ts
...省略
async createTodo(
createTodoRequestDTO: CreateTodoRequestDTO,
imagePath: string, // 画像のURL取得
): Promise<Todo> {
const { title, isCompleted } = createTodoRequestDTO;
const newTodo = this.create({
id: uuidv4(),
title,
isCompleted: !!isCompleted,
imgUrl: imagePath, // 画像のURL保存
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
await this.save(newTodo);
return newTodo;
}
...省略
ここまでできたところでもう一度画像を送信してみましょう。下記のようにimgUrl
が含まれたレスポンスが返れば成功です。
POST /todo Response
{
"id": "71ea60cf-e0e4-4db4-bdc7-44a777b2f23d",
"title": "file send test",
"isCompleted": true,
"imgUrl": "http://localhost:3090/files/arch_lesson1_ch4-1-3724.png",
"createdAt": "2023-01-08T06:11:08.870Z",
"updatedAt": "2023-01-08T06:11:08.871Z"
}
まとめ
以上で「ファイルを添付する」Chapterおよび「MySQLとの接続」Lessonは終了となります。このChapterではInterceptorを使ってファイルを保存しましたが、保存先とファイル名の指定のみの最小構成で利用しました。Multerにはファイルのバリデーションをかけられたりサイズの上限を決めることができたりと様々な機能が存在するので興味がある方は調べてみてください。実際の現場で利用する際はファイル形式のバリデーションやサイズの上限値を決めて利用するのが一般的です。

目次
- Chapter1 初期設定(main.ts)
- Chapter2 module
- Chapter3 entity
- Chapter4 controller
- Chapter5 service
- Chapter6 repository
- Chapter7 request
- Chapter8 response
- Chapter9 MySQLとの接続
- Chapter10 DBから取得したデータを表示する(SELECT)
- Chapter11 画面から入力した内容を登録する(INSERT)
- Chapter12 画面から入力した内容で更新する(UPDATE)
- Chapter13 選択したデータを削除する(DELETE)
- Chapter14 ファイルを添付する