NestJSの基礎
NestJSを何も知らない状態から、全体像と最小構成を掴む
このシリーズで作るもの
このシリーズでは、nestjs-practice-api という小さなユーザー管理APIを作りながら、NestJS案件で 会話についていける / コードレビューできる / 軽微修正できる / API追加できる 状態を目指す。
このシリーズのロードマップ
- NestJSの基礎
Controller / Service / Module / DI の最小構成を掴む。 - NestJS DTO / ValidationPipe 入門
API入口の入力ルールをDTOで定義し、ValidationPipeで検証する。 - NestJSでCRUD APIを作る(Prisma + SQLite)
PrismaとSQLiteを使い、ユーザー管理のCRUDを実装する。 - NestJS TypeORM QueryRunnerでトランザクション制御
TypeORMのQueryRunnerで複数テーブルを跨ぐ処理を明示的なトランザクションで安全にまとめる。 - NestJSの例外処理
APIエラーの返し方を揃える。 - NestJS Guard / JWT認証
ログイン、JWT発行、認証必須APIの基本を押さえる。 - NestJS + Swagger
API仕様をSwagger UIで確認できるようにする。 - NestJSをDocker化する(任意)
ローカル実行環境をコンテナで再現しやすくする。 - NestJS BullMQでジョブキューを実装する
Consumerとジョブキューの基本パターンを実装する。 - NestJS Winstonでロギングを整える
構造化ログ(JSON)と開発用カラーログを環境ごとに切り替える。 - NestJSのユニットテスト
Service・Controller・Guard を TestingModule で単体検証する実践パターンを学ぶ。 - NestJS E2Eテスト(Jest + Supertest)
Supertest でHTTPリクエストを実際に送り、認証フローを含むE2Eテストを実装する。 - NestJS PDF非同期生成とトークンダウンロード
BullMQでPDF生成をキューイングし、PlaywrightでHTMLをPDF変換、使い捨てトークンで安全にダウンロードさせる。
NestJSとは
TypeScript前提として設計された、Node.jsで 保守しやすいサーバーアプリ を作るためのフレームワーク。
-
Express/Fastify上で動く
HTTPサーバー自体はExpressまたはFastifyが実行し、NestJSはその上に「アプリケーション構造」を提供する。 NestJSは内部HTTPエンジンとしてExpressまたはFastifyを選択可能で、デフォルトではExpressを利用する。 Controller / Service / Module / DI などによる構造化された設計を提供しつつ、選択したHTTP基盤(Express/Fastify)のエコシステムも活用できる。 -
Angularの設計思想(バックエンド版Angularという人もいる)
- Module中心:Module、Controller、Serviceが最小単位でありそれぞれに役割分担が明確に決まっており、ModuleでController、Service、Provider、Repositoryを束ねる。
- Dependency Injection(DI)が標準:NestJS側が自動でインスタンス注入(Expressだと自分でnewする必要がある)。依存関係をコンテナが管理して、テストや差し替えがやりやすい。
- デコレータ文化:Angular系はデコレータ文化があり、NestJSもかなりそう。
@Controller()や@Get()のように、クラス/メソッドに役割を付与する。
-
“レイヤー分離”を強く促す
NestJSは:- Module → 配線
- Controller → HTTP入口
- Service → 業務ロジック
- Repository → DBアクセス(Serviceに書くと業務ロジックとSQL/ORMが混ざるため)
のように責務分離をかなり強く推奨
-
クラスベース設計
関数文化のReact/Next.jsとは違い、クラス重視 -
“エンタープライズ感”が強い
- 型安全
- DI
- 層分離
- テスト容易性
- 保守性
- 大人数開発
を重視。
「小さいAPIをサクッと作りたい」だけなら、Express、Hono、Fastifyの方が軽い
まず覚える3つ(超重要)
sequenceDiagram
participant C as クライアント
participant Ctrl as Controller
participant Svc as Service
participant DB as DB / 外部API
C->>Ctrl: HTTP リクエスト
Ctrl->>Svc: 処理を依頼
Svc->>DB: データ取得・保存
DB-->>Svc: 結果
Svc-->>Ctrl: 処理結果
Ctrl-->>C: HTTP レスポンス(JSON等)
1) Controller(入口)
HTTPリクエストを受け取って、レスポンスを返す場所。
- 例:
GET /usersを受けて、ユーザー一覧JSONを返す - Controllerには「入力の受け取り・バリデーション・Service呼び出し」くらいを書き、重い処理はServiceに逃がすのが基本
2) Service(中身)
ビジネスロジックを書く場所。
- 例: DBからユーザーを取る、整形する、権限をチェックする
- Controllerから呼ばれる“部品”になりがちなので、単体テストしやすい
3) Module(配線)
ControllerやServiceを「このアプリはこれを使う」と登録して、つなぐ場所。
- NestJSは基本的に「Moduleの世界」に閉じて動く
- 何かが動かないとき、まず疑うのは「Moduleに登録されてない」「export/importの関係が間違ってる」
最小の形(Controller / Service / Module)
NestJSを早速試してみることを目的に、最小のREST APIを1本作る。
この記事で作るサンプル
nest new で作ったプロジェクト api-sample に、ユーザー一覧を返すだけの API を足す。
| 項目 | 内容 |
|---|---|
| エンドポイント | GET /users のみ(作成・更新・削除はしない) |
| レスポンス | ユーザー配列の JSON(id と name を持つオブジェクトが2件) |
| データの置き場所 | UsersService 内の固定配列(DB・ORM は使わない) |
| 学ぶこと | UsersModule / UsersController / UsersService の役割分担と、Module への登録・DI |
動作イメージは次のとおり。
curl http://localhost:3000/users
# → [{"id":"u_1","name":"Taro"},{"id":"u_2","name":"Hanako"}]初期状態の GET /(Hello World!)はそのまま残し、別ルートとして /users を追加する形になる。実務の「機能ごとに Module を分ける」最小版として読める。
サンプル実装手順
- 作業フォルダ作成
mkdir nestjs-practice
cd nestjs-practice- NestJSプロジェクト作成(公式CLI)
npm i -g @nestjs/cli
nest new api-sampleNest CLI(@nestjs/cli) は、nest new や nest g module などのコマンドを提供する公式のCLIツールである。npm i -g @nestjs/cli は、作業ディレクトリに関係なく nest を叩けるようにする グローバルインストール。
nest new が作る api-sample/package.json の devDependencies にも @nestjs/cli が入る。リポジトリを clone した人は npm install(または pnpm / yarn)後に npx nest や package.json の scripts 経由で同じCLIを使えるため、グローバルに入れていなくてもよい。
初期のテスト雛形(例: src/app.controller.spec.ts、test/app.e2e-spec.ts)も、この nest new のタイミングで置かれることが多い。テストの書き方は後続の NestJSテスト入門 で扱う。
nest new 実行時に package manager を聞かれたら、普段使っているもの(npm/pnpm/yarn)を選べばOK。
ここでは npm 想定で進める。
- プロジェクトへ移動して起動確認
cd api-sample
npm run start:devブラウザで http://localhost:3000 にアクセスして Hello World! が出れば初期起動は成功。
- ファイル構成(今回触る場所)
api-sample/
└─ src/
├─ app.module.ts
└─ users/
├─ users.module.ts
├─ users.controller.ts
└─ users.service.ts各ファイルの役割は次のとおり。
src/app.module.ts
アプリ全体のルートModule。機能Module(今回はUsersModule)を読み込む起点。src/users/users.module.ts
users機能の配線定義。ControllerとServiceを登録して関連付ける。src/users/users.controller.ts
HTTPの入口。GET /usersのようなルーティングを定義し、Serviceを呼び出す。src/users/users.service.ts
処理の中身(ビジネスロジック)を置く。DBアクセスや計算など、Controllerから分離したい処理を書く。
どこを変えると何が変わるかをざっくり言うと、
- レスポンス内容を変える:
users.service.ts - エンドポイントURLやHTTPメソッドを変える:
users.controller.ts - users機能の依存関係を変える:
users.module.ts - アプリに機能を追加/読み込みする:
app.module.ts
usersモジュール一式を作成
# 別ターミナルで実行(start:dev を止めてもOK)
cd api-sample/src/
nest g module users
nest g service users
nest g controller users各コマンドは、Nest CLI が 雛形ファイルの生成 と Module への登録(配線) をまとめて行う。
| コマンド | 生成される主なファイル | 具体的にやっていること |
|---|---|---|
nest g module users | src/users/users.module.ts | users 機能用の Module を新規作成する。多くの場合、ルートの app.module.ts の imports に UsersModule を追記して、アプリ全体から読み込めるようにする。 |
nest g service users | src/users/users.service.ts | 処理本体用の Service クラスを生成し、UsersModule の providers に登録する。@Injectable() 付きの空クラスができ、あとから DI で Controller などに注入できる。 |
nest g controller users | src/users/users.controller.ts | HTTP の入口となる Controller を生成し、UsersModule の controllers に登録する。@Controller() 付きの空クラスができ、あとから @Get() などのルートを足す。 |
手で同じことをするなら、「3 ファイルを作る」+「users.module.ts で Controller / Service を imports・providers・controllers に書く」+「app.module.ts に UsersModule を足す」に相当する。CLI はその定型作業を省略してくれる。
src/users/users.service.ts
import { Injectable } from "@nestjs/common";
type User = { id: string; name: string };
@Injectable()
export class UsersService {
list(): User[] {
return [
{ id: "u_1", name: "Taro" },
{ id: "u_2", name: "Hanako" },
];
}
}このファイルは「返すデータの中身」を決める場所。
今は固定配列を返しているが、実務ではここがDBや外部API呼び出しに置き換わることが多い。
src/users/users.controller.ts
import { Controller, Get } from "@nestjs/common";
import { UsersService } from "./users.service";
@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
list() {
return this.usersService.list();
}
}このファイルはHTTPルーティングの定義場所。
@Controller("users")でベースパスを/usersにする@Get()で GET リクエストを受ける- メソッド内で
usersService.list()を呼び、結果をJSONで返す
src/users/users.module.ts(生成済みだが確認)
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}このファイルはDIの登録表のイメージ。
controllers: HTTP入口として使うクラスproviders: Service など、Nest に生成・管理させる部品の一覧
@Injectable() や「注入」の意味は、手順のあとにある Aside「DI(依存注入)が何をしているか」で整理する。
src/app.module.tsでUsersModuleを読み込む
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { UsersModule } from "./users/users.module";
@Module({
imports: [UsersModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}imports: [UsersModule] がないと、users機能はアプリに組み込まれない。
「ファイルはあるのにルートが効かない」ときはここを疑うとよい。
- 動作確認
npm run start:dev
curl http://localhost:3000/users以下のようなJSONが返れば成功。
[
{ "id": "u_1", "name": "Taro" },
{ "id": "u_2", "name": "Hanako" }
]