Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026

NestJS là framework Node.js xây dựng trên TypeScript, lấy cảm hứng từ Angular. Nó cung cấp kiến trúc module rõ ràng với @Module(), @Controller(), @Injectable() và Dependency Injection tích hợp sẵn.

Lý do chọn NestJS thay Express thuần: kiến trúc có cấu trúc (Modules/Controllers/Services phân tách rõ ràng), TypeScript-first với decorators và metadata reflection, IoC container tích hợp dễ test, hỗ trợ Microservices/GraphQL/WebSockets/gRPC. NestJS vẫn chạy trên Express (hoặc Fastify) bên dưới, nhưng thêm lớp abstraction giúp code dễ maintain và scale hơn.

Module là đơn vị tổ chức cơ bản trong NestJS, nhóm các thành phần liên quan lại. Mỗi app có ít nhất một root module (AppModule).

@Module() nhận một object với 4 thuộc tính: imports (modules khác cần dùng), controllers (xử lý HTTP requests), providers (services, repositories, guards...), và exports (providers cho phép modules khác sử dụng). Chỉ những providers được exports mới có thể được inject ở module khác.

Các loại module: Feature Module nhóm theo tính năng (UsersModule, AuthModule), Shared Module export providers để tái sử dụng, Global Module dùng @Global() để providers available toàn app không cần import, Dynamic Module cấu hình runtime qua forRoot() / forRootAsync().

Controller chịu trách nhiệm nhận HTTP requests và trả về responses. Controller map routes đến handler methods thông qua decorators.

@Controller('users') đặt base route /users. Các HTTP method decorators: @Get(), @Post(), @Patch(), @Put(), @Delete(). Có thể thêm path vào decorator như @Get(':id') để tạo route động.

Parameter decorators để extract data từ request: @Param('id') lấy route param, @Query() lấy query string, @Body() lấy request body, @Headers() lấy headers, @Req() / @Res() để access raw request/response (dùng @Res() sẽ mất một số tính năng NestJS như interceptors).

Provider là bất kỳ class nào được annotate với @Injectable() — services, repositories, factories, helpers. NestJS quản lý vòng đời và inject chúng tự động thông qua constructor injection.

Cách hoạt động: khai báo provider trong providers array của module, NestJS IoC container tạo instance và inject vào các class phụ thuộc qua constructor. Reflector đọc TypeScript metadata để biết type cần inject.

Scope của providers: DEFAULT (Singleton) — một instance cho toàn app, REQUEST — instance mới cho mỗi request, TRANSIENT — instance mới mỗi lần inject. Custom providers cho phép linh hoạt hơn: useValue để inject giá trị cụ thể, useFactory để tạo provider với logic phức tạp, useClass để swap implementation.

@nestjs/config giúp quản lý environment variables an toàn với type-safety. ConfigModule.forRoot() với isGlobal: true cho phép inject ConfigService ở bất kỳ module nào mà không cần import lại.

Validation schema với Joi: validationSchema: Joi.object({ PORT: Joi.number().default(3000), JWT_SECRET: Joi.string().min(32).required() }) — app sẽ fail ngay khi start nếu env vars thiếu hoặc sai format.

Namespaced config với registerAs('database', () => ({ host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT) })) cho phép nhóm config và inject type-safe qua @Inject(databaseConfig.KEY) private dbConfig: ConfigType<typeof databaseConfig>. ConfigService.get<string>('KEY') dùng khi không cần namespace, ConfigService.get<string>('database.host') với dot notation cho nested config.

NestJS tích hợp Swagger qua @nestjs/swagger. Setup trong main.ts: DocumentBuilder cấu hình title/description/version/auth, SwaggerModule.createDocument() tạo document, SwaggerModule.setup('api/docs', app, document) serve UI.

Annotations trên DTO: @ApiProperty({ example, description }) cho required fields, @ApiPropertyOptional() cho optional fields. Hỗ trợ enum, type, minLength, maxLength, default.

