jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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)đả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 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 decideenforced, standard
DI {DI}bring your ownbuilt-in
Boilerplate {Mã khuôn}minimalmore upfront
Best for {Hợp cho}small/bespoke serviceslarge 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ả}:

  1. Feature modules (users, posts, auth) with controllers + services + DTOs {module feature với controller + service + DTO}.
  2. PrismaService provider + the Phase 12 schema; migrations in CI {provider PrismaService + schema Phase 12; migration trong CI}.
  3. JWT auth with @nestjs/passport, a RolesGuard, and a @Roles decorator (Phase 5) {auth JWT, RolesGuard, decorator @Roles}.
  4. Global ValidationPipe, a TransformInterceptor, and an exception filter {ValidationPipe toàn cục, TransformInterceptor, và exception filter}.
  5. A BullMQ queue for emails (@nestjs/bullmq), Swagger docs, and unit + e2e tests {queue BullMQ cho email, docs Swagger, và test unit + e2e}.
  6. 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ệ}.