| name | nestjs-expert |
| description | NestJS development principles and decision-making. Modern Node.js enterprise framework with TypeScript, dependency injection, modules. |
NestJS Expert - Modern Node.js Enterprise Framework
NestJS development principles and decision-making.
Learn to THINK, not copy-paste patterns.
⚠️ How to Use This Skill
This skill teaches decision-making principles for NestJS development.
- ASK user for use case (API, Microservice, WebSocket, GraphQL)
- Choose proper module structure for the project size
- Use TypeScript properly with interfaces and DTOs
1. NestJS Core Concepts
Architecture Overview
NestJS follows Angular-like architecture:
├── Modules (logical grouping)
├── Controllers (route handlers)
├── Services (business logic)
├── Providers (DI beans)
├── Pipes (validation)
├── Guards (auth)
├── Interceptors (middleware)
└── Filters (error handling)
Directory Structure by Scale
Small (的单模块):
src/
├── app.controller.ts
├── app.service.ts
├── app.module.ts
└── main.ts
Medium (分模块):
src/
├── modules/
│ ├── users/
│ │ ├── users.controller.ts
│ │ ├── users.service.ts
│ │ ├── users.module.ts
│ │ ├── dto/
│ │ └── entities/
│ └── auth/
├── common/
│ ├── decorators/
│ ├── filters/
│ ├── guards/
│ └── interceptors/
└── config/
Large (DDD):
src/
├── domain/
│ ├── users/
│ │ ├── entities/
│ │ ├── repositories/
│ │ └── services/
│ └── orders/
├── application/
│ ├── use-cases/
│ └── dtos/
├── infrastructure/
│ ├── database/
│ └── external/
└── presentation/
├── controllers/
└── modules/
2. Module System
Feature Modules
@Module({
imports: [
TypeOrmModule.forFeature([UserEntity]),
PassportModule.register({ defaultStrategy: 'jwt' }),
forwardRef(() => AuthModule),
],
controllers: [UsersController],
providers: [
UsersService,
{
provide: 'USER_REPOSITORY',
useFactory: (dataSource: DataSource) => dataSource.getRepository(UserEntity),
inject: ['DATA_SOURCE'],
},
],
exports: [UsersService],
})
export class UsersModule {}
Shared Modules
@Global()
@Module({
providers: [
{
provide: 'LOGGER',
useClass: Logger,
},
],
exports: ['LOGGER'],
})
export class CommonModule {}
3. Dependency Injection Patterns
Constructor Injection
@Injectable()
export class UsersService {
constructor(
private readonly usersRepository: UsersRepository,
private readonly emailService: EmailService,
@Inject('CONFIG') private config: ConfigService,
) {}
}
Multiple Implementations
export interface PaymentGateway {
process(amount: number): Promise<PaymentResult>;
}
@Injectable()
export class StripePaymentGateway implements PaymentGateway {
async process(amount: number): Promise<PaymentResult> {
}
}
@Injectable()
export class PayPalPaymentGateway implements PaymentGateway {
async process(amount: number): Promise<PaymentResult> {
}
}
@Injectable()
export class OrderService {
constructor(
@Inject('PaymentGateway') private paymentGateway: PaymentGateway,
) {}
}
{
provide: ,
: config. ===
?
: ,
}
Custom Providers
{
provide: 'APP_NAME',
useValue: 'MyApp',
}
{
provide: UsersService,
useClass: UsersServiceImpl,
}
{
provide: 'ANALYTICS',
useFactory: (config: ConfigService) => {
return new Analytics(config.analyticsKey);
},
inject: [ConfigService],
}
4. Controllers and Routing
REST Controller
@Controller('users')
@UseGuards(JwtAuthGuard)
@Serialize(UserDto)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
@HttpCode(200)
async findAll(@Query() query: PaginationQuery) {
return this.usersService.findAll(query.page, query.limit);
}
@Get(':id')
@HttpCode(200)
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@Post()
@HttpCode(201)
@UsePipes(new ValidationPipe({ transform: true }))
async create() {
..(createUserDto);
}
()
()
() {
..(id, updateUserDto);
}
()
()
() {
..(id);
}
}
GraphQL Controller
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly usersService: UsersService) {}
@Query(() => [User])
async users(): Promise<User[]> {
return this.usersService.findAll();
}
@Mutation(() => User)
@UseGuards(GqlJwtAuthGuard)
async createUser(@Args('createUserInput') createUserInput: CreateUserInput): Promise<User> {
return this.usersService.create(createUserInput);
}
@ResolveField()
async posts(@Parent() user: User): Promise<Post[]> {
return this.usersService.getUserPosts(user.);
}
}
WebSocket Gateway
@WebSocketGateway({
namespace: 'chat',
cors: { origin: '*' },
})
export class ChatGateway {
@WebSocketServer()
server: Server;
afterInit(server: Server) {
console.log('WebSocket Gateway initialized');
}
@SubscribeMessage('message')
handleMessage(client: Socket, payload: any): void {
const message = { ...payload, timestamp: new Date() };
this.server.emit('message', message);
}
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}
}
5. Validation and Pipes
Built-in ValidationPipe
export class CreateUserDto {
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@MinLength(8)
@MaxLength(100)
password: string;
@IsOptional()
@IsString()
name?: string;
@IsEnum(['user', 'admin', 'moderator'])
role: UserRole;
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
);
await app.();
}
Custom Pipe
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
const parsedValue = parseInt(value, 10);
if (isNaN(parsedValue)) {
throw new BadRequestException(`Invalid number: ${value}`);
}
return parsedValue;
}
}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
6. Authentication
JWT Auth with Guards
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: JwtPayload) {
const user = await this.usersService.findOne(payload.sub);
if (!user) {
throw new UnauthorizedException();
}
return { userId: payload.sub, email: payload.email, roles: payload.roles };
}
}
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(: ) {
.(context);
}
}
()
()
() {
req.;
}
Role-based Access Control
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Delete(':id')
async remove(@Param('id') id: string) {
}
7. Database Integration
TypeORM Integration
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'mydb',
entities: [User, Post],
synchronize: false,
logging: process.env.NODE_ENV === 'development',
}),
],
})
export class AppModule {}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
@Transform(({ value }) => bcrypt.hash(value), { to: 'save' })
password: string;
@Column({ : })
: ;
( , post.)
: [];
()
: ;
()
: ;
}
()
{
() {}
(): <[]> {
..();
}
(: ): <> {
user = ..({
: { id },
: [],
});
(!user) {
();
}
user;
}
}
Prisma Integration
@Injectable()
export class PrismaService extends PrismaClient {
constructor(config: ConfigService) {
super({
datasources: {
db: {
url: config.get('DATABASE_URL'),
},
},
});
}
}
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findAll() {
return this.prisma.user.findMany({
include: { posts: true },
});
}
}
8. Error Handling
Custom Exception Filters
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
const res = exception.getResponse();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: typeof res === 'object' ? (res as any).message : res,
});
}
}
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const logger = ();
logger.(, exception ? exception. : (exception));
ctx = host.();
response = ctx.();
response.().({
: ,
: ().(),
: ,
});
}
}
9. Testing
Unit Tests
describe('UsersService', () => {
let service: UsersService;
let repository: MockRepository<User>;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: createMockRepository<User>(),
},
],
}).compile();
service = module.get<UsersService>(UsersService);
repository = module.get(getRepositoryToken(User));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('findAll', () => {
it('should return array of users', async () => {
const users = [new User(), new ()];
jest.(repository, ).(users);
( service.()).(users);
});
});
});
E2E Tests
describe('UsersController (e2e)', () => {
let app: INestApplication;
let jwtToken: string;
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
const response = await request(app.getHttpServer())
.post('/auth/login')
.send({ email: 'admin@test.com', password: 'password123' });
jwtToken = response.body.access_token;
});
it('/users (GET) - should return users', async () => {
const response = await request(app.getHttpServer())
.get('/users')
.set('Authorization', `Bearer ${jwtToken}`);
(response.).();
(.(response.)).();
});
});
10. Decision Checklist
Before implementing:
❌ Anti-Patterns to Avoid
❌ DON'T:
- Put all code in single module
- Use any instead of proper types
- Skip validation pipes
- Use synchronize: true in production
- Not implement error handling
- Forget to set up CORS
✅ DO:
- Use DTOs with class-validator
- Use Pipes for transformation
- Implement proper guards
- Use interceptors for logging
- Use filters for error handling
- Use serialization with class-transformer
📋 Tools by Use Case
| Use Case | Tool | Package |
|---|
| Database ORM | TypeORM | @nestjs/typeorm |
| Database ORM | Prisma | nestjs-prisma |
| Validation | class-validator | @nestjs/class-validator |
| GraphQL | Apollo | @nestjs/graphql + graphql |
| WebSockets | Socket.io | @nestjs/websockets |
| Queue | BullMQ | @nestjs/bull |
| Config | Config | @nestjs/config |
| Docs | Swagger | @nestjs/swagger |
| Auth | Passport | @nestjs/passport |
| Events | Event Emitter | @nestjs/event-emitter |
| Cron | Schedule | @nestjs/schedule |
| Testing | Jest | @nestjs/testing |
Remember: NestJS is about structure and enterprise patterns.
Use modules, services, and controllers properly.
Think in architecture.