Annotations trên Controller: @ApiTags('GroupName') nhóm endpoints trong Swagger UI, @ApiBearerAuth() chỉ định cần JWT, @ApiOperation({ summary }) mô tả endpoint, @ApiResponse({ status, type, description }) document các response codes. Param annotations: @ApiParam() cho route params, @ApiQuery() cho query params. PickType, OmitType, PartialType từ @nestjs/swagger để tạo derived DTOs tái sử dụng schema.

NestJS xử lý request qua pipeline theo thứ tự cố định: Middleware chạy đầu tiên (logging, CORS, session — access raw req/res), tiếp theo Guards kiểm tra authorization (trả về true/false), rồi Interceptors pre-processing (transform request trước khi vào handler), sau đó Pipes validate và transform input data, tiếp theo Controller Handler thực thi business logic chính, rồi Interceptors post-processing (transform response), cuối cùng Exception Filters bắt và format errors thành response chuẩn.

Middleware và Guards đều chạy trước handler nhưng Guards có access vào ExecutionContext nên biết handler nào sẽ được gọi. Exception Filters chỉ hoạt động khi có exception, nếu không có lỗi thì bỏ qua. Mỗi layer có thể áp dụng theo thứ tự: global → controller → route.

Guards quyết định request có được phép đi tiếp không (authorization). Khác với Middleware, Guards implement interface CanActivate và có access vào ExecutionContext — biết được handler nào sẽ được gọi, rất hữu ích cho role-based access control.

JWT Auth Guard hoạt động: extract Bearer token từ header Authorization, verify token bằng JwtService.verify(), nếu hợp lệ attach payload vào request.user và trả về true, ngược lại throw UnauthorizedException. Guards có thể áp dụng với @UseGuards() ở mức route, controller, hoặc global qua APP_GUARD provider.

Public decorator pattern: dùng SetMetadata('isPublic', true) với @Public() decorator, trong guard đọc metadata qua Reflector để bỏ qua authentication cho các route công khai.

Pipes có hai use-case chính: validation (throw exception nếu data không hợp lệ) và transformation (chuyển đổi input sang dạng mong muốn). Pipes implement interface PipeTransform với method transform(value, metadata).

ValidationPipe của NestJS kết hợp với class-validator để validate DTO tự động. Cấu hình quan trọng: whitelist: true loại bỏ các properties không khai báo trong DTO, forbidNonWhitelisted: true throw error thay vì strip, transform: true tự động chuyển đổi type (string sang number), enableImplicitConversion: true convert dựa trên TypeScript type.

DTO dùng decorators từ class-validator như @IsString(), @IsEmail(), @MinLength(), @IsOptional(), @IsEnum(). class-transformer cung cấp @Transform() để biến đổi giá trị trước khi validate. Pipe global đăng ký qua app.useGlobalPipes() hoặc APP_PIPE provider.

Interceptors wrap việc thực thi handler, cho phép chạy code trước và sau handler. Chúng implement NestInterceptor với method intercept(context, next) trả về Observable. Gọi next.handle() để tiếp tục pipeline, dùng RxJS operators để transform.

Use-cases phổ biến: Response transform — dùng map() để wrap tất cả response trong object chuẩn { success: true, data, timestamp }. Logging — ghi thời gian xử lý với tap(). Caching — kiểm tra cache trước, nếu hit thì return of(cachedData) bỏ qua handler. Timeout — dùng timeout(5000) throw TimeoutError sau 5 giây. Error mapping — catchError() để transform exceptions.

Apply với @UseInterceptors() ở route/controller hoặc global qua APP_INTERCEPTOR. Khác Guard và Pipe, Interceptors chạy cả trước lẫn sau handler nên có thể transform response.

Exception Filters bắt các exceptions được throw trong ứng dụng và format response lỗi. NestJS có built-in filter xử lý HttpException và các subclass của nó. Nếu exception không phải HttpException, NestJS trả về 500 Internal Server Error mặc định.

Custom global filter implement ExceptionFilter với method catch(exception, host). host.switchToHttp() lấy req/res. Trong filter có thể check instanceof HttpException để lấy status code, log lỗi, format response chuẩn với statusCode, message, timestamp, path.

Built-in HTTP exceptions: NotFoundException, BadRequestException, UnauthorizedException, ForbiddenException, ConflictException, InternalServerErrorException... Đăng ký global qua app.useGlobalFilters() hoặc preferred là { provide: APP_FILTER, useClass: ... } để hỗ trợ DI.

