Software_development_department nestjs-expert
Provides NestJS patterns for modules, controllers, providers, guards, interceptors, and microservices. Use when working with NestJS TypeScript files (*.module.ts, *.controller.ts, *.service.ts) or when the user mentions NestJS, Nest.js, or NestJS modules.
install
source · Clone the upstream repo
git clone https://github.com/tranhieutt/software_development_department
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/tranhieutt/software_development_department "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/nestjs-expert" ~/.claude/skills/tranhieutt-software-development-department-nestjs-expert && rm -rf "$T"
manifest:
.claude/skills/nestjs-expert/SKILL.mdsource content
NestJS Expert
Critical rules (non-obvious)
- Circular dependencies: use
in both modules; better — restructure to avoidforwardRef(() => ServiceB) - Global modules: use
sparingly; prefer explicit imports to keep modules testable@Global()
/APP_GUARD
: registered in AppModule providers, not in individual modulesAPP_INTERCEPTOR- Lifecycle hooks order:
→onModuleInit
→ ready;onApplicationBootstrap
→onModuleDestroy
→beforeApplicationShutdownonApplicationShutdown - Never use
without type assertion — it'sreq.user
from Passport; extendanyExpress.Request
Module structure
@Module({ imports: [TypeOrmModule.forFeature([User]), JwtModule], controllers: [UserController], providers: [UserService, UserRepository], exports: [UserService], // export what other modules need }) export class UserModule {}
Controller with validation
@Controller("users") @UseGuards(JwtAuthGuard) export class UserController { constructor(private readonly userService: UserService) {} @Get(":id") @HttpCode(HttpStatus.OK) async findOne(@Param("id", ParseUUIDPipe) id: string, @CurrentUser() user: User) { return this.userService.findOneOrFail(id); } @Post() @Roles(Role.ADMIN) async create(@Body() dto: CreateUserDto) { return this.userService.create(dto); } }
Service with repository pattern
@Injectable() export class UserService { constructor( @InjectRepository(User) private readonly repo: Repository<User>, private readonly eventEmitter: EventEmitter2, ) {} async findOneOrFail(id: string): Promise<User> { const user = await this.repo.findOne({ where: { id } }); if (!user) throw new NotFoundException(`User ${id} not found`); return user; } async create(dto: CreateUserDto): Promise<User> { const user = this.repo.create(dto); const saved = await this.repo.save(user); this.eventEmitter.emit("user.created", new UserCreatedEvent(saved)); return saved; } }
JWT auth guard pattern
@Injectable() export class JwtAuthGuard extends AuthGuard("jwt") { canActivate(context: ExecutionContext) { return super.canActivate(context); } handleRequest(err: any, user: any) { if (err || !user) throw err ?? new UnauthorizedException(); return user; } } // Custom decorator for current user export const CurrentUser = createParamDecorator( (_, ctx: ExecutionContext) => ctx.switchToHttp().getRequest().user, );
Global exception filter
@Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; ctx.getResponse().status(status).json({ statusCode: status, message: exception instanceof HttpException ? exception.message : "Internal server error", timestamp: new Date().toISOString(), }); } } // Register: app.useGlobalFilters(new AllExceptionsFilter())
Interceptor: response transform + timing
@Injectable() export class TransformInterceptor implements NestInterceptor { intercept(ctx: ExecutionContext, next: CallHandler): Observable<any> { const start = Date.now(); return next.handle().pipe( map(data => ({ data, duration: Date.now() - start, timestamp: new Date() })), ); } }
Validation DTO
export class CreateUserDto { @IsEmail() email: string; @IsString() @MinLength(8) @Matches(/^(?=.*[A-Z])(?=.*\d)/, { message: "Must contain uppercase and digit" }) password: string; @IsEnum(Role) @IsOptional() role?: Role = Role.USER; } // Global: app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }))
Config with validation
// app.module.ts ConfigModule.forRoot({ isGlobal: true, validationSchema: Joi.object({ NODE_ENV: Joi.string().valid("development", "production", "test").required(), PORT: Joi.number().default(3000), DATABASE_URL: Joi.string().required(), JWT_SECRET: Joi.string().min(32).required(), }), })
Common pitfalls
| Pitfall | Fix |
|---|---|
| Injecting service into wrong module | Export service from its module; import module |
Missing on lifecycle hooks | if doing DB work on startup |
without | Strips extra fields; prevents mass assignment |
| Blocking the event loop in provider | Use ; never synchronous I/O |
Missing | Required for to fire in Docker |