1最小のサービス
@Injectable() を付けただけの最小のサービスです。HTTP のことを何も知らないただのロジック置き場であり、どこから呼ばれても同じ仕事をするのがサービスの理想形だと示します。
import { Injectable } from "@nestjs/common";
@Injectable() // 「DIコンテナで管理してよいクラス」という宣言
export class GreetingService {
greet(userName: string): string {
return "こんにちは、" + userName + "さん";
}
}
// HTTP を知らない、純粋なロジックの置き場になる
2コントローラへ注入する
サービスとコントローラをつなぐ最も基本の形です。new と書く代わりにコンストラクタへ「欲しい型」を書くだけで受け取れる、という前レッスンの DI がそのまま NestJS の書き味になっています。
import { Controller, Get, Injectable } from "@nestjs/common";
@Injectable()
export class UsersService {
findAll() {
return [{ id: 1, name: "Alice" }];
}
}
@Controller("users")
export class UsersController {
// new とは書かず、欲しい型を引数に書くだけ
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
}
3太ったコントローラをサービスへ逃がす
コントローラに直書きされた業務ロジックをサービスへ移すリファクタの例です。ロジックに findAdults という名前が付いて再利用とテストがしやすくなる、実務レビューで最も頻繁に指摘されるパターンです。
// 悪い例: コントローラのハンドラに業務ロジックが直書き
// @Get("adults")
// findAdults() {
// return this.users.filter((u) => u.age >= 18);
// }
import { Injectable } from "@nestjs/common";
@Injectable()
export class UsersService {
private users = [
{ id: 1, name: "Alice", age: 20 },
{ id: 2, name: "Bob", age: 15 },
];
// 良い例: ロジックをサービスに置き、名前を付ける
findAdults() {
return this.users.filter((u) => u.age >= 18);
}
}
// コントローラ側は this.usersService.findAdults() を呼ぶだけになる
41つのインスタンスを共有する意味
プロバイダが既定でシングルトン(アプリ全体で1個)になることの意味をプレーン TypeScript で再現した例です。複数のコントローラが同じサービスを注入すると、内部の状態も共有されることが分かります。
TypeScript
5サービスからサービスへの注入
コントローラだけでなくサービス同士も同じ仕組みで注入できることを示す実務例です。監査ログやメール送信のような横断的な処理を専用サービスに切り出し、各サービスから再利用するのが定番の構成です。
import { Injectable } from "@nestjs/common";
@Injectable()
export class AuditLogService {
record(action: string): void {
console.log("[監査ログ] " + action);
}
}
@Injectable()
export class UsersService {
// サービス同士も同じ仕組みで注入できる
constructor(private readonly audit: AuditLogService) {}
remove(id: number) {
this.audit.record("ユーザー削除: id=" + id);
return { removed: id };
}
}
// 監査ログ・通知・決済など、横断機能の再利用が定番の用途