TypeORM là một trong những ORM phổ biến nhất với NestJS (Prisma cũng được ưa chuộng trong các project mới), sử dụng Data Mapper pattern thông qua Repository. Setup với TypeOrmModule.forRoot() trong AppModule cấu hình connection (type, host, port, credentials, entities path), synchronize: false trong production — dùng migrations thay.

Entity định nghĩa bằng @Entity() decorator với các column decorators: @PrimaryGeneratedColumn(), @Column(), @CreateDateColumn(), @UpdateDateColumn(). Relations: @OneToMany(), @ManyToOne(), @ManyToMany(), @OneToOne().

Trong feature module, TypeOrmModule.forFeature([Entity]) đăng ký Repository. Service inject @InjectRepository(Entity) để dùng Repository<Entity> với các methods: find(), findOne({ where: { id } }), create(), save(), update(), delete(). Lưu ý: TypeORM 0.3+ yêu cầu findOne() phải có { where: {...} }. Query Builder cho queries phức tạp: createQueryBuilder('alias').leftJoinAndSelect().where().getMany().

JWT Auth flow gồm hai phần chính: AuthService xác thực credentials và cấp token, JwtStrategy/Guard bảo vệ routes. Cài đặt @nestjs/jwt, @nestjs/passport, passport, passport-jwt, bcryptjs. Tạo AuthModule import JwtModule.registerAsync() với ConfigService để lấy JWT_SECRETexpiresIn.

AuthService có method login(): tìm user theo email, compare password với bcrypt.compare(), nếu đúng sign JWT với payload { sub: userId, email, role } và trả về access_token (ngắn hạn 15m) + refresh_token (dài hạn 7d).

JwtStrategy extends PassportStrategy(Strategy) với config jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()secretOrKey. Method validate(payload) attach user vào request. Guard JwtAuthGuard extends AuthGuard('jwt') từ Passport. @Public() decorator dùng SetMetadata để bỏ qua guard cho login/register endpoints.

NestJS dùng Jest và cung cấp Test.createTestingModule() để tạo test environment với DI container đầy đủ.

Unit test service: tạo mock repository với jest.fn() cho mỗi method, đăng ký trong module với { provide: getRepositoryToken(Entity), useValue: mockRepo }. Test từng method riêng lẻ, verify calls với expect(mock).toHaveBeenCalledWith(), reset mocks với jest.clearAllMocks() sau mỗi test.

E2E/Integration test: dùng Test.createTestingModule({ imports: [AppModule] }), override providers với .overrideProvider(Service).useValue(mockService), tạo app với moduleFixture.createNestApplication(), init global pipes/guards, dùng supertest để call real HTTP endpoints. Ưu tiên override service thay vì repository cho integration tests để test controller + service logic mà không cần DB thật.

Prisma là ORM thế hệ mới với type-safety tuyệt vời, ngày càng được ưa dùng thay TypeORM. Schema định nghĩa trong prisma/schema.prisma với cú pháp riêng, prisma generate tạo Prisma Client type-safe hoàn toàn.

Setup NestJS: tạo PrismaService extends PrismaClient implements OnModuleInit, gọi this.$connect() trong onModuleInit(). Wrap trong @Global() @Module() để dùng toàn app. Prisma Client API rất fluent: prisma.user.findMany({ include, where, orderBy }), prisma.user.create({ data }), transactions với prisma.$transaction([]).

So sánh Prisma vs TypeORM: Prisma có type-safety tuyệt vời (auto-generated types từ schema), prisma migrate dev rõ ràng an toàn hơn synchronize: true của TypeORM. TypeORM quen thuộc với Java/Spring developers, hỗ trợ Active Record pattern. Prisma không hỗ trợ MongoDB aggregation pipeline tốt bằng Mongoose. Hiện tại Prisma được cộng đồng ưa chuộng hơn cho dự án mới.

NestJS tích hợp Multer qua @nestjs/platform-express để xử lý multipart/form-data. Không cần install thêm gì với Express adapter.

