Bỏ qua điều hướng

NestJS là gì? Framework Node.js kiến trúc module và TypeScript

NestJS là framework Node.js sử dụng TypeScript với kiến trúc module giúp xây dựng các ứng dụng backend quy mô lớn chuyên nghiệp, ổn định và dễ bảo trì.

Tuan Tran Van
8 phút đọc
Mục lục (10 phần)
  1. NestJS là gì?
  2. Một ứng dụng NestJS gồm những thành phần nào?
  3. Dependency injection hoạt động thế nào trong NestJS?
  4. Request đi qua những lớp nào trước khi tới controller?
  5. Bên dưới NestJS: Express, Fastify và decorator của TypeScript
  6. NestJS 11 mang lại những gì?
  7. Cấu trúc dự án và những sai lầm thường gặp
  8. NestJS hay Express — nên chọn cái nào?
  9. Nên bắt đầu với NestJS từ đâu?
  10. Tài liệu tham khảo

NestJS là một framework Node.js tiến bộ được thiết kế để xây dựng các ứng dụng server-side hiệu quả, đáng tin cậy và có khả năng mở rộng.

Framework này sử dụng TypeScript làm ngôn ngữ chính và xây dựng dựa trên kiến trúc module hóa chặt chẽ.

Về mặt triết lý, NestJS là sự kết hợp thực dụng giữa Lập trình hướng đối tượng (OOP), Lập trình hàm (FP) và Lập trình phản xạ hàm (FRP). Thay vì để lập trình viên tự bơi trong việc tổ chức mã nguồn như các thư viện truyền thống, NestJS cung cấp một bộ khung kiến trúc hoàn chỉnh để giải quyết bài toán quản lý sự phức tạp của ứng dụng backend.

Framework được Kamil Myśliwiec giới thiệu vào năm 2017, lấy cảm hứng mạnh mẽ từ kiến trúc của Angular để mang lại sự nhất quán cho hệ sinh thái Node.js.

NestJS mang trật tự kiến trúc đến hệ sinh thái Node.js vốn tự do và thiếu quy chuẩn

NestJS là gì?

Node.js mang lại sự tự do tối đa nhưng cũng để lại lỗ hổng lớn về mặt cấu trúc (architecture). Khi dự án phình to, việc thiếu quy chuẩn khiến mã nguồn trở nên lỏng lẻo và khó kiểm soát trong các đội ngũ lớn. NestJS ra đời để lấp đầy khoảng trống này bằng tính chất "opinionated" (định hướng cao). Framework buộc bạn tuân theo các chuẩn mực nhất định, giúp duy trì sự đồng nhất tối đa. Đây là lý do các tập đoàn như Adidas hay Roche lựa chọn NestJS để vận hành các hệ thống quan trọng.

Lợi ích thực tế nhất là tính bảo trì và khả năng kiểm thử. Nhờ sự phân tách rạch ròi giữa logic nghiệp vụ và tầng giao thức, bạn có thể thay đổi công nghệ bên dưới mà không làm hỏng logic lõi. NestJS tích hợp sẵn Jest và cung cấp công cụ mạnh mẽ để mock các phụ thuộc, giúp việc viết Unit Test hay E2E Test trở thành một phần tự nhiên của quy trình phát triển.

Để khởi tạo dự án, bạn sử dụng Nest CLI:

bash
npm i -g @nestjs/cli
nest new project-name

Một ứng dụng NestJS gồm những thành phần nào?

Cấu trúc NestJS dựa trên ba trụ cột chính: Modules, Controllers và Providers.

Ba trụ cột của một ứng dụng NestJS: Modules, Controllers và Providers cùng quan hệ giữa chúng

Modules: Được định nghĩa bằng decorator @Module(). Nest sử dụng metadata này để tổ chức "application graph" (biểu đồ ứng dụng). Module đóng vai trò đóng gói (encapsulation). Một provider chỉ có thể được sử dụng ở module khác nếu nó được liệt kê trong mảng exports và module đích phải imports module gốc đó.

Controllers: Chịu trách nhiệm xử lý request HTTP và điều hướng (routing). Bạn sử dụng các decorator như @Get(), @Post(), @Body() để khai báo endpoint. Controller chỉ nên đóng vai trò điều phối, nhận dữ liệu và gọi đến service.

Providers: Bao gồm Service, Repository, Factory, Helper. Đây là các lớp có thể được "inject" (tiêm) vào các thành phần khác. Providers thường chứa logic nghiệp vụ hoặc tương tác dữ liệu.

Ví dụ về một Feature Module:

typescript
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}
 
@Controller("cats")
export class CatsController {
  constructor(private catsService: CatsService) {}
 
  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }
}

Dependency injection hoạt động thế nào trong NestJS?

NestJS sử dụng container Inversion of Control (IoC) để quản lý phụ thuộc dựa trên kiểu dữ liệu (type) của TypeScript. Khi bạn khai báo một service trong constructor, Nest sẽ tự động tìm instance phù hợp để tiêm vào.

Container IoC của NestJS tiêm provider vào lớp cần dùng, kèm ba vòng đời Singleton, Request và Transient

Về vòng đời (scope), có ba loại chính:

  1. Singleton (Mặc định): Một instance duy nhất cho toàn bộ ứng dụng.
  2. Request scope: Tạo instance mới cho mỗi request.
  3. Transient scope: Tạo instance mới mỗi khi được inject.

