Node.js/TypeScript backend framework with dependency injection and modular architecture
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
REFERENCE.mdVALIDATION.mdexamples/README.mdexamples/jwt-authentication.example.tsexamples/user-management-crud.example.tstemplates/TEST-RESULTS.mdtemplates/controller.template.tstemplates/dto.template.tstemplates/entity.template.tstemplates/module.template.tstemplates/repository.template.tstemplates/service.spec.template.tstemplates/service.template.tstemplates/test-templates.jsname: NestJS Framework version: 1.0.0 framework_versions: min: 8.0.0 max: 11.x recommended: 10.4.0 compatible_agents: backend-developer: ">=3.0.0" tech-lead-orchestrator: ">=2.5.0" description: Node.js/TypeScript backend framework with dependency injection and modular architecture frameworks:
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%
// 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:
// 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:
// users/dto/create-user.dto.ts
export class CreateUserDto {
@ApiProperty({ example: 'john@example.com' })
@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:
// 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:
// 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:
// 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:
// 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:
// 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: 'test@test.com', 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: 'test@test.com', password: 'Pass123!' };
repository.findByEmail.mockResolvedValue({ id: 1 });
await expect(service.createUser(dto)).rejects.toThrow(ConflictException);
});
});
});
Test Strategy:
// Bad: Direct dependency on implementation
@Injectable()
export class UserService {
async createUser(email: string, password: string) {
const hashed = await bcrypt.hash(password, 10); // Tight coupling!
// ...
}
}
// 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);
// ...
}
}
// Bad: No validation, any type
@Post()
async create(@Body() body: any) {
return this.service.create(body);
}
// Good: Strong typing + validation
@Post()
async create(@Body(ValidationPipe) dto: CreateUserDto): Promise<UserResponseDto> {
return this.service.create(dto);
}
// Bad: Returns password field!
@Get(':id')
async findOne(@Param('id') id: string) {
return this.userService.findOne(id); // Contains password
}
// 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
}
@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;
}
}
// 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 });
// 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'],
});
# 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