Upload single file: dùng @UseInterceptors(FileInterceptor('fieldName', options))@UploadedFile() decorator. Options quan trọng: storagediskStorage() lưu disk hoặc memoryStorage() lưu buffer (dùng khi upload S3), fileFilter để reject file không hợp lệ, limits.fileSize giới hạn kích thước.

Upload multiple files: FilesInterceptor('fieldName', maxCount) với @UploadedFiles(). ParseFilePipe với validators MaxFileSizeValidatorFileTypeValidator là cách clean nhất để validate. Upload S3: dùng memoryStorage() để lấy file.buffer, gọi AWS SDK s3.putObject() với buffer và file.mimetype. Luôn generate tên file ngẫu nhiên để tránh xung đột và directory traversal.

Custom Decorators giúp code sạch hơn và tái sử dụng được. NestJS cung cấp createParamDecorator cho param decorators và SetMetadata để đính kèm metadata.

@CurrentUser: dùng createParamDecorator((data, ctx) => ctx.switchToHttp().getRequest().user). Có thể nhận tham số để extract field cụ thể: @CurrentUser('email') trả về user.email, @CurrentUser() trả về toàn bộ user object. Dùng trong controller thay vì @Request() req rồi req.user.

@Roles(...roles): dùng SetMetadata('roles', roles) để đính kèm metadata. RolesGuard implement CanActivate, dùng Reflector.getAllAndOverride() để đọc required roles từ handler và class, so sánh với request.user.role.

@Public(): SetMetadata(IS_PUBLIC_KEY, true), đọc trong JwtAuthGuard — nếu isPublic === true thì return true ngay mà không verify token. Pattern này cho phép global guard nhưng vẫn có public routes.

NestJS có 3 provider scopes kiểm soát vòng đời instance:

DEFAULT (Singleton): một instance dùng cho toàn app — đây là default và phổ biến nhất. Phù hợp cho stateless services như DatabaseService, ConfigService.

REQUEST: tạo instance mới cho mỗi incoming request, bị destroy sau khi response xong. Dùng khi service cần lưu request-specific data (tenant context, request ID, user info). Nhược điểm: tốn memory hơn và các dependencies của nó cũng bị kéo thành REQUEST scope.

TRANSIENT: tạo instance mới mỗi lần được inject — không share giữa các consumers. Dùng khi cần isolated state mỗi lần dùng.

Scope injection chain: nếu SERVICE_A (REQUEST scope) được inject vào SERVICE_B, SERVICE_B cũng tự động trở thành REQUEST scope. Pitfall: dùng REQUEST scope tràn lan làm giảm performance đáng kể — chỉ dùng khi thực sự cần.

Dynamic modules cho phép configure module lúc runtime với tham số — khác static modules cấu hình cứng trong code.

forRoot(options) là synchronous factory nhận options và trả về DynamicModule. forRootAsync(options) hỗ trợ async config như đọc từ ConfigService:

typescript
// Cách implement forRootAsync trong custom module
static forRootAsync(options: AsyncOptions): DynamicModule {
  return {
    module: DatabaseModule,
    imports: options.imports || [],
    providers: [
      {
        provide: DATABASE_OPTIONS,
        useFactory: options.useFactory,
        inject: options.inject || [],
      },
      DatabaseService,
    ],
    exports: [DatabaseService],
  };
}

Dùng trong AppModule:

typescript
DatabaseModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (config: ConfigService) => ({
    url: config.get('DATABASE_URL'),
  }),
  inject: [ConfigService],
})

Pattern này dùng trong TypeOrmModule.forRootAsync(), JwtModule.registerAsync(), CacheModule.registerAsync().

Custom providers cho phép kiểm soát cách NestJS tạo và inject dependencies:

useValue: inject giá trị cụ thể — thường dùng cho config objects, mocking trong tests:

typescript
{ provide: 'CONFIG', useValue: { apiKey: 'abc' } }

useClass: chỉ định class khác để inject — dùng để swap implementation (mock, stub):

typescript
{ provide: UserService, useClass: MockUserService }

useFactory: factory function tạo provider — hỗ trợ async và inject dependencies:

