概要
NestJSのルーティングは、Expressのように「パスとハンドラ」を書き並べるのではなく、@Controller や @Get といったデコレータで宣言します。
この記事では、main.ts のグローバルプレフィックス → コントローラー → モジュールへの登録という3ステップで、1本のURLがどう組み立てられるかを整理します。
前提:Expressとの比較
まず、生のExpressでルーティングを書くとこうなります。
// Express の場合(パスとハンドラを明示的に結びつける)
app.get('/api/users', (req, res) => { ... });
app.post('/api/users', (req, res) => { ... });
app.get('/api/users/:id', (req, res) => { ... });
「どのURLにどの関数を割り当てるか」を自分で書き並べる方式です。数が増えるとパスの重複や書き漏れに気づきにくくなります。
NestJSはこれをデコレータで宣言的に置き換えます。ルーティングテーブルはフレームワークが起動時に組み立てるので、開発者はクラス・メソッドに「担当するURL」をラベルとして貼るだけになります。
デコレータとは
@〇〇 の記法で、クラスやメソッドにメタ情報(ラベル)を付ける仕組みです。NestJSが起動時にラベルを読み取り、ルーティングテーブルを自動で組み立てます。
デコレータ自体は関数呼び出しなので、@Get(':id') は「:id というパスを覚えておいて」というメタデータを、そのメソッドに紐付けているだけです。それ自体はルーティングを行いません。実際の登録は後述の起動処理で行われます。
ステップ1:グローバルプレフィックス
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api'); // 全エンドポイントに /api を付ける
await app.listen(3000);
}
bootstrap();
-
NestFactory.create(AppModule)で、AppModuleを起点にアプリ全体を組み立てます -
setGlobalPrefix('api')で、全URLが/api/...始まりになります
一部のパスだけプレフィックスから除外する
ヘルスチェックのように /api を付けたくないエンドポイントは exclude で除外できます。
import { RequestMethod } from '@nestjs/common';
app.setGlobalPrefix('api', {
exclude: [{ path: 'health', method: RequestMethod.GET }], // GET /health のまま
});
ステップ2:コントローラーでパスを宣言
// src/users/users.controller.ts
import { Controller, Get, Post, Body, Param, Query } from '@nestjs/common';
@Controller('users') // このクラスは /users を担当
export class UsersController {
@Get() // GET /api/users
findAll(@Query('limit') limit?: string) {
return [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
}
@Get(':id') // GET /api/users/1
findOne(@Param('id') id: string) {
return { id, name: 'Alice' };
}
@Post() // POST /api/users
create(@Body() body: { name: string }) {
return { id: 3, name: body.name };
}
}
※@Controller('users') のパスに先頭スラッシュは不要
URLの組み立て
グローバルプレフィックス /api
+
@Controller /users
+
@Get(':id') /:id
=
GET /api/users/1 → findOne() が呼ばれる
リクエストから値を取り出すデコレータ
| デコレータ | 取得元 | 例 |
|---|---|---|
@Param('id') |
URLパスパラメータ |
/users/1 → '1'
|
@Query('limit') |
クエリ文字列 |
/users?limit=10 → '10'
|
@Body() |
リクエストボディ | { "name": "Alice" } |
@Headers('authorization') |
ヘッダー | 'Bearer ...' |
ステップ3:モジュールにコントローラーを登録
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
@Module({
controllers: [UsersController], // ここに登録して初めて有効になる
})
export class UsersModule {}
// src/app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
@Module({
imports: [UsersModule], // AppModuleに取り込む
})
export class AppModule {}
デコレータを正しく書いていても、controllers への登録や imports への取り込みを忘れると、そのエンドポイントは存在しません(404が返ります)。
なお、コントローラーが依存するサービスは同じモジュールの providers に登録します(ルーティングの話からは外れるので詳細は割愛)。
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
主なルーティングデコレータ一覧
@Get('path') // GET /api/path
@Post('path') // POST /api/path
@Put('path') // PUT /api/path
@Patch('path') // PATCH /api/path
@Delete('path') // DELETE /api/path
@Head('path') // HEAD /api/path
@Options('path') // OPTIONS /api/path
@All('path') // 全HTTPメソッドを受ける
@Put は「リソース全体の置き換え」、@Patch は「一部更新」という使い分けが一般的です。
NestJS起動時の流れ
npm run start:dev
↓
main.ts が実行される
↓
AppModule を読み込む
↓
imports に書かれた UsersModule を読み込む
↓
controllers に書かれた UsersController のデコレータを読み取る
↓
以下のルーティングテーブルを自動登録:
GET /api/users → findAll()
GET /api/users/:id → findOne()
POST /api/users → create()
↓
リクエスト待ち状態へ
まとめ
| 設定箇所 | デコレータ/メソッド | 役割 |
|---|---|---|
main.ts |
app.setGlobalPrefix('api') |
全URLの先頭に /api を付ける |
| コントローラークラス | @Controller('users') |
クラスのURL担当範囲を決める |
| コントローラーメソッド |
@Get() @Post() など |
HTTPメソッドとパスを紐付ける |
| コントローラーメソッドの引数 |
@Param() @Query() @Body()
|
リクエストから値を取り出す |
| モジュール | controllers: [...] |
コントローラーをNestJSに認識させる |
AppModule |
imports: [...] |
モジュールをアプリに取り込む |
デコレータを読むだけでそのクラス・メソッドがどのURLを担当しているかが一目でわかるのがNestJSの特徴ですね。