Pre-request hook là các hàm chạy trước tất cả enhancer (guard, interceptor, pipe) cho mỗi lần một pattern handler được gọi đến. Chúng là phiên bản tương đương của HTTP middleware trong thế giới microservices: nơi để thiết lập ngữ cảnh riêng cho từng request mà guard và các phần còn lại của pipeline cần thấy được.
Ghi chú người dịch Nếu bạn từng viết middleware Express (app.use((req, res, next) => ...)) thì next() ở đây đóng vai trò tương tự: hook phải tự gọi next() để pipeline tiếp tục, giống hệt việc middleware Express phải gọi next() để request không bị “treo”. Khác biệt là ở đây next() trả về một RxJS Observable thay vì việc gọi hàm đồng bộ, vì toàn bộ pipeline xử lý message của Nest microservices được xây dựng trên RxJS.
Gợi ý Interface PreRequestHook được export từ package @nestjs/common.
Các hook thực thi theo thứ tự sau:
Incoming message
└─ Pre-request hooks (registration order)
└─ Guards
└─ Interceptors
└─ Pipes
└─ Handler
Exception ném ra bên trong một hook sẽ được bắt bởi cùng lớp bọc RpcProxy bao quanh toàn bộ pipeline, vì vậy các exception filter hiện có xử lý chúng mà không cần cấu hình thêm.
Gắn hook (Binding hooks)
Đăng ký hook trên instance của application trước khi gọi app.listen() bằng registerPreRequestHook. Gọi nhiều lần sẽ tích lũy các hook theo đúng thứ tự đăng ký.
Mỗi hook nhận vào một ExecutionContext và một hàm next. Gọi next() sẽ chuyển sang hook kế tiếp, hoặc khi không còn hook nào nữa, chuyển sang pipeline guard → interceptor → pipe → handler. Hook phải return Observable do next() tạo ra.
Cảnh báo Nếu một hook không gọi next(), handler sẽ không bao giờ được thực thi — cùng một quy tắc như HTTP middleware.
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{ transport: Transport.TCP },
);
app.registerPreRequestHook((ctx, next) => {
return next();
});
await app.listen();
}
bootstrap();
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.TCP,
});
app.registerPreRequestHook((ctx, next) => {
return next();
});
await app.listen();
}
bootstrap();
Lan truyền AsyncLocalStorage (AsyncLocalStorage propagation)
Một trường hợp sử dụng phổ biến là khởi tạo AsyncLocalStorage trước khi các guard chạy, để store đó khả dụng suốt toàn bộ pipeline.
Ghi chú người dịch Nếu bạn đã quen Go, AsyncLocalStorage ở đây tương đương context.Context được truyền ngầm qua goroutine — chỉ khác là Node không truyền tham số qua từng hàm mà “gắn” giá trị vào ngữ cảnh thực thi bất đồng bộ hiện tại, mọi callback bên trong als.run(...) đều đọc lại được cùng giá trị đó. Đây là cách phổ biến để làm request-scoped correlation ID/tracing mà không phải truyền tay qua constructor hay tham số hàm ở mọi tầng.
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { Observable } from 'rxjs';
import { AsyncLocalStorage } from 'async_hooks';
import { randomUUID } from 'crypto';
import { AppModule } from './app.module';
export const als = new AsyncLocalStorage<{ correlationId: string }>();
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{ transport: Transport.TCP },
);
app.registerPreRequestHook((ctx, next) => {
return new Observable(subscriber => {
als.run({ correlationId: randomUUID() }, () => {
next().subscribe(subscriber);
});
});
});
await app.listen();
}
bootstrap();
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { Observable } from 'rxjs';
import { AsyncLocalStorage } from 'async_hooks';
import { randomUUID } from 'crypto';
import { AppModule } from './app.module';
export const als = new AsyncLocalStorage();
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.TCP,
});
app.registerPreRequestHook((ctx, next) => {
return new Observable(subscriber => {
als.run({ correlationId: randomUUID() }, () => {
next().subscribe(subscriber);
});
});
});
await app.listen();
}
bootstrap();
Vì hook chạy trước guard, als.getStore() sẽ trả về store đã được khởi tạo ở bất kỳ guard, interceptor, hay handler nào:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { als } from './main';
@Injectable()
export class CorrelationGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const { correlationId } = als.getStore()!;
return true;
}
}
import { Injectable } from '@nestjs/common';
import { als } from './main';
@Injectable()
export class CorrelationGuard {
canActivate(context) {
const { correlationId } = als.getStore();
return true;
}
}
Logging và metrics
Có thể áp dụng các operator RxJS lên Observable trả về từ next() để quan sát toàn bộ vòng đời của handler:
import { tap, catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
app.registerPreRequestHook((ctx, next) => {
const handler = ctx.getHandler().name;
const start = Date.now();
return next().pipe(
tap(() => console.log(`[${handler}] completed in ${Date.now() - start}ms`)),
catchError(err => {
console.error(`[${handler}] failed:`, err.message);
return throwError(() => err);
}),
);
});
const { tap, catchError } = require('rxjs/operators');
const { throwError } = require('rxjs');
app.registerPreRequestHook((ctx, next) => {
const handler = ctx.getHandler().name;
const start = Date.now();
return next().pipe(
tap(() => console.log(`[${handler}] completed in ${Date.now() - start}ms`)),
catchError(err => {
console.error(`[${handler}] failed:`, err.message);
return throwError(() => err);
}),
);
});
Gợi ý registerPreRequestHook chỉ hoạt động ở cấp global — không hỗ trợ đăng ký theo từng pattern riêng lẻ. Khi không có hook nào được đăng ký, pipeline vẫn đi theo đường xử lý nhanh như trước khi tính năng này được thêm vào. Việc hỗ trợ WebSocket gateway chủ ý không nằm trong phạm vi này.