☰ Mục lục series
Tài liệu NestJS tiếng Việt
  1. Nhập môn (Overview)
    1. Giới thiệu
    2. First steps
    3. Controllers
    4. Providers
    5. Modules
    6. Middleware
    7. Exception filters
    8. Pipes
    9. Guards
    10. Interceptors
    11. Custom decorators
  2. Nền tảng (Fundamentals)
    1. Custom providers
    2. Asynchronous providers
    3. Dynamic modules
    4. Injection scopes
    5. Circular dependency
    6. Module reference
    7. Lazy-loading modules
    8. Execution context
    9. Lifecycle events
    10. Discovery service
    11. Platform agnosticism
    12. Testing
  3. Kỹ thuật (Techniques)
    1. Configuration
    2. Database (SQL)
    3. MongoDB
    4. Validation
    5. Caching
    6. Serialization
    7. Versioning
    8. Task scheduling
    9. Queues
    10. Logging
    11. Cookies
    12. Events
    13. Compression
    14. File upload
    15. Streaming files
    16. HTTP module
    17. Session
    18. MVC
    19. Performance (Fastify)
    20. Server-Sent Events
  4. Bảo mật (Security)
    1. Authentication
    2. Authorization
    3. Encryption & Hashing
    4. Helmet
    5. CORS
    6. CSRF
    7. Rate limiting
    8. Vòng đời request
  5. OpenAPI (Swagger)
    1. Introduction
    2. Types and parameters
    3. Operations
    4. Security
    5. Mapped types
    6. Decorators
    7. CLI plugin
    8. Other features
  6. Recipes
    1. REPL
    2. CRUD generator
    3. SWC (fast compiler)
    4. Passport (auth)
    5. Hot reload
    6. MikroORM
    7. Router module
    8. Health checks (Terminus)
    9. CQRS
    10. Prisma
    11. Serve static
    12. Commander
    13. Async local storage
  7. WebSockets
    1. Gateways
    2. Exception filters
    3. Pipes
    4. Guards
    5. Interceptors
    6. Adapters
  8. Microservices
    1. Overview
    2. Redis
    3. MQTT
    4. NATS
    5. RabbitMQ
    6. Kafka
    7. gRPC
    8. Custom transporters
    9. Exception filters
    10. Pipes
    11. Pre-request hooks
    12. Guards
    13. Interceptors
  9. GraphQL
    1. Quick start
    2. Resolvers
    3. Mutations
    4. Subscriptions
    5. Scalars
    6. Directives
    7. Interfaces
    8. Unions and enums
    9. Field middleware
    10. Mapped types
    11. Plugins
    12. Complexity
    13. Extensions
    14. CLI plugin
    15. Generating SDL
    16. Sharing models
    17. Other features
    18. Federation
  10. CLI & triển khai
    1. CLI overview
    2. Workspaces
    3. Libraries
    4. Usage
    5. Scripts
    6. Deployment
    7. Standalone apps
  11. FAQ
    1. Serverless
    2. HTTP adapter
    3. Keep-Alive connections
    4. Global path prefix
    5. Raw body
    6. Hybrid application
    7. HTTPS & multiple servers
    8. Common errors
  12. Phụ lục
    1. Bảng thuật ngữ
Microservices

Pre-request hooks

Bản dịch từ docs.nestjs.com/microservices/pre-request-hooks

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.

Nội dung gốc: NestJS Documentation © Kamil Myśliwiec, giấy phép MIT. Bản dịch và ghi chú: Lê Văn Dũng.