NestJS is the most downloaded TypeScript backend framework on npm. Where Express gives you routing primitives and leaves architecture to you, NestJS prescribes a module system, dependency injection container, and decorator-based API that scales across large teams.
Installation
npm install -g @nestjs/cli
nest new my-api
cd my-api
npm run start:dev
Controllers
import { Controller, Get, Post, Patch, Delete, Param, Body, Query, ParseUUIDPipe } from '@nestjs/common'
import { PostsService } from './posts.service'
import { CreatePostDto } from './dto/create-post.dto'
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Get()
findAll(@Query('page') page = 1, @Query('limit') limit = 20) {
return this.postsService.findAll({ page: +page, limit: +limit })
}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.postsService.findOne(id)
}
@Post()
create(@Body() dto: CreatePostDto) {
return this.postsService.create(dto)
}
@Delete(':id')
remove(@Param('id', ParseUUIDPipe) id: string) {
return this.postsService.remove(id)
}
}
Services
import { Injectable, NotFoundException } from '@nestjs/common'
import { PrismaService } from '../prisma/prisma.service'
@Injectable()
export class PostsService {
constructor(private readonly prisma: PrismaService) {}
async findOne(id: string) {
const post = await this.prisma.post.findUnique({ where: { id } })
if (!post) throw new NotFoundException(`Post ${id} not found`)
return post
}
async create(dto: CreatePostDto) {
const slug = dto.title.toLowerCase().replace(/[^a-z0-9]+/g, '-')
return this.prisma.post.create({ data: { ...dto, slug } })
}
async remove(id: string) {
await this.findOne(id)
await this.prisma.post.delete({ where: { id } })
}
}
Validation with DTOs
npm install class-validator class-transformer
import { IsString, IsBoolean, IsOptional, MinLength, MaxLength } from 'class-validator'
export class CreatePostDto {
@IsString()
@MinLength(3)
@MaxLength(200)
title: string
@IsString()
@MinLength(10)
content: string
@IsOptional()
@IsBoolean()
published?: boolean
}
Enable globally in main.ts:
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // strip extra properties
forbidNonWhitelisted: true,
transform: true // auto-convert types
}))
Modules
@Module({
controllers: [PostsController],
providers: [PostsService],
exports: [PostsService]
})
export class PostsModule {}
JWT Authentication
// jwt.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private usersService: UsersService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET
})
}
async validate(payload: { sub: string; email: string }) {
const user = await this.usersService.findOne(payload.sub)
if (!user) throw new UnauthorizedException()
return user
}
}
// Protect routes
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
return req.user
}
Custom Decorator
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => ctx.switchToHttp().getRequest().user
)
// Usage
@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@CurrentUser() user: User) {
return user
}
Testing
describe('PostsService', () => {
let service: PostsService
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
PostsService,
{
provide: PrismaService,
useValue: {
post: {
findUnique: jest.fn(),
create: jest.fn(),
delete: jest.fn()
}
}
}
]
}).compile()
service = module.get<PostsService>(PostsService)
})
it('throws NotFoundException for missing post', async () => {
jest.spyOn(service['prisma'].post, 'findUnique').mockResolvedValue(null)
await expect(service.findOne('bad-id')).rejects.toThrow(NotFoundException)
})
})
Swagger
npm install @nestjs/swagger
// main.ts
const config = new DocumentBuilder()
.setTitle('My API')
.setVersion('1.0')
.addBearerAuth()
.build()
SwaggerModule.setup('api/docs', app, SwaggerModule.createDocument(app, config))
NestJS vs Fastify vs Express
| NestJS | Fastify | Express | |
|---|---|---|---|
| Architecture | Opinionated | Minimal | Minimal |
| DI container | Built-in | Manual | Manual |
| Validation | class-validator pipes | JSON Schema | External |
| Swagger | Auto-generated | Manual | Manual |
| Learning curve | High | Medium | Low |
| Best for | Large teams | High-throughput | Simple apps |
Full article at stacknotice.com/blog/nestjs-complete-guide-2026
Top comments (0)