typescript
{ provide: 'DB', useFactory: async (config: ConfigService) => {
  return createConnection(config.get('DB_URL'))
}, inject: [ConfigService] }

useExisting: alias — inject cùng instance từ token khác:

typescript
{ provide: 'LOGGER', useExisting: WinstonLogger }

Sử dụng string token cần @Inject('TOKEN') decorator trong constructor vì TypeScript không thể reflect string literals.

ExecutionContext extends ArgumentsHost, cung cấp thông tin về execution context hiện tại (HTTP, WebSocket, RPC). Guards, Interceptors và Exception Filters đều nhận ExecutionContext.

Các methods quan trọng:
- getType(): trả về 'http' | 'ws' | 'rpc'
- switchToHttp(): trả về HttpArgumentsHost với getRequest(), getResponse()
- switchToWs(): trả về WsArgumentsHost với getData(), getClient()
- switchToRpc(): cho microservices
- getHandler(): trả về handler function đang được gọi
- getClass(): trả về controller class

Dùng getHandler()getClass() kết hợp Reflector để đọc metadata:

typescript
const roles = this.reflector.getAllAndOverride<string[]>('roles', [
  context.getHandler(),  // Route-level metadata
  context.getClass(),    // Controller-level metadata
]);

Pattern này cho phép metadata được định nghĩa ở cả route lẫn controller, với route-level ưu tiên hơn.

Transactions đảm bảo multiple DB operations thành công hoặc rollback toàn bộ.

Cách 1 — QueryRunner (recommend cho complex transactions):

typescript
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
  await queryRunner.manager.save(User, user);
  await queryRunner.manager.save(Profile, profile);
  await queryRunner.commitTransaction();
} catch (err) {
  await queryRunner.rollbackTransaction();
  throw err;
} finally {
  await queryRunner.release();
}

Cách 2 — EntityManager.transaction() (cleaner cho simple cases):

typescript
await this.dataSource.transaction(async manager => {
  await manager.save(User, user);
  await manager.save(Profile, profile);
  // Tự động rollback nếu throw
});

Cách 3 — @Transaction decorator (deprecated trong TypeORM 0.3+, không dùng).

Pitfall: không mix repository từ DI và queryRunner.manager trong cùng transaction — chúng dùng connection pool khác nhau.

Repository API (High-level): phù hợp cho CRUD đơn giản, dễ đọc, type-safe:

typescript
const users = await this.usersRepo.find({
  where: { isActive: true, role: Role.USER },
  relations: ['profile'],
  order: { createdAt: 'DESC' },
  take: 20, skip: 0,
});

Query Builder (Low-level): cho queries phức tạp với dynamic conditions, subqueries, raw SQL expressions:

typescript
const result = await this.usersRepo
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .where('user.isActive = :active', { active: true })
  .andWhere('post.publishedAt > :date', { date: lastWeek })
  .select(['user.id', 'user.email', 'COUNT(post.id) as postCount'])
  .groupBy('user.id')
  .having('COUNT(post.id) > 0')
  .orderBy('postCount', 'DESC')
  .getRawMany();

Dùng Repository API cho 80% cases.

Dùng Query Builder khi: complex JOINs, aggregations (COUNT/SUM/AVG), dynamic WHERE conditions, raw SQL expressions cần.

Helmet: HTTP security headers middleware — ngăn chặn XSS, clickjacking, sniffing:

typescript
import helmet from 'helmet';
app.use(helmet()); // Thêm vào main.ts

CORS: chỉ allow origins cụ thể:

typescript
app.enableCors({
  origin: ['https://yourdomain.com'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
});

Rate Limiting với @nestjs/throttler:

typescript
ThrottlerModule.forRoot([
  { name: 'short', ttl: 1000, limit: 3 },     // 3 req/s
  { name: 'medium', ttl: 10000, limit: 20 },  // 20 req/10s
  { name: 'long', ttl: 60000, limit: 100 },   // 100 req/min
])

Input sanitization: class-validator + ValidationPipe với whitelist: true ngăn chặn mass assignment.

Dùng sanitize-html cho user-generated content. SQL Injection: TypeORM parameterized queries tự động escape — không bao giờ dùng raw string interpolation trong queries.

JWT (Stateless): token mang đủ thông tin, server không cần lưu state. Phù hợp:
- Microservices và distributed systems
- Mobile apps (localStorage/SecureStorage)
- Stateless REST APIs
- Cross-domain authentication

Sessions (Stateful): server lưu session data (DB hoặc Redis), client chỉ giữ session ID trong cookie. Phù hợp:
- Traditional web apps với server-side rendering
- Cần revoke session ngay lập tức (banking, admin)
- Không muốn expose user data trong token

NestJS Session setup với express-session + Redis:

typescript
app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { secure: true, httpOnly: true, maxAge: 86400000 },
}));

