agentsclimarketplace

Nestjs expert

Skill tranhieutt/software_development_department/.claude/skills/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.From its SKILL.md

Install
npx -y skills add tranhieutt/software_development_department --skill nestjs-expert

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

5.2 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it

NestJS Expert

Critical rules (non-obvious)

  • Circular dependencies: use forwardRef(() => ServiceB) in both modules; better — restructure to avoid
  • Global modules: use @Global() sparingly; prefer explicit imports to keep modules testable
  • APP_GUARD / APP_INTERCEPTOR: registered in AppModule providers, not in individual modules
  • Lifecycle hooks order: onModuleInit → onApplicationBootstrap → ready; onModuleDestroy → beforeApplicationShutdown → onApplicationShutdown
  • Never use req.user without type assertion — it's any from Passport; extend Express.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

PitfallFix
Injecting service into wrong moduleExport service from its module; import module
Missing async on lifecycle hooksasync onModuleInit() if doing DB work on startup
ValidationPipe without whitelist: trueStrips extra fields; prevents mass assignment
Blocking the event loop in providerUse async/await; never synchronous I/O
Missing enableShutdownHooks()Required for onModuleDestroy to fire in Docker

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.