NestJS Framework
About
This skill enables building scalable Node.js/TypeScript backend applications using NestJS. It provides dependency injection, modular architecture, and enterprise patterns for structured development. Use it when creating maintainable server-side applications with comprehensive testing coverage.
Documentation
NestJS Framework Skill
Quick Reference
When to Use: Building scalable Node.js/TypeScript backend applications with modular architecture
Core Strengths: Dependency injection, modular design, enterprise patterns, comprehensive testing
Target Coverage: Services ≥80%, Controllers ≥70%, E2E ≥60%, Overall ≥75%
Essential Patterns
Module Architecture
// users/users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User]), AuthModule],
controllers: [UserController],
providers: [
UserService,
UserRepository,
{ provide: 'USER_REPOSITORY', useClass: UserRepository },
],
exports: [UserService],
})
export class UsersModule {}
Key Principles:
- Clear module boundaries and responsibilities
- Export only what other modules need
- Import shared modules (AuthModule, DatabaseModule)
- Use token-based providers for abstraction
Dependency Injection
// users/services/user.service.ts
@Injectable()
export class UserService {
constructor(
@Inject('USER_REPOSITORY') private readonly userRepository: UserRepository,
private readonly hashingService: HashingService,
private readonly eventEmitter: EventEmitter2,
) {}
async createUser(dto: CreateUserDto): Promise<User> {
const hashedPassword = await this.hashingService.hash(dto.password);
const user = await this.userRepository.create({
...dto,
password: hashedPassword,
});
this.eventEmitter.emit('user.created', user);
return user;
}
}
Best Practices:
- Use constructor injection for all dependencies
- Inject interfaces/tokens, not concrete implementations
- Keep services focused on single responsibility
- Emit events for cross-cutting concerns
DTO Validation
// users/dto/create-user.dto.ts
export class CreateUserDto {
@ApiProperty({ example: '[email protected]' })
@IsEmail({}, { message: 'Invalid email format' })
email: string;
@ApiProperty({ example: 'StrongP@ss123', minLength: 8 })
@IsString()
@MinLength(8)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/, {
message: 'Password must contain uppercase, lowercase, number, symbol'
})
password: string;
@ApiProperty({ example: 'John Doe', required: false })
@IsOptional()
@MaxLength(100)
name?: string;
}
Validation Rules:
- All inputs validated with class-validator decorators
- API documentation via @ApiProperty
- Custom error messages for user clarity
- Optional fields with @IsOptional()
Repository Pattern
// users/repositories/user.repository.ts
@Injectable()
export class UserRepository {
constructor(
@InjectRepository(User) private readonly repository: Repository<User>
) {}
async findByEmail(email: string): Promise<User | null> {
return this.repository.findOne({ where: { email } });
}
async findById(id: number): Promise<User> {
const user = await this.repository.findOne({ where: { id } });
if (!user) throw new NotFoundException('User not found');
return user;
}
async create(data: Partial<User>): Promise<User> {
const user = this.repository.create(data);
return this.repository.save(user);
}
}
Repository Guidelines:
- Encapsulate all database operations
- Throw domain-specific exceptions
- Use TypeORM query builder for complex queries
- Keep repositories focused on data access only
Controller Best Practices
// users/controllers/user.controller.ts
@ApiTags('users')
@Controller('users')
@UseInterceptors(ClassSerializerInterceptor)
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create new user' })
@ApiResponse({ status: 201, type: UserResponseDto })
@ApiResponse({ status: 400, description: 'Validation failed' })
async create(
@Body(ValidationPipe) dto: CreateUserDto
): Promise<UserResponseDto> {
const user = await this.userService.create(dto);
return plainToInstance(UserResponseDto, user);
}
@Get(':id')
@UseGuards(JwtAuthGuard)
@ApiParam({ name: 'id', type: 'number' })
async findOne(
@Param('id', ParseIntPipe) id: number
): Promise<UserResponseDto> {
const user = await this.userService.findById(id);
return plainToInstance(UserResponseDto, user);
}
}
Controller Checklist:
- @ApiTags for logical grouping
- @ApiOperation for endpoint description
- @ApiResponse for status codes + types
- ValidationPipe for DTO validation
- Guards for authentication/authorization
- Transform responses with DTOs
Authentication & Authorization
// auth/guards/jwt-auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
handleRequest(err, user, info) {
if (err || !user) {
throw new UnauthorizedException('Invalid or expired token');
}
return user;
}
}
// auth/guards/roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler());
if (!requiredRoles) return true;
const request = context.switchToHttp().getRequest();
const user = request.user;
return requiredRoles.some(role => user.roles?.includes(role));
}
}
// Usage in controller
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN)
@Delete(':id')
async delete(@Param('id') id: number): Promise<void> {
await this.userService.delete(id);
}
Auth Patterns:
- JWT strategy with Passport.js
- Role-based access control with custom decorators
- Guard composition for complex rules
- Secure password hashing (bcrypt, argon2)
Exception Handling
// common/filters/http-exception.filter.ts
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const exceptionResponse = exception.getResponse();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: ctx.getRequest().url,
message: typeof exceptionResponse === 'string'
? exceptionResponse
: (exceptionResponse as any).message,
});
}
}
// Usage in main.ts
app.useGlobalFilters(new HttpExceptionFilter());
Error Strategy:
- Global exception filter for consistency
- Domain-specific exceptions (UserNotFoundException)
- Include correlation IDs for debugging
- Log errors with appropriate severity
Testing
// users/services/user.service.spec.ts
describe('UserService', () => {
let service: UserService;
let repository: MockType<UserRepository>;
let hashingService: MockType<HashingService>;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UserService,
{
provide: 'USER_REPOSITORY',
useFactory: mockRepository,
},
{
provide: HashingService,
useFactory: mockHashingService,
},
],
}).compile();
service = module.get<UserService>(UserService);
repository = module.get('USER_REPOSITORY');
hashingService = module.get(HashingService);
});
describe('createUser', () => {
it('should hash password and create user', async () => {
const dto = { email: '[email protected]', password: 'Pass123!' };
const hashedPassword = 'hashed_password';
hashingService.hash.mockResolvedValue(hashedPassword);
repository.findByEmail.mockResolvedValue(null);
repository.create.mockResolvedValue({ id: 1, ...dto, password: hashedPassword });
const result = await service.createUser(dto);
expect(hashingService.hash).toHaveBeenCalledWith(dto.password);
expect(repository.create).toHaveBeenCalledWith({
...dto,
password: hashedPassword,
});
expect(result.id).toBe(1);
});
it('should throw ConflictException if user exists', async () => {
const dto = { email: '[email protected]', password: 'Pass123!' };
repository.findByEmail.mockResolvedValue({ id: 1 });
await expect(service.createUser(dto)).rejects.toThrow(ConflictException);
});
});
});
Test Strategy:
- Unit tests for services (≥80% coverage)
- Integration tests for repositories
- E2E tests for critical workflows (≥60% coverage)
- Mock external dependencies
- Test error paths and edge cases
Common Anti-Patterns
❌ Don't: Tight Coupling
// Bad: Direct dependency on implementation
@Injectable()
export class UserService {
async createUser(email: string, password: string) {
const hashed = await bcrypt.hash(password, 10); // Tight coupling!
// ...
}
}
✅ Do: Abstraction
// Good: Depend on abstraction
@Injectable()
export class UserService {
constructor(private readonly hashingService: HashingService) {}
async createUser(dto: CreateUserDto) {
const hashed = await this.hashingService.hash(dto.password);
// ...
}
}
❌ Don't: No Input Validation
// Bad: No validation, any type
@Post()
async create(@Body() body: any) {
return this.service.create(body);
}
✅ Do: DTO Validation
// Good: Strong typing + validation
@Post()
async create(@Body(ValidationPipe) dto: CreateUserDto): Promise<UserResponseDto> {
return this.service.create(dto);
}
❌ Don't: Expose Sensitive Data
// Bad: Returns password field!
@Get(':id')
async findOne(@Param('id') id: string) {
return this.userService.findOne(id); // Contains password
}
✅ Do: Transform Responses
// Good: Use response DTO with @Exclude()
@Get(':id')
@UseInterceptors(ClassSerializerInterceptor)
async findOne(@Param('id', ParseIntPipe) id: number): Promise<UserResponseDto> {
const user = await this.userService.findById(id);
return plainToInstance(UserResponseDto, user); // Excludes password
}
Performance Patterns
Caching
@Injectable()
export class UserService {
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache,
private userRepository: UserRepository,
) {}
async findById(id: number): Promise<User> {
const cacheKey = `user:${id}`;
const cached = await this.cacheManager.get<User>(cacheKey);
if (cached) return cached;
const user = await this.userRepository.findById(id);
await this.cacheManager.set(cacheKey, user, 3600); // 1 hour TTL
return user;
}
}
Background Jobs
// users/processors/email.processor.ts
@Processor('email')
export class EmailProcessor {
@Process('welcome')
async sendWelcomeEmail(job: Job<{ email: string; name: string }>) {
const { email, name } = job.data;
await this.emailService.sendWelcome(email, name);
}
}
// In service
await this.emailQueue.add('welcome', { email: user.email, name: user.name });
Query Optimization
// Bad: N+1 query problem
const users = await this.repository.find();
for (const user of users) {
user.orders = await this.orderRepository.findByUserId(user.id);
}
// Good: Use relations
const users = await this.repository.find({
relations: ['orders'],
});
Integration Checklist
- Module properly structured with clear boundaries
- All dependencies injected via constructor
- DTOs with class-validator decorators
- Repository pattern for data access
- Guards for authentication/authorization
- Exception filters for consistent errors
- OpenAPI/Swagger documentation
- Unit tests ≥80% coverage
- Integration tests for API endpoints
- E2E tests for critical paths
- Caching for frequently accessed data
- Background jobs for async operations
- Health check endpoint
Quick Commands
# Generate resources
nest g module users
nest g controller users
nest g service users
# Generate complete CRUD
nest g resource users
# Run tests
npm run test # Unit tests
npm run test:e2e # E2E tests
npm run test:cov # Coverage report
# Build and run
npm run build
npm run start:dev # Watch mode
npm run start:prod # Production
See Also
- REFERENCE.md - Comprehensive guide with microservices, GraphQL, advanced patterns
- templates/ - Code generation templates
- examples/ - Real-world implementation examples
Quick Install
/plugin add https://github.com/FortiumPartners/ai-mesh/tree/main/nestjs-frameworkCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
subagent-driven-development
DevelopmentThis skill executes implementation plans by dispatching a fresh subagent for each independent task, with code review between tasks. It enables fast iteration while maintaining quality gates through this review process. Use it when working on mostly independent tasks within the same session to ensure continuous progress with built-in quality checks.
algorithmic-art
MetaThis Claude Skill creates original algorithmic art using p5.js with seeded randomness and interactive parameters. It generates .md files for algorithmic philosophies, plus .html and .js files for interactive generative art implementations. Use it when developers need to create flow fields, particle systems, or other computational art while avoiding copyright issues.
executing-plans
DesignUse the executing-plans skill when you have a complete implementation plan to execute in controlled batches with review checkpoints. It loads and critically reviews the plan, then executes tasks in small batches (default 3 tasks) while reporting progress between each batch for architect review. This ensures systematic implementation with built-in quality control checkpoints.
cost-optimization
OtherThis Claude Skill helps developers optimize cloud costs through resource rightsizing, tagging strategies, and spending analysis. It provides a framework for reducing cloud expenses and implementing cost governance across AWS, Azure, and GCP. Use it when you need to analyze infrastructure costs, right-size resources, or meet budget constraints.
