Node.js Super Senior · Phase 14 — NestJS Deep Dive
Bonus Phase 14: master NestJS — modules, controllers, providers and DI, DTO validation, guards and role-based auth, interceptors, exception filters, the request lifecycle, Prisma, config, queues, and testing.
This is Bonus Phase 14 {Đây là Bonus Phase 14}. You can persist (Phases 11–12) and accelerate with Redis (Phase 13); now we give that all structure {Bạn lưu trữ được (Phase 11–12) và tăng tốc với Redis (Phase 13); giờ ta cho tất cả một cấu trúc}. Across this series you hand-built middleware pipelines, DI containers, layered architecture, validation, guards, and error handlers {Suốt series bạn tự dựng pipeline middleware, DI container, kiến trúc phân tầng, validation, guard, và error handler}. NestJS is what happens when those patterns are formalized into one opinionated, batteries-included framework {NestJS là điều xảy ra khi các mẫu đó được chính thức hóa thành một framework có chủ kiến, đầy đủ pin}. Because you understand the fundamentals underneath, Nest will feel like naming things you already know {Vì bạn hiểu nền tảng bên dưới, Nest sẽ như đặt tên cho thứ bạn đã biết}.
Nest is TypeScript-first, dependency-injection-first, and runs on Express (or Fastify) under the hood {Nest ưu tiên TypeScript, ưu tiên dependency injection, và chạy trên Express (hoặc Fastify) bên dưới}.
14.1 Why a framework like Nest {Vì sao cần framework như Nest}
Raw Express gives you freedom and zero structure — every team invents its own folders, DI, and conventions {Express thô cho tự do và không cấu trúc — mỗi team tự bịa folder, DI, quy ước}. Nest provides a standard architecture (modules, providers, controllers) and a real DI container, so large teams build consistently and testably {Nest cung cấp kiến trúc chuẩn và một DI container thật, nên team lớn build nhất quán và test được}.
npm i -g @nestjs/cli
nest new my-api
nest g resource users # scaffolds module + controller + service + DTOs + tests
14.2 The building blocks {Các khối xây dựng}
Module ── groups a feature (imports, controllers, providers, exports)
├── Controller ── HTTP layer: routes, params (thin — like Phase 3)
└── Provider/Service ── business logic, injected via DI (Phase 6)
Modules {Module}
A module wires a feature together; the AppModule is the root {Module ráp một feature; AppModule là gốc}:
import { Module } from '@nestjs/common';
@Module({
imports: [PrismaModule], // other modules this one depends on
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // make UsersService available to importing modules
})
export class UsersModule {}
Controllers — thin HTTP layer {Controller — tầng HTTP mỏng}
import { Controller, Get, Post, Param, Query, Body, HttpCode } from '@nestjs/common';
@Controller('users') // route prefix /users
export class UsersController {
constructor(private readonly users: UsersService) {} // injected by DI
@Get()
findAll(@Query('page') page = '1') { return this.users.findAll(Number(page)); }
@Get(':id')
findOne(@Param('id') id: string) { return this.users.findOne(id); } // 404 thrown in service
@Post()
@HttpCode(201)
create(@Body() dto: CreateUserDto) { return this.users.create(dto); }
}
Note what’s gone versus Phase 3 {Để ý cái biến mất so với Phase 3}: no manual res.status().json(), no router wiring — decorators declare the route and Nest serializes the return value {không res.status().json() tay, không đấu router — decorator khai báo route và Nest serialize giá trị trả}.
Providers & DI {Provider & DI}
@Injectable() marks a class the container can construct and inject {@Injectable() đánh dấu một class mà container có thể tạo và tiêm}:
import { Injectable, NotFoundException } from '@nestjs/common';
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {} // constructor injection
findAll(page: number) { return this.prisma.user.findMany({ skip: (page - 1) * 20, take: 20 }); }
async findOne(id: string) {
const user = await this.prisma.user.findUnique({ where: { id } });
if (!user) throw new NotFoundException('User not found'); // → automatic 404
return user;
}
}
This is the repository + service pattern (Phase 6) and dependency inversion (Phase 10), now enforced by the framework {Đây là mẫu repository + service (Phase 6) và đảo ngược phụ thuộc (Phase 10), giờ được framework thực thi}. Custom providers (useClass, useValue, useFactory) and scopes (default singleton, plus REQUEST scope) work just like the container you built {Provider tùy biến và scope hoạt động đúng như container bạn đã dựng}.
14.3 DTOs & validation {DTO & validation}
A DTO defines the shape of incoming data; the global ValidationPipe validates and transforms it {Một DTO định nghĩa hình dạng dữ liệu đến; ValidationPipe toàn cục validate và biến đổi}:
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail() email!: string;
@IsString() @MinLength(8) password!: string;
}
// main.ts — turn it on globally, and strip unknown fields
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
whitelist: true drops properties not in the DTO (defense in depth); transform: true coerces payloads to the DTO class and primitives {whitelist: true bỏ thuộc tính không có trong DTO; transform: true ép payload về class DTO và kiểu nguyên thủy}. Prefer nestjs-zod if you want Zod as your single source of truth (Phase 6) {Ưu tiên nestjs-zod nếu muốn Zod làm nguồn sự thật duy nhất}.
14.4 The request lifecycle — Nest’s pipeline {Vòng đời request — pipeline của Nest}
This is the Phase 2/3 middleware pipeline, formalized into named, ordered stages {Đây là pipeline middleware Phase 2/3, chính thức hóa thành các giai đoạn có tên, có thứ tự}:
request
▼
Middleware ─▶ Guards ─▶ Interceptors(pre) ─▶ Pipes ─▶ Handler
│
response ◄── Interceptors(post) ◄─────────────────────────┘
▲
Exception Filter (catches anything thrown anywhere above)
Guards — authorization {Guard — phân quyền}
A guard returns true/false to allow the request — perfect for auth and RBAC (Phase 5) {Guard trả true/false để cho qua — hợp cho auth và RBAC (Phase 5)}:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(ctx: ExecutionContext): boolean {
const required = this.reflector.get<string[]>('roles', ctx.getHandler());
if (!required) return true;
const { user } = ctx.switchToHttp().getRequest();
return required.includes(user?.role); // false → automatic 403
}
}
// A custom @Roles('admin') decorator attaches metadata the guard reads
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Post() @Roles('admin') @UseGuards(JwtAuthGuard, RolesGuard)
remove(@Param('id') id: string) { /* ... */ }
Interceptors — wrap the handler {Interceptor — bọc handler}
Interceptors run before and after the handler (the Decorator pattern, Phase 10) — ideal for response shaping, logging, timing, and caching {Interceptor chạy trước và sau handler — lý tưởng cho định hình response, log, đo thời gian, cache}:
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(_ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
return next.handle().pipe(map((data) => ({ data, timestamp: Date.now() })));
}
}
Exception filters — centralized errors {Exception filter — lỗi tập trung}
Nest’s built-in HttpException family covers most cases; a filter customizes the response shape (the Phase 3 global error handler, formalized) {Họ HttpException sẵn có lo phần lớn; một filter tùy biến hình dạng response (error handler toàn cục Phase 3, chính thức hóa)}:
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const res = host.switchToHttp().getResponse();
const status = exception instanceof HttpException ? exception.getStatus() : 500;
res.status(status).json({ error: { status, message: /* ... */ '' } });
}
}
14.5 Database, config & async work {Database, config & việc async}
Wrap PrismaClient (Phase 12) in an injectable provider and export it from a module {Bọc PrismaClient trong một provider tiêm được và export từ một module}:
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() { await this.$connect(); }
async onModuleDestroy() { await this.$disconnect(); } // graceful shutdown (Phase 7)
}
The official modules map straight onto earlier phases {Các module chính thức ánh xạ thẳng vào các phase trước}: @nestjs/config (validated env, Phase 7), @nestjs/passport + @nestjs/jwt (auth, Phase 5), @nestjs/bullmq (queues, Phase 6), @nestjs/schedule (cron), @nestjs/event-emitter (event-driven, Phase 10), and @nestjs/swagger (auto OpenAPI docs from your DTOs) {…}.
Nest also has first-class microservices transports (TCP, Redis, NATS, Kafka, gRPC) and GraphQL — the architectures from Phase 10, built in {Nest cũng có transport microservices hạng nhất và GraphQL — các kiến trúc Phase 10, dựng sẵn}.
14.6 Testing {Kiểm thử}
Nest’s DI makes testing trivial — swap real providers for mocks via the testing module (Phase 9) {DI của Nest khiến test dễ — đổi provider thật bằng mock qua testing module}:
import { Test } from '@nestjs/testing';
const moduleRef = await Test.createTestingModule({
providers: [
UsersService,
{ provide: PrismaService, useValue: mockPrisma }, // inject a fake
],
}).compile();
const service = moduleRef.get(UsersService);
For end-to-end, bootstrap the app and drive it with supertest (Phase 9), ideally against a Testcontainers Postgres {Cho end-to-end, khởi động app và lái bằng supertest, lý tưởng với Postgres Testcontainers}.
14.7 Nest vs raw Express — choosing {Nest vs Express thô — chọn lựa}
| Express (Phase 3) | NestJS | |
|---|---|---|
| Structure {Cấu trúc} | you decide | enforced, standard |
| DI {DI} | bring your own | built-in |
| Boilerplate {Mã khuôn} | minimal | more upfront |
| Best for {Hợp cho} | small/bespoke services | large teams, long-lived apps |
Senior take {Quan điểm senior}: reach for Nest when team size and longevity justify the structure; a small focused service may be happier on plain Express/Fastify {dùng Nest khi quy mô team và tuổi thọ xứng với cấu trúc; service nhỏ tập trung có thể vui hơn với Express/Fastify thuần}.
14.8 Practice {Thực hành}
Rebuild the Phase 10 capstone on NestJS, reusing everything {Dựng lại capstone Phase 10 trên NestJS, tái dùng tất cả}:
- Feature modules (
users,posts,auth) with controllers + services + DTOs {module feature với controller + service + DTO}. PrismaServiceprovider + the Phase 12 schema; migrations in CI {providerPrismaService+ schema Phase 12; migration trong CI}.- JWT auth with
@nestjs/passport, aRolesGuard, and a@Rolesdecorator (Phase 5) {auth JWT,RolesGuard, decorator@Roles}. - Global
ValidationPipe, aTransformInterceptor, and an exception filter {ValidationPipetoàn cục,TransformInterceptor, và exception filter}. - A BullMQ queue for emails (
@nestjs/bullmq), Swagger docs, and unit + e2e tests {queue BullMQ cho email, docs Swagger, và test unit + e2e}. - Dockerize and ship via the Phase 7 pipeline {đóng gói Docker và ship qua pipeline Phase 7}.
What’s next {Phần tiếp theo}
You now have a structured, injectable, validated framework around your data (Phases 11–12) and cache (Phase 13) {Giờ bạn có một framework có cấu trúc, tiêm được, đã validate quanh dữ liệu (Phase 11–12) và cache (Phase 13)}. Notice the through-line {Để ý mạch xuyên suốt}: NestJS didn’t teach you anything new — it gave production-grade names and structure to the fundamentals you built by hand {NestJS không dạy bạn điều gì mới — nó đặt tên và cấu trúc chuẩn production cho nền tảng bạn tự dựng}.
One critical layer remains — the one attackers probe first {Còn một tầng then chốt — tầng kẻ tấn công dò đầu tiên}. In Phase 15, the capstone, we go deep on authentication & security with JWT — access/refresh token rotation, OAuth2/OIDC with PKCE, role- and permission-based access control as Nest guards, secure cookie vs header storage, and the attacks each choice defends against {Ở Phase 15, phần đỉnh, ta đi sâu vào xác thực & bảo mật với JWT — xoay access/refresh token, OAuth2/OIDC với PKCE, RBAC làm guard Nest, lưu cookie vs header an toàn, và các đòn tấn công mỗi lựa chọn phòng vệ}.