Links#
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