Back to skills
SKILL.md
Nestjs
ASecurityNestJS framework patterns — modules, providers, guards, interceptors, pipes, and microservices.
- 2 stars
- 0 votes
- 0 copies
- 2 views
- Added October 26, 2026
Works with
Security analysis
100/100npx -y skills add nhan03bit/ultrathink --skill nestjs --agent claude-codeAre you the author of Nestjs?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/nhan03bit-nestjs)---
name: nestjs
description: NestJS framework patterns — modules, providers, guards, interceptors, pipes, and microservices.
layer: domain
category: backend
triggers:
- "nestjs"
- "nest module"
- "nest guard"
- "nest interceptor"
- "nest pipe"
inputs:
- "NestJS application architecture decisions"
- "Module and provider organization"
- "Guard and interceptor implementation"
- "Microservice communication patterns"
outputs:
- "NestJS modules with proper dependency injection"
- "Guards, interceptors, and pipes"
- "Controller and service implementations"
- "Microservice transport configurations"
linksTo:
- typescript-patterns
- graphql
- websockets
- microservices
linkedFrom:
- dependency-injection
preferredNextSkills:
- typescript-patterns
- graphql
- microservices
fallbackSkills:
- fastapi
- hono
riskLevel: low
memoryReadPolicy: selective
memoryWritePolicy: none
sideEffects: []
---
# NestJS Framework Patterns
## Purpose
Provide expert guidance on NestJS application architecture including module organization, dependency injection, request lifecycle (guards, interceptors, pipes, filters), microservice patterns, and production deployment. Covers NestJS 10+ with modern decorators and TypeScript strict mode.
## Module Architecture
**Organize by domain, not technical concern:**
```
src/
app.module.ts
main.ts
common/
decorators/
filters/
guards/
interceptors/
pipes/
config/
config.module.ts
config.service.ts
users/
users.module.ts
users.controller.ts
users.service.ts
users.repository.ts
dto/
create-user.dto.ts
update-user.dto.ts
entities/
user.entity.ts
orders/
orders.module.ts
orders.controller.ts
orders.service.ts
...
```
**Module definition with proper imports/exports:**
```typescript
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // only export what other modules need
})
export class UsersModule {}
```
## Controllers — Keep Thin
Controllers handle HTTP concerns only. Delegate all business logic to services:
```typescript
// src/users/users.controller.ts
import {
Controller, Get, Post, Put, Delete,
Param, Body, Query, HttpCode, HttpStatus,
ParseIntPipe, UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { PaginationDto } from '../common/dto/pagination.dto';
@ApiTags('users')
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create a new user' })
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
findAll(@Query() pagination: PaginationDto) {
return this.usersService.findAll(pagination);
}
@Get(':id')
@UseGuards(JwtAuthGuard)
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOneOrFail(id);
}
@Put(':id')
@UseGuards(JwtAuthGuard)
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id', ParseIntPipe) id: number) {
return this.usersService.remove(id);
}
}
```
## Services — Business Logic Layer
```typescript
// src/users/users.service.ts
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { PaginationDto } from '../common/dto/pagination.dto';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepo: Repository<User>,
) {}
async create(dto: CreateUserDto): Promise<User> {
const exists = await this.userRepo.findOneBy({ email: dto.email });
if (exists) {
throw new ConflictException('Email already registered');
}
const user = this.userRepo.create(dto);
return this.userRepo.save(user);
}
async findAll(pagination: PaginationDto) {
const [items, total] = await this.userRepo.findAndCount({
skip: (pagination.page - 1) * pagination.limit,
take: pagination.limit,
order: { createdAt: 'DESC' },
});
return { items, total, page: pagination.page, limit: pagination.limit };
}
async findOneOrFail(id: number): Promise<User> {
const user = await this.userRepo.findOneBy({ id });
if (!user) throw new NotFoundException(`User #${id} not found`);
return user;
}
async update(id: number, dto: Partial<CreateUserDto>): Promise<User> {
const user = await this.findOneOrFail(id);
Object.assign(user, dto);
return this.userRepo.save(user);
}
async remove(id: number): Promise<void> {
const result = await this.userRepo.delete(id);
if (result.affected === 0) {
throw new NotFoundException(`User #${id} not found`);
}
}
}
```
## DTOs with Validation
```typescript
// src/users/dto/create-user.dto.ts
import { IsEmail, IsString, MinLength, MaxLength } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateUserDto {
@ApiProperty({ example: 'user@example.com' })
@IsEmail()
email: string;
@ApiProperty({ example: 'Jane Doe' })
@IsString()
@MinLength(1)
@MaxLength(100)
name: string;
@ApiProperty({ minLength: 8 })
@IsString()
@MinLength(8)
password: string;
}
// src/users/dto/update-user.dto.ts
import { PartialType } from '@nestjs/swagger';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
```
## Guards
**JWT authentication guard:**
```typescript
// src/common/guards/jwt-auth.guard.ts
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}
handleRequest(err: any, user: any) {
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
```
**Role-based guard:**
```typescript
// src/common/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
```
## Interceptors
**Logging interceptor:**
```typescript
// src/common/interceptors/logging.interceptor.ts
import {
Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(LoggingInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest();
const { method, url } = req;
const start = Date.now();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - start;
this.logger.log(`${method} ${url} — ${duration}ms`);
}),
);
}
}
```
**Transform response interceptor:**
```typescript
// src/common/interceptors/transform.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, map } from 'rxjs';
export interface ApiResponse<T> {
data: T;
meta?: Record<string, any>;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
return next.handle().pipe(map((data) => ({ data })));
}
}
```
## Pipes
**Custom validation pipe with whitelist:**
```typescript
// src/main.ts
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // strip non-decorated properties
forbidNonWhitelisted: true, // throw on unknown properties
transform: true, // auto-transform query params to types
transformOptions: {
enableImplicitConversion: true,
},
}),
);
```
## Exception Filters
```typescript
// src/common/filters/all-exceptions.filter.ts
import {
ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
private readonly logger = new Logger(AllExceptionsFilter.name);
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException
? exception.getResponse()
: 'Internal server error';
this.logger.error(`${request.method} ${request.url}`, exception);
response.status(status).json({
statusCode: status,
message: typeof message === 'string' ? message : (message as any).message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
```
## Custom Decorators
```typescript
// src/common/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
// src/common/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
// src/common/decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return data ? request.user?.[data] : request.user;
},
);
```
## Configuration with ConfigModule
```typescript
// src/config/config.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule as NestConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
@Module({
imports: [
NestConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required(),
JWT_SECRET: Joi.string().required(),
JWT_EXPIRATION: Joi.string().default('1h'),
}),
}),
],
})
export class AppConfigModule {}
```
## Best Practices
1. **One module per domain** — Keep modules focused on a single bounded context.
2. **Export only what is needed** — Minimize the module's public API surface.
3. **Use constructor injection** — Avoid property injection; it is harder to test.
4. **Global guards/interceptors via APP_GUARD** — Register in `app.module.ts` providers.
5. **Use DTOs for all input** — Never trust raw `@Body()` without validation.
6. **Custom exceptions extend HttpException** — Maintain consistent error contracts.
7. **Use `@nestjs/config` for env vars** — Validate at startup with Joi.
8. **Scope providers carefully** — Default singleton scope; use REQUEST scope only when necessary.
9. **Use `@nestjs/swagger`** — Auto-generate API docs from DTOs and decorators.
10. **Test with `@nestjs/testing`** — Use `Test.createTestingModule()` for isolated unit tests.
## Common Pitfalls
| Pitfall | Problem | Fix |
|---------|---------|-----|
| Circular dependency | Two modules importing each other | Use `forwardRef()` or extract shared logic to a third module |
| Missing `@Injectable()` | Provider not recognized by DI | Always decorate services with `@Injectable()` |
| REQUEST scope leak | Entire dependency chain becomes request-scoped | Minimize REQUEST-scoped providers; use `@Inject(REQUEST)` sparingly |
| No `whitelist: true` | Extra properties pass validation | Set `whitelist` and `forbidNonWhitelisted` in global `ValidationPipe` |
| Heavy controllers | Controllers contain business logic | Move logic to services; controllers handle HTTP only |
| Forgetting `async` | Promise returned but not awaited | All DB operations should be `async/await` |
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.