nestjs-best-practices
Provides comprehensive NestJS best practices including modular architecture, dependency injection scoping, exception filters, DTO validation with class-validator, and Drizzle ORM integration. Use when designing NestJS modules, implementing providers, creating exception filters, validating DTOs, or integrating Drizzle ORM within NestJS applications.
Works with
---
name: nestjs-best-practices
description: Provides comprehensive NestJS best practices including modular architecture, dependency injection scoping, exception filters, DTO validation with class-validator, and Drizzle ORM integration. Use when designing NestJS modules, implementing providers, creating exception filters, validating DTOs, or integrating Drizzle ORM within NestJS applications.
license: MIT
---
# NestJS Best Practices
## Overview
Grounded in the [Official NestJS Documentation](https://docs.nestjs.com/), this skill enforces modular architecture, dependency injection scoping, exception filters, DTO validation with `class-validator`, and Drizzle ORM integration patterns.
## When to Use
- Designing/refactoring NestJS modules or dependency injection
- Creating exception filters, validating DTOs, or integrating Drizzle ORM
- Reviewing code for anti-patterns or onboarding to a NestJS codebase
## Instructions
### 1. Modular Architecture
Follow strict module encapsulation. Each domain feature should be its own `@Module()`:
- Export only what other modules need — keep internal providers private
- Use `forwardRef()` only as a last resort for circular dependencies; prefer restructuring
- Group related controllers, services, and repositories within the same module
- Use a `SharedModule` for cross-cutting concerns (logging, configuration, caching)
See `references/arch-module-boundaries.md` for enforcement rules.
### 2. Dependency Injection
Choose the correct provider scope based on use case:
| Scope | Lifecycle | Use Case |
|-------------|------------------------------|---------------------------------------------|
| `DEFAULT` | Singleton (shared) | Stateless services, repositories |
| `REQUEST` | Per-request instance | Request-scoped data (tenant, user context) |
| `TRANSIENT` | New instance per injection | Stateful utilities, per-consumer caches |
- Default to `DEFAULT` scope — only use `REQUEST` or `TRANSIENT` when justified
- Use constructor injection exclusively — avoid property injection
- Register custom providers with `useClass`, `useValue`, `useFactory`, or `useExisting`
See `references/di-provider-scoping.md` for enforcement rules.
### 3. Request Lifecycle
Understand and respect the NestJS request processing pipeline:
```
Middleware → Guards → Interceptors (before) → Pipes → Route Handler → Interceptors (after) → Exception Filters
```
- **Middleware**: Cross-cutting concerns (logging, CORS, body parsing)
- **Guards**: Authorization and authentication checks (return `true`/`false`)
- **Interceptors**: Transform response data, add caching, measure timing
- **Pipes**: Validate and transform input parameters
- **Exception Filters**: Catch and format error responses
### 4. Error Handling
Standardize error responses across the application:
- Extend `HttpException` for HTTP-specific errors
- Create domain-specific exception classes (e.g., `OrderNotFoundException`)
- Implement a global `ExceptionFilter` for consistent error formatting
- Use the Result pattern for expected business logic failures
- Never silently swallow exceptions
See `references/error-exception-filters.md` for enforcement rules.
### 5. Validation
Enforce input validation at the API boundary:
- Enable `ValidationPipe` globally with `transform: true` and `whitelist: true`
- Decorate all DTO properties with `class-validator` decorators
- Use `class-transformer` for type coercion (`@Type()`, `@Transform()`)
- Create separate DTOs for Create, Update, and Response operations
- Never trust raw user input — validate everything
See `references/api-validation-dto.md` for enforcement rules.
### 6. Database Patterns (Drizzle ORM)
Integrate Drizzle ORM following NestJS provider conventions:
- Wrap the Drizzle client in an injectable provider
- Use the Repository pattern for data access encapsulation
- Define schemas in dedicated schema files per domain module
- Use transactions for multi-step operations
- Keep database logic out of controllers
See `references/db-drizzle-patterns.md` for enforcement rules.
## Best Practices
| Area | Do | Don't |
|--------------------|------------------------------------------|------------------------------------------|
| Modules | One module per domain feature | Dump everything in `AppModule` |
| DI Scoping | Default to singleton scope | Use `REQUEST` scope without justification|
| Error Handling | Custom exception filters + domain errors | Bare `try/catch` with `console.log` |
| Validation | Global `ValidationPipe` + DTO decorators | Manual `if` checks in controllers |
| Database | Repository pattern with injected client | Direct DB queries in controllers |
| Testing | Unit test services, e2e test controllers | Skip tests or test implementation details|
| Configuration | `@nestjs/config` with typed schemas | Hardcode values or use `process.env` |
## Examples
### Example: New Domain Module with Validation
When building a "Product" feature, follow this workflow:
**1. Create the module with proper encapsulation:**
```typescript
// product/product.module.ts
@Module({
imports: [DatabaseModule],
controllers: [ProductController],
providers: [ProductService, ProductRepository],
exports: [ProductService], // Only export what others need
})
export class ProductModule {}
```
**2. Create validated DTOs:**
```typescript
// product/dto/create-product.dto.ts
import { IsString, IsNumber, IsPositive, MaxLength } from 'class-validator';
export class CreateProductDto {
@IsString() @MaxLength(255) readonly name: string;
@IsNumber() @IsPositive() readonly price: number;
}
```
**3. Service with error handling:**
```typescript
@Injectable()
export class ProductService {
constructor(private readonly productRepository: ProductRepository) {}
async findById(id: string): Promise<Product> {
const product = await this.productRepository.findById(id);
if (!product) throw new ProductNotFoundException(id);
return product;
}
}
```
**4. Verify module registration:**
```bash
# Check module is imported in AppModule
grep -r "ProductModule" src/app.module.ts
# Run e2e to confirm exports work
npx jest --testPathPattern="product"
```
## Constraints and Warnings
1. **Do not mix scopes without justification** — `REQUEST`-scoped providers cascade to all dependents
2. **Never access database directly from controllers** — always go through service and repository layers
3. **Avoid `forwardRef()`** — restructure modules to eliminate circular dependencies
4. **Do not skip `ValidationPipe`** — always validate at the API boundary with DTOs
5. **Never hardcode secrets** — use `@nestjs/config` with environment variables
6. **Keep modules focused** — one domain feature per module, avoid "god modules"
## References
- `references/architecture.md` — Deep-dive into NestJS architectural patterns
- `references/` — Individual enforcement rules with correct/incorrect examples
- `assets/templates/` — Starter templates for common NestJS componentsMore General & Other skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
grill-with-docs
mattpocock/skills
A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.