Bạn có thể dùng @Optional() để đánh dấu phụ thuộc không bắt buộc và @Inject() cho "Property-based injection" khi làm việc với lớp kế thừa phức tạp. Đặc biệt, useFactory là giải pháp tối ưu cho các provider cần khởi tạo bất đồng bộ, ví dụ như thiết lập kết nối Database trước khi ứng dụng sẵn sàng.

Request đi qua những lớp nào trước khi tới controller?

Vòng đời request (Request Lifecycle) trong NestJS rất chặt chẽ:

Middleware → Guards → Interceptors (Trước) → Pipes → Controller → Interceptors (Sau)

Vòng đời một request trong NestJS đi lần lượt qua Middleware, Guards, Interceptors, Pipes rồi tới Controller

  • Guards: Xử lý xác thực/ủy quyền.
  • Interceptors: Can thiệp vào request trước khi xử lý hoặc biến đổi dữ liệu trả về và đo lường hiệu suất.
  • Pipes: Chuyển đổi và validate dữ liệu. ValidationPipe kết hợp với class-validator là combo chuẩn để đảm bảo dữ liệu đầu vào (DTO) luôn sạch.

Bên dưới NestJS: Express, Fastify và decorator của TypeScript

NestJS có tính chất "platform-agnostic" (không phụ thuộc nền tảng). Mặc định, framework sử dụng @nestjs/platform-express. Nếu cần hiệu suất cực cao, bạn có thể chuyển sang @nestjs/platform-fastify.

NestJS là lớp trừu tượng nằm trên HTTP engine bên dưới, có thể đổi giữa Express và Fastify

Sức mạnh của Nest đến từ reflect-metadata và decorator: bạn khai báo hành vi ngay trên lớp và phương thức, thay vì viết mã boilerplate để nối chúng lại.

Cách khởi tạo với Express Application:

typescript
async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  await app.listen(3000);
}
bootstrap();

NestJS 11 mang lại những gì?

Bản cập nhật ngày 22/01/2025 tập trung vào hiệu suất khởi động và tính năng Cloud-native:

  • Hiệu suất startup: Đại tu quá trình tạo "opaque key" (khóa mờ) cho module. Thay vì băm (hashing) metadata phức tạp như trước, Nest 11 dùng tham chiếu đối tượng trực tiếp, giúp các ứng dụng lớn khởi động nhanh hơn đáng kể.
  • JSON Logging: Tích hợp sẵn trong ConsoleLogger, cực kỳ hữu ích cho việc thu thập log trên Docker/Kubernetes.
  • Microservices: Thêm phương thức .unwrap() để truy cập trực tiếp client instance (NATS, Kafka, Redis) và theo dõi trạng thái qua observable stream.
  • Dependencies: Hỗ trợ Express v5 và Fastify v5. Lưu ý: Wildcard route trong Express 5 phải đặt tên (ví dụ: *splat).
  • Tính năng mới khác: Bổ sung ParseDatePipeIntrinsicException (loại exception không tự động bị log vào hệ thống JSON log, giúp giảm nhiễu).

Cấu hình JSON logging:

typescript
const app = await NestFactory.create(AppModule, {
  logger: new ConsoleLogger({ json: true, colors: true }),
});

Cấu trúc dự án và những sai lầm thường gặp

Đừng tổ chức dự án theo lớp (Layered structure). Hãy dùng Feature-based structure (tổ chức theo tính năng). Mỗi thư mục tính năng nên chứa đủ module, controller, service và DTO của nó.

So sánh hai cách tổ chức thư mục dự án: theo tính năng và theo lớp

Sai lầm phổ biến:

  1. StrictNullChecks: CLI mặc định có thể để strictNullChecks: false. Hãy bật nó lên ngay để tránh lỗi runtime với null/undefined.
  2. Circular Dependency: Lỗi phụ thuộc vòng là dấu hiệu của "Design Smell". Trước khi dùng forwardRef(), hãy thử tách logic dùng chung ra một CommonModule.
  3. DTO vs Entity: Luôn tách biệt DTO (dữ liệu API) và Entity (dữ liệu DB) để tránh rò rỉ thông tin nhạy cảm.

NestJS hay Express — nên chọn cái nào?

  • Express: Phù hợp dự án nhỏ, prototype nhanh, cần sự tự do tuyệt đối.
  • NestJS: Lựa chọn hàng đầu cho Enterprise. Tuy nhiên, nếu đội ngũ chỉ có 2-5 người và ứng dụng đơn giản, chi phí viết boilerplate (Module, Provider, DTO) của Nest có thể trở thành một khoản "nợ kỹ thuật" không đáng có.

So sánh NestJS và Express: cấu trúc sẵn có cho đội ngũ lớn so với sự tự do tối đa cho dự án nhỏ

Trên GitHub, NestJS hiện có hơn 76.000 sao — nhiều hơn cả Express, framework mà nó chạy bên trên.

Nên bắt đầu với NestJS từ đâu?

Hãy cài Nest CLI, học cách tạo Module đầu tiên và viết Unit Test ngay với file .spec.ts có sẵn. Giá trị thật của NestJS nằm ở chuẩn kiến trúc chung mà cả đội cùng tuân theo — thứ Node.js thuần không có sẵn.

Tài liệu tham khảo

Chia sẻ bài viết

X / TwitterFacebookLinkedIn