https://docs.nestjs.com/
https://docs.nestjs.com/first-steps
https://docs.nestjs.com/modules
https://docs.nestjs.com/controllers
https://docs.nestjs.com/providers
https://docs.nestjs.com/techniques/validation
https://docs.nestjs.com/techniques/configuration
https://docs.nestjs.com/guards
https://docs.nestjs.com/interceptors
https://docs.nestjs.com/exception-filters
https://docs.nestjs.com/fundamentals/testing
https://docs.nestjs.com/openapi/introduction

1. Important Points#

NestJS 是 Node.js server-side application framework:
    TypeScript first
    built around module / controller / provider
    default HTTP platform can use Express
    supports Fastify adapter
    supports DI / decorator / metadata
    good for API / BFF / worker / microservice gateway

适合:
    structured backend API
    medium / large TypeScript service
    team needs clear module boundary
    validation / guard / interceptor / OpenAPI are required

不适合:
    very small script
    simple one-file HTTP service
    team dislikes decorator-heavy style
    CPU-heavy workload without worker / queue separation
核心原则:
    keep controller thin
    put business logic in service
    use DTO for request boundary
    enable ValidationPipe globally
    validate config at startup
    module should own one business capability
    database client should be one shared provider
    do not leak ORM query shape into controller

2. Service Configuration#

create project#

npm i -g @nestjs/cli

nest new order-api
cd order-api

npm install @nestjs/config zod
npm install drizzle-orm postgres
npm install -D drizzle-kit
notes:
    @nestjs/cli:
        scaffolds NestJS project

    @nestjs/config:
        loads environment config

    zod:
        validates config / request body when needed

    drizzle-orm / drizzle-kit:
        type-safe SQL ORM and migration tool

package scripts#

{
  "scripts": {
    "start:dev": "nest start --watch",
    "build": "nest build",
    "start:prod": "node dist/main.js",
    "test": "jest",
    "test:e2e": "jest --config ./test/jest-e2e.json",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate",
    "db:studio": "drizzle-kit studio"
  }
}

project structure#

order-api
├── src
│   ├── main.ts
│   ├── app.module.ts
│   ├── config
│   │   ├── app-config.ts
│   │   └── config.schema.ts
│   ├── database
│   │   ├── database.module.ts
│   │   ├── database.provider.ts
│   │   └── schema.ts
│   └── orders
│       ├── dto
│       │   ├── create-order.dto.ts
│       │   └── update-order-status.dto.ts
│       ├── orders.controller.ts
│       ├── orders.module.ts
│       ├── orders.repository.ts
│       └── orders.service.ts
├── drizzle.config.ts
└── test
    └── orders.e2e-spec.ts
structure rules:
    app.module.ts:
        root composition only

    main.ts:
        global pipes / filters / swagger / app listen

    database/:
        connection provider and schema

    feature module:
        controller + service + repository + dto

3. Core Concepts#

module#

import { Module } from '@nestjs/common';
import { OrdersController } from './orders.controller';
import { OrdersService } from './orders.service';
import { OrdersRepository } from './orders.repository';

@Module({
  controllers: [OrdersController],
  providers: [OrdersService, OrdersRepository],
  exports: [OrdersService],
})
export class OrdersModule {}
module 用来组织 dependency graph:
    controllers:
        HTTP entrypoints

    providers:
        service / repository / client / factory

    exports:
        what other modules can inject

controller#

import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { CreateOrderDto } from './dto/create-order.dto';
import { OrdersService } from './orders.service';

@Controller('orders')
export class OrdersController {
  constructor(private readonly ordersService: OrdersService) {}

  @Post()
  create(@Body() dto: CreateOrderDto) {
    return this.ordersService.create(dto);
  }

  @Get(':id')
  findById(@Param('id') id: string) {
    return this.ordersService.findById(id);
  }
}
controller should:
    parse route / body / query
    call service
    return response DTO

controller should not:
    open database connection
    contain business workflow
    know SQL details

provider / service#

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateOrderDto } from './dto/create-order.dto';
import { OrdersRepository } from './orders.repository';

@Injectable()
export class OrdersService {
  constructor(private readonly ordersRepository: OrdersRepository) {}

