44 questions
20 / 44 questions
NestJS là một framework Node.js xây dựng ứng dụng server-side, dùng TypeScript làm mặc định và kiến trúc lấy cảm hứng từ Angular (module, DI, decorator). Dưới lớp trừu tượng, mặc định nó chạy trên Express (có thể đổi sang Fastify). Vấn đề nó giải quyết là Express thuần không áp đặt cấu trúc: Nest cung cấp kiến trúc rõ ràng, Dependency Injection, và hệ sinh thái tích hợp sẵn (validation, guard, interceptor) giúp code dễ mở rộng và test.
Ba khối cốt lõi là Module, Controller và Provider.
@Module()): đơn vị tổ chức, gom nhóm các thành phần liên quan.@Controller()): xử lý request đến, định nghĩa route và trả response.@Injectable()): chứa business logic, có thể được inject vào nơi khác.Controller nhận request, ủy quyền logic cho provider (thường là service), và module gắn kết chúng lại với nhau.
Module là class đánh dấu @Module() dùng để tổ chức ứng dụng thành các khối chức năng. Nó có bốn thuộc tính chính:
controllers: các controller thuộc module.providers: các provider được khởi tạo và thuộc phạm vi module này.imports: các module khác mà module này cần dùng.exports: các provider được chia sẻ ra ngoài cho module khác import.Mỗi ứng dụng có ít nhất một root module (AppModule), là điểm khởi đầu để Nest dựng cây dependency.
NestJS có một IoC container: provider được đánh dấu @Injectable() và đăng ký trong providers của module. Khi một class cần dependency, Nest tự khởi tạo và inject qua constructor dựa trên type. Mặc định provider là singleton trong phạm vi ứng dụng, giúp tái sử dụng và dễ test (thay bằng mock).
@Injectable()
export class UsersService {
constructor(private readonly repo: UserRepository) {}
}
IoC (Inversion of Control) đảo ngược quyền kiểm soát: thay vì class tự tạo dependency của mình bằng new, việc khởi tạo được giao cho container của framework. Nest đóng vai trò container: nó đọc metadata từ decorator, dựng dependency graph, rồi cung cấp instance vào đúng chỗ. Lợi ích là loose coupling (class không phụ thuộc cách tạo dependency), dễ thay thế bằng mock khi test, và tập trung quản lý vòng đời của object.
@Injectable() đánh dấu một class là provider có thể được quản lý bởi IoC container của Nest. Nó gắn metadata để Nest biết class này có thể được inject và cũng có thể nhận dependency qua constructor. Không có decorator này (hoặc decorator khác dùng DI), Nest không thể resolve các tham số constructor của class, vì thông tin type bị mất khi biên dịch nếu không có metadata reflection.
Ngoài cách khai báo provider tiêu chuẩn, Nest cho phép định nghĩa custom provider để kiểm soát cách tạo instance:
inject dependency, hỗ trợ async) để tạo giá trị.{ provide: 'CONFIG', useValue: { port: 3000 } }
Injection token là định danh mà Nest dùng để tra cứu provider trong container. Với class, token chính là class type. Nhưng khi inject giá trị không phải class (config, hằng số, interface), ta cần một token tường minh — thường là string hoặc tốt hơn là Symbol / hằng số để tránh trùng lặp. Khi đó phải dùng @Inject(TOKEN) ở constructor vì Nest không suy ra được từ type.
constructor(@Inject('CONFIG') private cfg: AppConfig) {}
Scope quyết định vòng đời và cách chia sẻ instance của provider:
Lưu ý: scope lan truyền lên chuỗi dependency, nên dùng REQUEST scope có thể ảnh hưởng hiệu năng.
Provider mặc định chỉ nằm trong phạm vi module đã khai báo nó. Để module khác dùng được, module chứa provider phải exports provider đó, và module cần dùng phải imports module đó.
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
Nest không dùng chung global scope tự động — quan hệ chia sẻ luôn tường minh qua imports/exports, giúp ranh giới module rõ ràng.
Module đánh dấu @Global() sẽ đưa các provider mà nó exports vào phạm vi toàn cục: các module khác dùng được mà không cần import lại. Thường dùng cho những thứ dùng khắp nơi như config, database, logging. Tuy nhiên nên dùng hạn chế vì nó làm mờ ranh giới dependency và khó theo dõi nguồn gốc provider. Module vẫn phải được import một lần ở root để đăng ký.
Dynamic module là module trả về cấu hình tùy biến lúc runtime thay vì tĩnh, thường qua static method trả về DynamicModule. Quy ước phổ biến:
forRoot(): cấu hình module một lần ở root (ví dụ kết nối database, options toàn cục).forFeature(): đăng ký phần cụ thể theo từng feature module (ví dụ các entity/repository cần dùng).TypeOrmModule.forRoot({ /* connection */ });
TypeOrmModule.forFeature([User]);
Nó cho phép tái sử dụng module với config khác nhau.
Controller (@Controller('prefix')) nhóm các route dưới một tiền tố. Các HTTP method decorator định nghĩa endpoint: @Get(), @Post(), @Put(), @Patch(), @Delete(). Tham số được trích qua decorator: @Param(), @Query(), @Body(), @Headers(), @Req().
@Controller('users')
export class UsersController {
@Get(':id')
findOne(@Param('id') id: string) { /* ... */ }
}
Nest tự map giá trị trả về của method thành response body với status code phù hợp.
DTO (Data Transfer Object) là class mô tả shape của dữ liệu truyền vào/ra endpoint. Trong Nest, DTO thường là class (không phải interface) để giữ được ở runtime nhằm gắn decorator validation của class-validator. Lợi ích: định nghĩa hợp đồng dữ liệu rõ ràng, cho phép validation tự động, và làm nguồn để sinh tài liệu Swagger.
export class CreateUserDto {
@IsString() name: string;
@IsEmail() email: string;
}
Ta gắn decorator từ class-validator (@IsString, @IsEmail, @Min...) lên DTO, rồi bật ValidationPipe (thường ở global). Khi request đến, pipe dùng class-transformer chuyển plain object thành instance DTO và validate; nếu sai sẽ tự ném 400 Bad Request. Nên bật option whitelist: true để loại field thừa và transform: true để tự ép kiểu.
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
Pipe hoạt động ngay trước khi giá trị được truyền vào handler, dùng cho hai mục đích: transformation (biến đổi input) và validation (kiểm tra hợp lệ). Các pipe có sẵn gồm ValidationPipe, ParseIntPipe, ParseUUIDPipe, ParseBoolPipe, DefaultValuePipe. Ví dụ ParseIntPipe ép :id từ string sang number và ném lỗi nếu không hợp lệ.
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {}
Custom pipe là class @Injectable() implement interface PipeTransform với method transform(value, metadata). Trong đó ta biến đổi hoặc kiểm tra value, trả về giá trị đã xử lý hoặc ném exception.
@Injectable()
export class TrimPipe implements PipeTransform {
transform(value: string) {
return typeof value === 'string' ? value.trim() : value;
}
}
Tham số metadata chứa thông tin như kiểu và loại tham số (body, query...).
Guard là class implement CanActivate, method canActivate() trả về boolean (hoặc Promise/Observable) quyết định request có được tiếp tục hay không. Guard chạy sau middleware nhưng trước pipe/interceptor, nên phù hợp cho authentication/authorization. Nếu trả false, Nest ném 403 Forbidden.
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(ctx: ExecutionContext) {
const req = ctx.switchToHttp().getRequest();
return Boolean(req.user);
}
}
Interceptor implement NestInterceptor với method intercept(context, next). Nó có thể can thiệp trước và sau khi handler chạy vì bao quanh luồng bằng RxJS Observable. Use case điển hình: transform response (bọc data theo format chuẩn), logging thời gian xử lý, đặt timeout, caching, xử lý ngoại lệ.
intercept(ctx: ExecutionContext, next: CallHandler) {
const now = Date.now();
return next.handle().pipe(tap(() => console.log(Date.now() - now)));
}
Interceptor dùng toán tử RxJS map trên next.handle() để định hình lại giá trị trả về của handler, ví dụ bọc mọi response trong một envelope chuẩn { data, statusCode }.
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(ctx: ExecutionContext, next: CallHandler) {
return next.handle().pipe(map((data) => ({ data })));
}
}
Đây là cách chuẩn để chuẩn hóa format API mà không lặp code trong từng controller.