Pitfall JWT: không thể revoke trước khi hết hạn trừ khi maintain blacklist (làm mất đi lợi thế stateless).

Pitfall Session: cần sticky sessions hoặc centralized store (Redis) khi scale horizontally.

Unit tests trong NestJS isolate một class bằng cách mock tất cả dependencies.

Pattern chuẩn với Test.createTestingModule():

typescript
describe('UsersService', () => {
  let service: UsersService;
  let repo: jest.Mocked<Repository<User>>;

  beforeEach(async () => {
    const mockRepo = {
      find: jest.fn(),
      findOne: jest.fn(),
      save: jest.fn(),
      delete: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: getRepositoryToken(User), useValue: mockRepo },
      ],
    }).compile();

    service = module.get<UsersService>(UsersService);
    repo = module.get(getRepositoryToken(User));
  });

  it('should find user by id', async () => {
    const user = { id: 1, email: 'test@test.com' };
    repo.findOne.mockResolvedValue(user);

    const result = await service.findOne(1);
    expect(result).toEqual(user);
    expect(repo.findOne).toHaveBeenCalledWith({ where: { id: 1 } });
  });
});

Dùng jest.spyOn() để spy mà không replace hoàn toàn.

Dùng jest.clearAllMocks() trong afterEach để reset state.

E2E tests test toàn bộ HTTP flow từ request đến response mà không cần real external services.

typescript
// test/users.e2e-spec.ts
describe('Users (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleFixture = await Test.createTestingModule({
      imports: [AppModule],
    })
    .overrideProvider(UsersService)  // Override real service
    .useValue(mockUsersService)
    .compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
    app.useGlobalFilters(new HttpExceptionFilter());
    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  it('GET /users — returns users list', () => {
    return request(app.getHttpServer())
      .get('/users')
      .set('Authorization', `Bearer ${testToken}`)
      .expect(200)
      .expect(res => {
        expect(res.body.data).toBeInstanceOf(Array);
      });
  });
});

Best practices: dùng test database thực (SQLite in-memory hoặc test Postgres), seed data trong beforeAll, cleanup trong afterAll.

Chạy E2E riêng biệt với jest --testPathPattern=e2e.

NestJS cung cấp @nestjs/event-emitter (wrapper của EventEmitter2) cho internal events — không phải distributed messaging mà là in-process pub/sub.

typescript
// Setup
EventEmitterModule.forRoot({ wildcard: true, delimiter: '.' })

// Emit từ service
import { EventEmitter2 } from '@nestjs/event-emitter';
this.eventEmitter.emit('order.created', new OrderCreatedEvent(order));

// Listen với @OnEvent
@OnEvent('order.created')
async handleOrderCreated(event: OrderCreatedEvent) {
  await this.emailService.sendOrderConfirmation(event.order);
}

// Wildcard
@OnEvent('order.*')  // Bắt tất cả order events
async handleAllOrderEvents(event: any) { ... }

Async events: @OnEvent('order.created', { async: true }) để handler chạy async không block emitter.

Dùng cho: decoupling business logic (sau khi create order, nhiều services cần xử lý), audit logging, notifications. Pitfall: không dùng cho cross-service communication — dùng Kafka/RabbitMQ thay.

Offset-based (skip/take): đơn giản, hỗ trợ random page access:

typescript
async findAll(page: number, limit: number) {
  const [data, total] = await this.repo.findAndCount({
    take: limit, skip: (page - 1) * limit,
    order: { createdAt: 'DESC' },
  });
  return { data, total, page, lastPage: Math.ceil(total / limit) };
}

