☰ 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ữ
Nền tảng (Fundamentals)

Sự kiện vòng đời (Lifecycle Events)

Bản dịch từ docs.nestjs.com/fundamentals/lifecycle-events

Một ứng dụng Nest, cũng như mọi thành phần ứng dụng, đều có một vòng đời (lifecycle) do Nest quản lý. Nest cung cấp lifecycle hook giúp bạn quan sát các sự kiện vòng đời quan trọng, và có khả năng hành động (chạy code đã đăng ký trên module, provider hoặc controller của bạn) khi các sự kiện đó xảy ra.

Trình tự vòng đời (Lifecycle sequence)

Sơ đồ dưới đây mô tả trình tự các sự kiện vòng đời quan trọng của ứng dụng, từ lúc ứng dụng được bootstrap cho tới khi tiến trình node thoát. Ta có thể chia toàn bộ vòng đời thành ba giai đoạn: khởi tạo (initializing), chạy (running) và kết thúc (terminating). Nhờ vòng đời này, bạn có thể lên kế hoạch khởi tạo phù hợp cho module và service, quản lý các kết nối đang hoạt động, và tắt ứng dụng một cách nhẹ nhàng (gracefully) khi nhận được tín hiệu kết thúc (termination signal).

Minh hoạ: lifecycle events

Các sự kiện vòng đời

Sự kiện vòng đời xảy ra trong lúc ứng dụng bootstrap và shutdown. Nest gọi các phương thức lifecycle hook đã đăng ký trên module, provider và controller tại mỗi sự kiện vòng đời sau đây (shutdown hook cần được bật trước, như mô tả bên dưới). Như sơ đồ trên thể hiện, Nest cũng gọi các phương thức nội bộ tương ứng để bắt đầu lắng nghe kết nối, và để dừng lắng nghe kết nối.

Trong bảng dưới đây, onModuleInit và onApplicationBootstrap chỉ được kích hoạt nếu bạn gọi tường minh app.init() hoặc app.listen().

Trong bảng dưới đây, onModuleDestroy, beforeApplicationShutdown và onApplicationShutdown chỉ được kích hoạt nếu bạn gọi tường minh app.close() hoặc nếu tiến trình nhận một tín hiệu hệ thống đặc biệt (như SIGTERM) và bạn đã gọi đúng enableShutdownHooks lúc bootstrap ứng dụng (xem phần Application shutdown bên dưới).

Phương thức lifecycle hook Sự kiện vòng đời kích hoạt lời gọi phương thức hook
onModuleInit() Được gọi ngay khi các phụ thuộc của module chứa (host module) đã được resolve xong.
onApplicationBootstrap() Được gọi ngay khi tất cả module đã khởi tạo xong, nhưng trước khi lắng nghe kết nối.
onModuleDestroy()* Được gọi sau khi nhận một tín hiệu kết thúc (ví dụ: SIGTERM).
beforeApplicationShutdown()* Được gọi sau khi tất cả handler onModuleDestroy() đã hoàn tất (Promise resolve hoặc reject); ngay khi hoàn tất, mọi kết nối hiện có sẽ được đóng (gọi app.close()).
onApplicationShutdown()* Được gọi sau khi các kết nối đóng (app.close() resolve).

* Với các sự kiện này, nếu bạn không gọi tường minh app.close(), bạn phải chủ động bật (opt-in) để chúng hoạt động với các tín hiệu hệ thống như SIGTERM. Xem Application shutdown bên dưới.

Cảnh báo Các lifecycle hook liệt kê ở trên không được kích hoạt cho các class request-scoped. Class request-scoped không gắn với vòng đời ứng dụng và tuổi thọ (lifespan) của chúng khó đoán trước. Chúng chỉ được tạo riêng cho mỗi request và tự động garbage-collected sau khi response được gửi đi.

Gợi ý Thứ tự thực thi của onModuleInit() và onApplicationBootstrap() phụ thuộc trực tiếp vào thứ tự import module, chờ (await) hook trước hoàn tất rồi mới tới hook sau.

Ghi chú người dịch Nếu bạn từng dùng Go và tự quản lý dependency qua constructor: các lifecycle hook này gần giống việc bạn tự viết hàm Init()/Close() cho từng struct rồi gọi tay theo đúng thứ tự trong main(). Điểm khác là Nest tự động hóa việc gọi đúng thứ tự dựa trên đồ thị phụ thuộc (dependency graph), bạn không cần tự sắp xếp.

Cách dùng

