home / skills / hoangnguyen0403 / agent-skills-standard / controllers-services

controllers-services skill

/skills/nestjs/controllers-services

This skill enforces NestJS controllers and services separation, decorators, validation, and interceptors to promote scalable, testable backend architecture.

npx playbooks add skill hoangnguyen0403/agent-skills-standard --skill controllers-services

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
2.6 KB
---
name: NestJS Controllers & Services
description: Controller/Service separation and Custom Decorators.
metadata:
  labels: [nestjs, controller, service]
  triggers:
    files: ['**/*.controller.ts', '**/*.service.ts']
    keywords: [Controller, Injectable, ExecutionContext, createParamDecorator]
---

# NestJS Controllers & Services Standards

## **Priority: P0 (FOUNDATIONAL)**

Layer separation standards and dependency injection patterns for NestJS applications.

## Controllers

- **Role**: Handler only. Delegate **all** logic to Services.
- **Context**: Use `ExecutionContext` helpers (`switchToHttp()`) for platform-agnostic code.
- **Custom Decorators**:
  - **Avoid**: `@Request() req` -> `req.user` (Not type-safe).
  - **Pattern**: Create typed decorators like `@CurrentUser()`, `@DeviceIp()`.

  ```typescript
  import { RequestWithUser } from 'src/common/interfaces/request.interface';

  export const CurrentUser = createParamDecorator(
    (data: unknown, ctx: ExecutionContext): User => {
      const request = ctx.switchToHttp().getRequest<RequestWithUser>();
      return request.user;
    },
  );
  ```

## DTOs & Validation

- **Strictness**:
  - `whitelist: true`: Strip properties without decorators.
  - **Critical**: `forbidNonWhitelisted: true`: Throw error if unknown properties exist.
- **Transformation**:
  - `transform: true`: Auto-convert primitives (String '1' -> Number 1) and instantiate DTO classes.
- **Documentation**:
  - **Automation**: Use the `@nestjs/swagger` CLI plugin (`nest-cli.json`) to auto-detect DTO properties without manual `@ApiProperty()` tags.

## Interceptors (Response Mapping)

- **Standardization**: specific responses should be mapped in **Interceptors**, not Controllers.
  - Use `map()` to wrap success responses (e.g. `{ data: T }`).
  - Refer to **[API Standards](../api-standards/SKILL.md)** for `PageDto` and `ApiResponse`.
  - Use `catchError()` to map low-level errors (DB constraints) to `HttpException` (e.g. `ConflictException`) _before_ they hit the global filter.

## Services & Business Logic

- **Singleton**: Default.
- **Stateless**: Do not store request-specific state in class properties unless identifying as `Scope.REQUEST`.

## Pipes & Validation

- **Global**: Register `ValidationPipe` globally.
- **Route Params**: Fail fast. Always use `ParseIntPipe`, `ParseUUIDPipe` on all ID parameters.

```typescript
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) { ... }
```

## Lifecycle Events

- **Init**: Use `OnModuleInit` for connection setup.
- **Destroy**: Use `OnApplicationShutdown` for cleanup. (Requires `enableShutdownHooks()`).

Overview

This skill codifies NestJS controller/service separation, DI patterns, and recommended custom decorator usage to keep controllers thin and services focused. It distills validation, DTO transformation, interceptor-based response mapping, and lifecycle guidance into actionable rules for maintainable TypeScript APIs.

How this skill works

Controllers remain request handlers only and delegate all business logic to services. Typed custom param decorators (e.g., @CurrentUser(), @DeviceIp()) extract request data safely. Global ValidationPipe settings and route-level pipes ensure strict DTO validation and reliable param parsing. Interceptors standardize response shapes and convert low-level errors into appropriate HttpExceptions before global filters run.

When to use it

  • Building REST or platform-agnostic controllers where separation of concerns is required
  • When you need type-safe access to request-scoped values (current user, client IP)
  • Enforcing strict DTO validation and automatic primitive transformation across the app
  • Mapping success/error responses consistently via interceptors
  • Managing module startup/teardown tasks like DB connections or cleanup

Best practices

  • Keep controllers as thin handlers: delegate all logic to services
  • Replace @Request() req usage with typed param decorators for type safety
  • Enable ValidationPipe globally with whitelist: true, forbidNonWhitelisted: true, transform: true
  • Use ParseIntPipe/ParseUUIDPipe on all ID params to fail fast and avoid manual parsing
  • Map success payloads and handle DB constraint errors in interceptors, not controllers
  • Treat services as stateless singletons; use Scope.REQUEST only when truly needed

Example use cases

  • Create a @CurrentUser() decorator that returns a typed User from ExecutionContext for auth-protected routes
  • Apply a global ValidationPipe to automatically strip unknown fields and convert string IDs to numbers
  • Wrap controller responses in an ApiResponse object inside an interceptor to enforce API shape
  • Use ParseIntPipe on route params to ensure endpoints receive valid numeric IDs
  • Implement OnModuleInit for DB connection setup and OnApplicationShutdown for graceful teardown

FAQ

Why avoid accessing req.user directly?

req.user is untyped and couples controllers to raw request shapes; typed decorators provide compile-time safety and clearer intent.

Should I always use forbidNonWhitelisted?

Yes for production APIs: it prevents silent acceptance of unexpected fields and reduces risk from malformed or malicious input.