
Nestjs Code Review
Review NestJS controllers and services against documented anti-patterns before merge or release.
Install
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nestjs-code-reviewWhat is this skill?
- Documents fat-controller and direct-repository-access anti-patterns with before/after TypeScript fixes
- Pushes business logic into services and thin controllers for testability
- Calls out god-service consolidation risks across users, orders, and unrelated domains
- Backend-focused guidance aligned with NestJS dependency injection and module boundaries
Adoption & trust: 991 installs on skills.sh; 271 GitHub stars; 3/3 security scanners passed (skills.sh audits).
Recommended Skills
Improve Codebase Architecturemattpocock/skills
Zoom Outmattpocock/skills
Caveman Reviewjuliusbrussee/caveman
Requesting Code Reviewobra/superpowers
Receiving Code Reviewobra/superpowers
Request Refactor Planmattpocock/skills
Journey fit
Primary fit
Canonical shelf is Ship because the skill is framed as review and quality gates on existing NestJS code, not greenfield scaffolding. Review subphase matches structured anti-pattern checks on controllers, services, and layering violations.
Common Questions / FAQ
Is Nestjs Code Review safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Nestjs Code Review
# NestJS Anti-Patterns ## Controller Anti-Patterns ### Fat Controllers Business logic in controllers makes code untestable and violates single responsibility. ```typescript // ❌ Anti-pattern: Business logic in controller @Post('register') async register(@Body() dto: RegisterDto) { const existing = await this.userRepo.findOne({ email: dto.email }); if (existing) throw new ConflictException('Email exists'); const salt = await bcrypt.genSalt(10); const hash = await bcrypt.hash(dto.password, salt); const user = this.userRepo.create({ ...dto, password: hash }); await this.userRepo.save(user); const token = this.jwtService.sign({ sub: user.id }); await this.emailService.sendWelcome(user.email); return { user, token }; } ``` **Fix**: Move all logic to a service, keep the controller thin. ### Direct Repository Access in Controllers Controllers should never access repositories directly — always go through a service layer. ```typescript // ❌ Anti-pattern @Controller('products') export class ProductController { constructor( @InjectRepository(Product) private readonly productRepo: Repository<Product>, ) {} } // ✅ Fix: Inject the service instead @Controller('products') export class ProductController { constructor(private readonly productService: ProductService) {} } ``` ## Service Anti-Patterns ### God Service A service that handles too many concerns becomes a maintenance nightmare. ```typescript // ❌ Anti-pattern: Service handling users, orders, payments, emails @Injectable() export class AppService { async createUser() { /* ... */ } async processOrder() { /* ... */ } async chargePayment() { /* ... */ } async sendEmail() { /* ... */ } async generateReport() { /* ... */ } } ``` **Fix**: Split into focused services — `UserService`, `OrderService`, `PaymentService`, etc. ### Tight Coupling to Infrastructure Services that directly depend on infrastructure (HTTP clients, file system, specific databases) are hard to test and replace. ```typescript // ❌ Anti-pattern: Direct infrastructure dependency @Injectable() export class NotificationService { async send(userId: string, message: string) { const response = await fetch('https://api.sendgrid.com/v3/mail/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.SENDGRID_KEY}` }, body: JSON.stringify({ to: userId, content: message }), }); return response.json(); } } // ✅ Fix: Use an interface/abstraction interface EmailProvider { send(to: string, content: string): Promise<void>; } @Injectable() export class NotificationService { constructor( @Inject('EMAIL_PROVIDER') private readonly emailProvider: EmailProvider, ) {} async send(userId: string, message: string): Promise<void> { const user = await this.userService.findOne(userId); await this.emailProvider.send(user.email, message); } } ``` ## Dependency Injection Anti-Patterns ### Manual Instantiation Bypassing the DI container loses lifecycle management and testability. ```typescript // ❌ Anti-pattern @Injectable() export class OrderService { private logger = new Logger(); // Not managed by DI private cache = new CacheService(); // Not injectable, not mockable async process() { this.logger.log('Processing'); } } ``` ### Circular Dependencies Two services depending on each other create circular dependency issues. ```typescript // ❌ Anti-pattern: Circular dependency @Injectable() export class UserService { constructor(private orderService: OrderService) {} } @Injectable() export class OrderService { constructor(private userService: UserService) {} } // ✅ Fix: Use forwardRef or restructure with events @Injectable() export class OrderService { constructor( @Inject(forwardRef(() => UserService)) private userService: UserService, ) {} } // ✅ Better fix: Break the cycle with an event @Injectable() export class OrderService { constructor(private eventEmitter: EventEmitter2) {} a