Mỗi lifecycle hook được đại diện bởi một interface. Về mặt kỹ thuật, các interface này là tùy chọn vì chúng không tồn tại sau khi biên dịch TypeScript. Tuy nhiên, dùng chúng vẫn là một thực hành tốt để tận dụng type checking mạnh và hỗ trợ từ editor. Để đăng ký một lifecycle hook, hãy implement interface tương ứng. Ví dụ, để đăng ký một phương thức được gọi trong lúc khởi tạo module trên một class cụ thể (ví dụ: Controller, Provider hoặc Module), hãy implement interface OnModuleInit bằng cách cung cấp phương thức onModuleInit(), như bên dưới:

import { Injectable, OnModuleInit } from '@nestjs/common';

@Injectable()
export class UsersService implements OnModuleInit {
  onModuleInit() {
    console.log(`The module has been initialized.`);
  }
}
import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  onModuleInit() {
    console.log(`The module has been initialized.`);
  }
}

Khởi tạo bất đồng bộ (Asynchronous initialization)

Cả hai hook OnModuleInit và OnApplicationBootstrap đều cho phép bạn trì hoãn quá trình khởi tạo ứng dụng (trả về một Promise, hoặc đánh dấu phương thức là async và await một lệnh gọi bất đồng bộ hoàn tất trong thân phương thức).

async onModuleInit(): Promise<void> {
  await this.fetch();
}
async onModuleInit() {
  await this.fetch();
}

Application shutdown

Các hook onModuleDestroy(), beforeApplicationShutdown() và onApplicationShutdown() được gọi trong giai đoạn kết thúc (terminating) (khi có lời gọi tường minh tới app.close() hoặc khi nhận tín hiệu hệ thống như SIGTERM nếu đã opt-in). Tính năng này thường được dùng cùng Kubernetes để quản lý vòng đời container, hoặc bởi Heroku cho dynos hay các dịch vụ tương tự.

Shutdown hook listener tiêu tốn tài nguyên hệ thống, nên mặc định chúng bị tắt. Để dùng shutdown hook, bạn phải bật listener bằng cách gọi enableShutdownHooks():

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Bắt đầu lắng nghe shutdown hook
  app.enableShutdownHooks();

  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

Cảnh báo Do giới hạn vốn có của nền tảng, NestJS chỉ hỗ trợ một phần cho application shutdown hook trên Windows. Bạn có thể kỳ vọng SIGINT hoạt động, cũng như SIGBREAK và ở một mức độ nào đó là SIGHUP - đọc thêm. Tuy nhiên SIGTERM sẽ không bao giờ hoạt động trên Windows vì việc kill một tiến trình trong task manager là vô điều kiện, “tức là, ứng dụng không có cách nào để phát hiện hay ngăn chặn nó”. Xem thêm tài liệu liên quan từ libuv để hiểu rõ hơn cách SIGINT, SIGBREAK và các tín hiệu khác được xử lý trên Windows. Ngoài ra, xem tài liệu Node.js về Process Signal Events

Lưu ý enableShutdownHooks tiêu tốn bộ nhớ vì phải khởi động các listener. Trong trường hợp bạn chạy nhiều ứng dụng Nest trong cùng một tiến trình Node (ví dụ, khi chạy test song song), Node có thể cảnh báo về việc có quá nhiều listener process. Vì lý do này, enableShutdownHooks không được bật mặc định. Hãy lưu ý điều này khi bạn chạy nhiều instance trong cùng một tiến trình Node.

Ghi chú người dịch Đây là cạm bẫy hay gặp khi viết test e2e: nếu bạn bật enableShutdownHooks() trong mỗi test suite mà không tắt listener đúng cách sau mỗi lần chạy, Node sẽ in cảnh báo MaxListenersExceededWarning. Cách xử lý phổ biến là chỉ bật hook này ở main.ts thực sự chạy production, không bật trong môi trường test.

Khi ứng dụng nhận một tín hiệu kết thúc, nó sẽ gọi lần lượt các phương thức onModuleDestroy(), beforeApplicationShutdown(), rồi onApplicationShutdown() đã đăng ký (theo đúng trình tự mô tả ở trên) với tín hiệu tương ứng là tham số đầu tiên. Nếu một hàm đã đăng ký await một lệnh gọi bất đồng bộ (trả về promise), Nest sẽ không tiếp tục trình tự cho tới khi promise đó resolve hoặc reject.

@Injectable()
class UsersService implements OnApplicationShutdown {
  onApplicationShutdown(signal: string) {
    console.log(signal); // ví dụ "SIGINT"
  }
}
@Injectable()
class UsersService implements OnApplicationShutdown {
  onApplicationShutdown(signal) {
    console.log(signal); // ví dụ "SIGINT"
  }
}

Lưu ý Gọi app.close() không kết thúc tiến trình Node, mà chỉ kích hoạt các hook onModuleDestroy() và onApplicationShutdown(), nên nếu có một số interval, tác vụ nền chạy dài, v.v. tiến trình sẽ không tự động bị kết thúc.

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.