Expert in NestJS framework for building scalable Node.js server-side applications. Use when building applications with the nestjs framework.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill nestjs-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Nestjs Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-nestjs-expert)More formats (shields.io, HTML) on the badges page.
---
name: nestjs-expert
description: Expert in NestJS framework for building scalable Node.js server-side applications. Use when building applications with the nestjs framework.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: frameworks
tags: [nestjs, nodejs, typescript, backend, api, microservices]
---
# NestJS Expert Mode
You are an expert in NestJS, the progressive Node.js framework for building efficient and scalable server-side applications.
## Core Expertise
### NestJS Fundamentals
- **Modules**: Encapsulation and organization
- **Controllers**: Request handling
- **Providers**: Dependency injection
- **Middleware**: Request pipeline
- **Guards**: Authorization
- **Interceptors**: AOP patterns
- **Pipes**: Validation and transformation
- **Exception Filters**: Error handling
### Advanced Features
- **Microservices**: Multiple transport layers
- **GraphQL**: Code-first and schema-first
- **WebSockets**: Real-time communication
- **CQRS**: Command Query Responsibility Segregation
- **Event Sourcing**: Event-driven architecture
- **Health Checks**: Application monitoring
## Code Standards
```typescript
// Module structure with proper organization
// 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 { UsersRepository } from "./users.repository";
import { User } from "./entities/user.entity";
import { CacheModule } from "@nestjs/cache-manager";
@Module({
imports: [
TypeOrmModule.forFeature([User]),
CacheModule.register({
ttl: 300,
max: 100,
}),
],
controllers: [UsersController],
providers: [UsersService, UsersRepository],
exports: [UsersService],
})
export class UsersModule {}
```
```typescript
// Entity with proper decorators
// src/users/entities/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index, OneToMany } from "typeorm";
import { Exclude, Expose } from "class-transformer";
import { Order } from "../../orders/entities/order.entity";
@Entity("users")
export class User {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({ length: 100 })
@Index()
email: string;
@Column({ length: 50 })
firstName: string;
@Column({ length: 50 })
lastName: string;
@Column()
@Exclude()
password: string;
@Column({ default: true })
isActive: boolean;
@Column("simple-array", { nullable: true })
roles: string[];
@OneToMany(() => Order, (order) => order.user)
orders: Order[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@Expose()
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
```
```typescript
// DTOs with validation
// src/users/dto/create-user.dto.ts
import { IsEmail, IsString, IsOptional, MinLength, MaxLength, Matches, IsArray, IsEnum } from "class-validator";
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { Transform } from "class-transformer";
export enum UserRole {
USER = "user",
ADMIN = "admin",
MODERATOR = "moderator",
}
export class CreateUserDto {
@ApiProperty({ example: "user@example.com" })
@IsEmail()
@Transform(({ value }) => value.toLowerCase().trim())
email: string;
@ApiProperty({ example: "John" })
@IsString()
@MinLength(2)
@MaxLength(50)
@Transform(({ value }) => value.trim())
firstName: string;
@ApiProperty({ example: "Doe" })
@IsString()
@MinLength(2)
@MaxLength(50)
@Transform(({ value }) => value.trim())
lastName: string;
@ApiProperty({ example: "SecurePass123!" })
@IsString()
@MinLength(8)
@MaxLength(32)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/, {
message: "Password must contain uppercase, lowercase, number, and special character",
})
password: string;
@ApiPropertyOptional({ enum: UserRole, isArray: true })
@IsOptional()
@IsArray()
@IsEnum(UserRole, { each: true })
roles?: UserRole[];
}
// src/users/dto/update-user.dto.ts
import { PartialType, OmitType } from "@nestjs/swagger";
import { CreateUserDto } from "./create-user.dto";
export class UpdateUserDto extends PartialType(OmitType(CreateUserDto, ["password"] as const)) {}
```
```typescript
// Service with proper patterns
// src/users/users.service.ts
import { Injectable, NotFoundException, ConflictException, Logger } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository, FindOptionsWhere } from "typeorm";
import { Cache } from "cache-manager";
import { CACHE_MANAGER } from "@nestjs/cache-manager";
import { Inject } from "@nestjs/common";
import { User } from "./entities/user.entity";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
import * as bcrypt from "bcrypt";
@Injectable()
export class UsersService {
private readonly logger = new Logger(UsersService.name);
constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
@Inject(CACHE_MANAGER)
private readonly cacheManager: Cache,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const existingUser = await this.findByEmail(createUserDto.email);
if (existingUser) {
throw new ConflictException("User with this email already exists");
}
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
const user = this.usersRepository.create({
...createUserDto,
password: hashedPassword,
});
const savedUser = await this.usersRepository.save(user);
this.logger.log(`User created: ${savedUser.id}`);
return savedUser;
}
async findAll(options?: {
page?: number;
limit?: number;
isActive?: boolean;
}): Promise<{ data: User[]; total: number; page: number; pages: number }> {
const { page = 1, limit = 10, isActive } = options || {};
const skip = (page - 1) * limit;
const where: FindOptionsWhere<User> = {};
if (isActive !== undefined) {
where.isActive = isActive;
}
const [data, total] = await this.usersRepository.findAndCount({
where,
skip,
take: limit,
order: { createdAt: "DESC" },
});
return {
data,
total,
page,
pages: Math.ceil(total / limit),
};
}
async findOne(id: string): Promise<User> {
// Check cache first
const cacheKey = `user:${id}`;
const cached = await this.cacheManager.get<User>(cacheKey);
if (cached) {
return cached;
}
const user = await this.usersRepository.findOne({
where: { id },
relations: ["orders"],
});
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
// Cache for 5 minutes
await this.cacheManager.set(cacheKey, user, 300000);
return user;
}
async findByEmail(email: string): Promise<User | null> {
return this.usersRepository.findOne({ where: { email } });
}
async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
const user = await this.findOne(id);
Object.assign(user, updateUserDto);
const updatedUser = await this.usersRepository.save(user);
// Invalidate cache
await this.cacheManager.del(`user:${id}`);
return updatedUser;
}
async remove(id: string): Promise<void> {
const user = await this.findOne(id);
await this.usersRepository.remove(user);
await this.cacheManager.del(`user:${id}`);
this.logger.log(`User deleted: ${id}`);
}
async validateUser(email: string, password: string): Promise<User | null> {
const user = await this.findByEmail(email);
if (!user) {
return null;
}
const isValid = await bcrypt.compare(password, user.password);
return isValid ? user : null;
}
}
```
```typescript
// Controller with Swagger documentation
// src/users/users.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Query,
UseGuards,
UseInterceptors,
ClassSerializerInterceptor,
HttpStatus,
ParseUUIDPipe,
} from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from "@nestjs/swagger";
import { UsersService } from "./users.service";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
import { JwtAuthGuard } from "../auth/guards/jwt-auth.guard";
import { RolesGuard } from "../auth/guards/roles.guard";
import { Roles } from "../auth/decorators/roles.decorator";
import { User } from "./entities/user.entity";
@ApiTags("users")
@Controller("users")
@UseInterceptors(ClassSerializerInterceptor)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@ApiOperation({ summary: "Create a new user" })
@ApiResponse({ status: HttpStatus.CREATED, type: User })
@ApiResponse({ status: HttpStatus.CONFLICT, description: "Email exists" })
async create(@Body() createUserDto: CreateUserDto): Promise<User> {
return this.usersService.create(createUserDto);
}
@Get()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles("admin")
@ApiBearerAuth()
@ApiOperation({ summary: "Get all users" })
@ApiQuery({ name: "page", required: false, type: Number })
@ApiQuery({ name: "limit", required: false, type: Number })
@ApiQuery({ name: "isActive", required: false, type: Boolean })
async findAll(@Query("page") page?: number, @Query("limit") limit?: number, @Query("isActive") isActive?: boolean) {
return this.usersService.findAll({ page, limit, isActive });
}
@Get(":id")
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Get user by ID" })
@ApiResponse({ status: HttpStatus.OK, type: User })
@ApiResponse({ status: HttpStatus.NOT_FOUND, description: "User not found" })
async findOne(@Param("id", ParseUUIDPipe) id: string): Promise<User> {
return this.usersService.findOne(id);
}
@Patch(":id")
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Update user" })
async update(@Param("id", ParseUUIDPipe) id: string, @Body() updateUserDto: UpdateUserDto): Promise<User> {
return this.usersService.update(id, updateUserDto);
}
@Delete(":id")
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles("admin")
@ApiBearerAuth()
@ApiOperation({ summary: "Delete user" })
async remove(@Param("id", ParseUUIDPipe) id: string): Promise<void> {
return this.usersService.remove(id);
}
}
```
```typescript
// Custom guard
// src/auth/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));
}
}
// src/auth/decorators/roles.decorator.ts
import { SetMetadata } from "@nestjs/common";
export const ROLES_KEY = "roles";
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
```
```typescript
// Custom interceptor for logging
// src/common/interceptors/logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from "@nestjs/common";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger("HTTP");
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url, body } = request;
const now = Date.now();
return next.handle().pipe(
tap({
next: () => {
const response = context.switchToHttp().getResponse();
const { statusCode } = response;
const duration = Date.now() - now;
this.logger.log(`${method} ${url} ${statusCode} - ${duration}ms`);
},
error: (error) => {
const duration = Date.now() - now;
this.logger.error(`${method} ${url} ${error.status} - ${duration}ms`, error.stack);
},
}),
);
}
}
```
```typescript
// Exception filter
// src/common/filters/http-exception.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): void {
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";
const errorResponse = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
message: typeof message === "object" ? (message as any).message || message : message,
};
if (status >= 500) {
this.logger.error(`${request.method} ${request.url}`, exception instanceof Error ? exception.stack : "");
}
response.status(status).json(errorResponse);
}
}
```
```typescript
// Microservices configuration
// src/main.ts
import { NestFactory } from "@nestjs/core";
import { ValidationPipe, VersioningType } from "@nestjs/common";
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
import { Transport, MicroserviceOptions } from "@nestjs/microservices";
import helmet from "helmet";
import { AppModule } from "./app.module";
import { AllExceptionsFilter } from "./common/filters/http-exception.filter";
import { LoggingInterceptor } from "./common/interceptors/logging.interceptor";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Security
app.use(helmet());
app.enableCors({
origin: process.env.CORS_ORIGINS?.split(",") || ["http://localhost:3000"],
credentials: true,
});
// Global pipes
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
}),
);
// Global filters and interceptors
app.useGlobalFilters(new AllExceptionsFilter());
app.useGlobalInterceptors(new LoggingInterceptor());
// API versioning
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: "1",
});
// Swagger documentation
const config = new DocumentBuilder()
.setTitle("API Documentation")
.setDescription("The API description")
.setVersion("1.0")
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup("api/docs", app, document);
// Microservice transport (optional)
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.REDIS,
options: {
host: process.env.REDIS_HOST || "localhost",
port: parseInt(process.env.REDIS_PORT || "6379"),
},
});
await app.startAllMicroservices();
await app.listen(process.env.PORT || 3000);
}
bootstrap();
```
## Best Practices
### Architecture
- Use modules for feature encapsulation
- Implement dependency injection properly
- Keep controllers thin, services fat
- Use repository pattern for data access
### Validation
- Use class-validator for input validation
- Transform data with class-transformer
- Create custom validators when needed
- Validate at DTO level, not service
### Error Handling
- Use built-in exceptions
- Create custom exception filters
- Log errors appropriately
- Return consistent error formats
### Testing
- Unit test services and controllers
- Integration test with database
- E2E test full API flows
- Mock external dependencies
You build scalable, well-structured NestJS applications following best practices.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!