  async create(dto: CreateOrderDto) {
    return this.ordersRepository.create(dto);
  }

  async findById(id: string) {
    const order = await this.ordersRepository.findById(id);
    if (!order) {
      throw new NotFoundException('order not found');
    }
    return order;
  }
}
service should:
    coordinate business workflow
    enforce business rule
    call repository / external client

repository should:
    own database query
    hide ORM details from service

4. Request / Runtime Best Practices#

validation#

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    }),
  );

  await app.listen(process.env.PORT ?? 3000);
}

bootstrap();
import { IsInt, IsString, Min } from 'class-validator';

export class CreateOrderDto {
  @IsString()
  userId!: string;

  @IsInt()
  @Min(1)
  amountCents!: number;
}
validation baseline:
    whitelist:
        remove properties without decorators

    forbidNonWhitelisted:
        reject unexpected properties

    transform:
        transform plain request object into DTO class

configuration#

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { z } from 'zod';

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'test', 'staging', 'production']),
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
});

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validate: (config) => envSchema.parse(config),
    }),
  ],
})
export class AppModule {}
config rules:
    fail fast at startup
    never read process.env everywhere
    keep .env.example updated
    production secrets should come from secret manager / mounted secret / env injection

exception / response#

use NestJS built-in HTTP exceptions:
    BadRequestException
    UnauthorizedException
    ForbiddenException
    NotFoundException
    ConflictException
    InternalServerErrorException

avoid:
    throw new Error() from controller for client errors
    return null for missing resource without explicit behavior

5. Security Best Practices#

security defaults:
    enable validation pipe globally
    reject unknown request properties
    use helmet in public HTTP API
    configure CORS explicitly
    do not log request body with secrets
    use guard for authn/authz
    keep rate limit at gateway / app layer
    expose Swagger only in controlled environment

guard#

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    return request.headers['x-api-key'] === process.env.INTERNAL_API_KEY;
  }
}
guard 用于:
    authentication
    authorization
    route access policy

不要把业务校验全部塞进 guard。

6. Reliability / Testing#

unit test#

import { Test } from '@nestjs/testing';
import { OrdersService } from './orders.service';
import { OrdersRepository } from './orders.repository';

describe('OrdersService', () => {
  it('returns order by id', async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [
        OrdersService,
        {
          provide: OrdersRepository,
          useValue: {
            findById: jest.fn().mockResolvedValue({ id: 'ord_1' }),
          },
        },
      ],
    }).compile();

    const service = moduleRef.get(OrdersService);
    await expect(service.findById('ord_1')).resolves.toEqual({ id: 'ord_1' });
  });
});

e2e test#

e2e test should cover:
    route path
    validation failure
    auth failure
    success response
    database transaction behavior if applicable

7. Monitoring#

logs:
    request_id
    method
    path
    status_code
    duration_ms
    user_id / tenant_id when safe
    error_name

metrics:
    http_request_duration_seconds
    http_requests_total
    validation_error_total
    database_query_duration_seconds
    unhandled_exception_total

alerts:
    5xx rate high
    p95 / p99 latency high
    database connection pool exhausted
    error log spike

8. Hands-on#

install validation packages#

npm install class-validator class-transformer

create resource#

nest g module orders
nest g controller orders
nest g service orders

run#

npm run start:dev

curl -X POST http://localhost:3000/orders \
  -H 'content-type: application/json' \
  -d '{"userId":"user_1","amountCents":1000}'

swagger#

npm install @nestjs/swagger swagger-ui-express
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

const config = new DocumentBuilder()
  .setTitle('order-api')
  .setVersion('1.0.0')
  .build();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);

9. Production Checklist#

application:
    global ValidationPipe enabled
    config validation enabled
    CORS configured explicitly
    Swagger controlled by environment
    graceful shutdown enabled
    health endpoint available

architecture:
    controller is thin
    service owns business rules
    repository owns database details
    modules have clear boundaries

security:
    auth guard applied
    secrets not logged
    dependency audit in CI
    rate limit / WAF considered

operations:
    structured JSON logs
    metrics exported
    e2e tests in CI
    database migration process documented