NestJS初心者の私「引数Decoratorって依存注入できないの?」
目次
引数DecoratorだけではDIできない
Param decorator(引数デコレーター)は依存性注入システムの外側で動作するため、
依存注入することで使えるサービスなどのクラスには直接アクセスできない。
そのため、インターセプターを介して間接的にサービスクラスを利用する。
なぜ、インターセプターなのか
インターセプターは依存性注入システムの一部になることができるため。
なぜ、インターセプターだけではできないのか
依存性注入システムの一部にはなれても、コントローラメソッドに値を返すことができないため。
逆に引数デコレーターは、DIシステムにはなれないが値を返せるので、組み合わせてつかうと良さそう。
【例】ログイン中のユーザーを返す引数デコレーター「CurrentUser」を実装
現在のリクエストオブジェクトにアクセスして、ユーザーIDを抜き出し
DBからユーザーデータを取得するのをやってくれる引数デコレーターを考えてみる。
コントローラメソッドに到達するまでにやってくれて便利そうだ。
この例をやるために、以下のライブラリが必要。
npm install cookie-session @types/cookie-session
インストール後、下記のように設定を追加。
// main.ts
const cookieSession = require("cookie-session");
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(cookieSession({ keys: ["jap98fhia2f"] })); // 追加
app.useGlobalPipes(new ValidationPipe({
whitelist: true
}));
await app.listen(3000);
}
bootstrap();
📘 Controller method
// GET http://localhost:3000/auth/whoami
@Get("/whoami")
@UseInterceptors(CurrentUserInterceptor) // インターセプターを利用する宣言
whoAmI(@CurrentUser() user: User) {
return user;
}
user にはログイン中のユーザー情報がある。ログインしてない場合は null 。
インターセプターを利用するということをデコレーターで宣言するのを忘れずに!
📘 Interceptor
@Injectable()
export class CurrentUserInterceptor implements NestInterceptor {
constructor(private usersService: UsersService) { // サービス依存注入。これが使いたいがために...
}
async intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
const { userId } = request.session || {};
if (userId) {
const user = await this.usersService.findOne(userId);
request.currentUser = user; // これがデコレーターに渡る
}
return next.handle();
}
}
UsersService は、DBからユーザーを取得するといった、ユーザーにまつわる一連の機能を有するサービス。依存注入を介して動作する。
現在のリクエストを介してセッションにアクセスし、 userId を含む場合(ログイン中である場合)、それにマッチするユーザーをDBから取得して再びセッションに格納する。
📕 Decorator
export const CurrentUser = createParamDecorator(
(data: never, context: ExecutionContext) => {
const request = context.switchToHttp().getRequest();
return request.currentUser; // NOTE: インターセプターから渡ってくる!!!
}
);
インターセプターがセッションに格納した currentUser にアクセスしてreturnすることで、
コントローラーメソッドの引数として動作できる。
📗 Module
@Module({
// このモジュールが提供するサービス(機能)
providers: [UsersService, AuthService, CurrentUserInterceptor],
// サービス(機能)を使用している(ルーティングしている)コントローラー
controllers: [UsersController],
// サービス内で依存しているサービスやリポジトリなど
imports: [TypeOrmModule.forFeature([User])]
})
export class UsersModule {
}
インターセプターを使いたいモジュールのprovidersに列挙する。
Interceptorをグローバルに展開できる定義方法
以下のようにModuleに定義することで、その他のコントローラすべてで自動的に実行されるようになる。
import { APP_INTERCEPTOR } from "@nestjs/core";
@Module({
providers: [
UsersService,
AuthService,
// どのコントローラでもこのインターセプターが実行されるよう、グローバルに適用する書き方
{ provide: APP_INTERCEPTOR, useClass: CurrentUserInterceptor }
],
controllers: [UsersController],
imports: [TypeOrmModule.forFeature([User])]
})
export class UsersModule {
}
1回書くだけですべてのルートに適用できる。