Nhược điểm: không ổn định khi data thay đổi real-time, chậm khi skip lớn (DB phải scan).

Cursor-based (keyset pagination): ổn định hơn cho real-time feeds, hiệu quả hơn khi scale:

typescript
async findAfterCursor(cursor: string, limit: number) {
  const decodedCursor = Buffer.from(cursor, 'base64').toString();
  // cursor = ISO timestamp của item cuối cùng
  const data = await this.repo.find({
    where: { createdAt: LessThan(new Date(decodedCursor)) },
    take: limit + 1,  // +1 để biết có page tiếp không
    order: { createdAt: 'DESC' },
  });
  const hasMore = data.length > limit;
  const items = hasMore ? data.slice(0, -1) : data;
  const nextCursor = hasMore ? Buffer.from(items.at(-1)!.createdAt.toISOString()).toString('base64') : null;
  return { items, nextCursor, hasMore };
}

Dùng offset cho: admin dashboards, search results.

Dùng cursor cho: social feeds, infinite scroll.

class-transformer cùng với ClassSerializerInterceptor tự động serialize/exclude fields trong response.

Exclude sensitive fields (password, tokens):

typescript
import { Exclude, Expose, Transform } from 'class-transformer';

export class UserEntity {
  id: number;
  email: string;

  @Exclude()  // Không expose trong response
  password: string;

  @Expose()
  @Transform(({ value }) => value?.toISOString())
  createdAt: Date;

  constructor(partial: Partial<UserEntity>) {
    Object.assign(this, partial);
  }
}

Enable globally:

typescript
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));

Controller return entity:

typescript
@Get(':id')
async findOne(@Param('id') id: number): Promise<UserEntity> {
  const user = await this.usersService.findOne(id);
  return new UserEntity(user);  // Wrap trong entity class
}

Pitfall: plain objects không bị transform — phải trả về instance của entity class để decorator có effect.

TypeORM cung cấp decorators tiện lợi cho timestamps và soft delete:

typescript
import {
  CreateDateColumn, UpdateDateColumn, DeleteDateColumn,
  PrimaryGeneratedColumn, Column, Entity,
} from 'typeorm';

@Entity()
export class BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @CreateDateColumn()  // Tự set khi INSERT
  createdAt: Date;

  @UpdateDateColumn()  // Tự update khi UPDATE
  updatedAt: Date;

  @DeleteDateColumn()  // Tự set khi softDelete(), NULL khi active
  deletedAt: Date | null;
}

Soft delete với TypeORM:

typescript
// Soft delete — set deletedAt
await this.repo.softDelete(id);

// Restore
await this.repo.restore(id);

// find() tự động filter deletedAt IS NULL
// Để include deleted:
await this.repo.find({ withDeleted: true });

Pitfall: @DeleteDateColumn chỉ hoạt động khi dùng softDelete()softRemove() — không phải delete() hay remove().

Connection pooling và query optimization là hai yếu tố quan trọng nhất để NestJS app chịu tải tốt ở production.

Connection Pooling với TypeORM:

typescript
TypeOrmModule.forRootAsync({
  useFactory: (config: ConfigService) => ({
    type: 'postgres',
    url: config.get('DATABASE_URL'),
    // Connection pool settings
    extra: {
      max: 20,              // Max connections (CPU cores * 2-4 là rule of thumb)
      min: 2,               // Min connections luôn mở
      idleTimeoutMillis: 30000,
      connectionTimeoutMillis: 2000,
    },
    logging: config.get('NODE_ENV') === 'development',
  }),
})

Query optimization:
1. Dùng select để chỉ lấy columns cần thiết
2. Tạo indexes trên foreign keys và frequently queried columns
3. Dùng QueryBuilder với addSelect thay vì relations cho large datasets
4. Paginate tất cả list queries — không bao giờ findAll() không có limit
5. Slow query logging: enable logging: ['query', 'slow'] với maxQueryExecutionTime: 1000

DataSource injection (TypeORM 0.3+):

typescript
constructor(private readonly dataSource: DataSource) {}
// Dùng this.dataSource.createQueryRunner() cho transactions