- 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
```typescript
// users/users.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([UserEntity]), // Entities managed by this module
PassportModule.register({ defaultStrategy: 'jwt' }),
forwardRef(() => AuthModule), // If there's circular dependency
],
controllers: [UsersController],
providers: [
UsersService,
{
provide: 'USER_REPOSITORY',
useFactory: (dataSource: DataSource) => dataSource.getRepository(UserEntity),
inject: ['DATA_SOURCE'],
},
],
exports: [UsersService], // Make available to other modules
})
export class UsersModule {}
```
### Shared Modules
```typescript
// common/common.module.ts
@Global()
@Module({
providers: [
{
provide: 'LOGGER',
useClass: Logger,
},
],
exports: ['LOGGER'],
})
export class CommonModule {}
// Now use in any module by importing CommonModule
```
---
## 3. Dependency Injection Patterns
### Constructor Injection
```typescript
// Standard injection
@Injectable()
export class UsersService {
constructor(
private readonly usersRepository: UsersRepository,
private readonly emailService: EmailService,
@Inject('CONFIG') private config: ConfigService,
) {}
}
```
### Multiple Implementations
```typescript
// Use interface
export interface PaymentGateway {
process(amount: number): Promise<PaymentResult>;
}
// Stripe implementation
@Injectable()
export class StripePaymentGateway implements PaymentGateway {
async process(amount: number): Promise<PaymentResult> {
// Stripe logic
}
}
// PayPal implementation
@Injectable()
export class PayPalPaymentGateway implements PaymentGateway {
async process(amount: number): Promise<PaymentResult> {
// PayPal logic
}
}
// Use with @Inject
@Injectable()
export class OrderService {
constructor(
@Inject('PaymentGateway') private paymentGateway: PaymentGateway,
) {}
}
// Register in module
{
provide: 'PaymentGateway',
useClass: config.paymentProvider === 'stripe'
? StripePaymentGateway
: PayPalPaymentGateway,
}
```
### Custom Providers
```typescript
// Value provider
{
provide: 'APP_NAME',
useValue: 'MyApp',
}
// Class provider (singleton)
{
provide: UsersService,
useClass: UsersServiceImpl,
}
// Factory provider (complex instantiation)
{
provide: 'ANALYTICS',
useFactory: (config: ConfigService) => {
return new Analytics(config.analyticsKey);
},
inject: [ConfigService],
}
```
---
## 4. Controllers and Routing
### REST Controller
```typescript
@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(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Patch(':id')
@HttpCode(200)
async update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
@HttpCode(204)
async remove(@Param('id', ParseIntPipe) id: number) {
return this.usersService.remove(id);
}
}
```
### GraphQL Controller
```typescript
@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.id);
}
}
```
### WebSocket Gateway
```typescript
@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
```typescript
// DTOs with class-validator
export class CreateUserDto {
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@MinLength(8)
@MaxLength(100)
password: string;
@IsOptional()
@IsString()
name?: string;
@IsEnum(['user', 'admin', 'moderator'])
role: UserRole;
}
// Global validation in main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip properties not in DTO
forbidNonWhitelisted: true, // Throw error if extra properties
transform: true, // Transform payloads to DTO instances
transformOptions: { enableImplicitConversion: true },
}),
);
await app.listen(3000);
}
```
### Custom Pipe
```typescript
@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;
}
}
// Usage in controller
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
```
---
## 6. Authentication
### JWT Auth with Guards
```typescript
// jwt.strategy.ts
@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 };
}
}
// jwt-auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
}
// Usage in controller
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
```
### Role-based Access Control
```typescript
// Roles guard
@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));
}
}
// Usage
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Delete(':id')
async remove(@Param('id') id: string) {
// Only admins can delete
}
```
---
## 7. Database Integration
### TypeORM Integration
```typescript
// app.module.ts
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'mydb',
entities: [User, Post],
synchronize: false, // Don't use in production!
logging: process.env.NODE_ENV === 'development',
}),
],
})
export class AppModule {}
// Entity
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
@Transform(({ value }) => bcrypt.hash(value), { to: 'save' })
password: string;
@Column({ default: 'user' })
role: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// Repository pattern
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async findAll(): Promise<User[]> {
return this.usersRepository.find();
}
async findOne(id: number): Promise<User> {
const user = await this.usersRepository.findOne({
where: { id },
relations: ['posts'],
});
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
}
}
```
### Prisma Integration
```typescript
عرض على GitHub