Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand NestJS fundamentals ๐ฏ
- Apply NestJS in real projects ๐๏ธ
- Debug common NestJS issues ๐
- Write type-safe enterprise APIs โจ
๐ฏ Introduction
Welcome to the exciting world of NestJS! ๐ In this comprehensive guide, weโll explore the most powerful enterprise-grade Node.js framework that combines the best of Object-Oriented Programming, Functional Programming, and Reactive Programming.
Youโll discover how NestJS can transform your backend development experience. Whether youโre building REST APIs ๐, GraphQL endpoints ๐, or microservices ๐ง, understanding NestJS is essential for creating scalable, maintainable server applications.
By the end of this tutorial, youโll feel confident building enterprise-level applications with NestJS! Letโs dive in! ๐โโ๏ธ
๐ Understanding NestJS
๐ค What is NestJS?
NestJS is like having a well-organized office building ๐ข for your backend code. Think of it as a framework that provides structure, organization, and best practices out of the box, similar to how Angular organizes frontend applications.
In TypeScript terms, NestJS leverages decorators, dependency injection, and modular architecture to create robust server-side applications. This means you can:
- โจ Build scalable applications with clear architecture
- ๐ Develop faster with powerful CLI tools
- ๐ก๏ธ Ensure type safety throughout your backend
- ๐ง Integrate seamlessly with databases and external services
๐ก Why Use NestJS?
Hereโs why developers love NestJS:
- Enterprise Architecture ๐๏ธ: Built-in support for design patterns
- TypeScript First ๐ป: Full TypeScript support with decorators
- Modular Structure ๐ฆ: Organize code into reusable modules
- Powerful CLI โก: Generate components, services, and more instantly
- Testing Ready ๐งช: Built-in testing utilities and mocking
- Rich Ecosystem ๐: Extensive library of official packages
Real-world example: Imagine building a shopping platform ๐. With NestJS, you can organize user management, product catalog, and order processing into separate modules that work together seamlessly.
๐ง Basic Syntax and Usage
๐ Installation and Setup
Letโs start with setting up our NestJS project:
# ๐ Install NestJS CLI globally
npm i -g @nestjs/cli
# ๐จ Create a new project
nest new awesome-api
# ๐ฆ Navigate to project
cd awesome-api
# โก Start development server
npm run start:dev
๐ฏ Basic Controller
Hereโs your first NestJS controller:
// ๐๏ธ src/app.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller('api/v1') // ๐ฏ Route prefix
export class AppController {
@Get('hello') // ๐ GET /api/v1/hello
getHello(): string {
return 'Hello, NestJS World! ๐';
}
@Get('status') // ๐ Health check endpoint
getStatus(): object {
return {
message: 'API is running! ๐',
timestamp: new Date().toISOString(),
status: 'healthy ๐'
};
}
}
๐ก Explanation: Notice how we use decorators like @Controller
and @Get
to define routes! The decorators tell NestJS how to handle HTTP requests.
๐จ Service Layer
NestJS promotes separation of concerns with services:
// ๐ง src/app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable() // ๐ฏ Makes this class injectable
export class AppService {
private readonly users = [
{ id: 1, name: 'Alice ๐ฉโ๐ป', role: 'developer' },
{ id: 2, name: 'Bob ๐จโ๐ฌ', role: 'tester' },
{ id: 3, name: 'Charlie ๐จโ๐ผ', role: 'manager' }
];
// ๐ Get all users
getAllUsers() {
return {
users: this.users,
count: this.users.length,
message: 'Users retrieved successfully! โจ'
};
}
// ๐ค Get user by ID
getUserById(id: number) {
const user = this.users.find(u => u.id === id);
if (!user) {
return { error: 'User not found ๐' };
}
return { user, message: 'User found! ๐' };
}
}
๐ก Practical Examples
๐ Example 1: E-commerce Product API
Letโs build a real product management system:
// ๐ท๏ธ src/products/dto/create-product.dto.ts
export class CreateProductDto {
name: string;
price: number;
description: string;
category: string;
emoji: string; // ๐จ Every product needs an emoji!
}
// ๐ฆ src/products/entities/product.entity.ts
export class Product {
id: number;
name: string;
price: number;
description: string;
category: string;
emoji: string;
createdAt: Date;
updatedAt: Date;
}
// ๐ง src/products/products.service.ts
import { Injectable } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';
import { Product } from './entities/product.entity';
@Injectable()
export class ProductsService {
private products: Product[] = [
{
id: 1,
name: 'TypeScript Handbook',
price: 29.99,
description: 'Complete guide to TypeScript',
category: 'books',
emoji: '๐',
createdAt: new Date(),
updatedAt: new Date()
}
];
private nextId = 2;
// โ Create new product
create(createProductDto: CreateProductDto): Product {
const newProduct: Product = {
id: this.nextId++,
...createProductDto,
createdAt: new Date(),
updatedAt: new Date()
};
this.products.push(newProduct);
return newProduct;
}
// ๐ Get all products
findAll(): Product[] {
return this.products;
}
// ๐ Get product by ID
findOne(id: number): Product | null {
return this.products.find(product => product.id === id) || null;
}
// ๐ Update product
update(id: number, updateData: Partial<Product>): Product | null {
const productIndex = this.products.findIndex(p => p.id === id);
if (productIndex === -1) return null;
this.products[productIndex] = {
...this.products[productIndex],
...updateData,
updatedAt: new Date()
};
return this.products[productIndex];
}
// ๐๏ธ Delete product
remove(id: number): boolean {
const initialLength = this.products.length;
this.products = this.products.filter(p => p.id !== id);
return this.products.length < initialLength;
}
}
// ๐ฎ src/products/products.controller.ts
import {
Controller,
Get,
Post,
Body,
Param,
Delete,
Put,
ParseIntPipe,
HttpException,
HttpStatus
} from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Post() // ๐จ POST /products
create(@Body() createProductDto: CreateProductDto) {
const product = this.productsService.create(createProductDto);
return {
message: `Product ${product.emoji} ${product.name} created! ๐`,
product
};
}
@Get() // ๐ GET /products
findAll() {
const products = this.productsService.findAll();
return {
message: 'Products retrieved successfully! โจ',
products,
count: products.length
};
}
@Get(':id') // ๐ GET /products/:id
findOne(@Param('id', ParseIntPipe) id: number) {
const product = this.productsService.findOne(id);
if (!product) {
throw new HttpException('Product not found ๐', HttpStatus.NOT_FOUND);
}
return {
message: 'Product found! ๐ฏ',
product
};
}
@Put(':id') // ๐ PUT /products/:id
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateData: Partial<CreateProductDto>
) {
const product = this.productsService.update(id, updateData);
if (!product) {
throw new HttpException('Product not found ๐', HttpStatus.NOT_FOUND);
}
return {
message: `Product ${product.emoji} ${product.name} updated! โจ`,
product
};
}
@Delete(':id') // ๐๏ธ DELETE /products/:id
remove(@Param('id', ParseIntPipe) id: number) {
const deleted = this.productsService.remove(id);
if (!deleted) {
throw new HttpException('Product not found ๐', HttpStatus.NOT_FOUND);
}
return { message: 'Product deleted successfully! ๐' };
}
}
๐ฏ Try it yourself: Add product search functionality with filtering by category and price range!
๐ Example 2: Authentication Guard
Letโs implement a security guard:
// ๐ก๏ธ src/auth/auth.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException
} from '@nestjs/common';
import { Request } from 'express';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
const token = request.headers.authorization;
// ๐ Simple token validation
if (!token || !token.startsWith('Bearer ')) {
throw new UnauthorizedException('No valid token provided! ๐ซ');
}
const actualToken = token.replace('Bearer ', '');
// ๐ฏ In real apps, validate JWT here
if (actualToken === 'super-secret-token') {
return true;
}
throw new UnauthorizedException('Invalid token! ๐');
}
}
// ๐ Usage in controller
import { UseGuards } from '@nestjs/common';
@Controller('admin')
@UseGuards(AuthGuard) // ๐ก๏ธ Protect all routes
export class AdminController {
@Get('dashboard')
getDashboard() {
return {
message: 'Welcome to admin dashboard! ๐',
data: 'Top secret admin data ๐คซ'
};
}
}
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Custom Decorators
Create reusable decorators for common patterns:
// ๐ฏ src/decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user; // ๐ค Extract user from request
},
);
// ๐ช Usage in controller
@Controller('profile')
export class ProfileController {
@Get()
@UseGuards(AuthGuard)
getProfile(@CurrentUser() user: any) {
return {
message: `Hello ${user.name}! ๐`,
profile: user
};
}
}
๐๏ธ Advanced Topic 2: Interceptors
Add cross-cutting concerns with interceptors:
// ๐ src/interceptors/logging.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const method = request.method;
const url = request.url;
const now = Date.now();
console.log(`๐ ${method} ${url} - Started`);
return next
.handle()
.pipe(
tap(() => {
const duration = Date.now() - now;
console.log(`โ
${method} ${url} - Completed in ${duration}ms`);
}),
);
}
}
// ๐ Apply globally in main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
await app.listen(3000);
}
bootstrap();
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Forgetting @Injectable
// โ Wrong way - missing decorator!
export class UserService {
getUsers() {
return ['users'];
}
}
// โ
Correct way - include @Injectable!
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
getUsers() {
return ['users']; // ๐ Now it can be injected!
}
}
๐คฏ Pitfall 2: Circular Dependencies
// โ Dangerous - circular dependency!
@Injectable()
export class UserService {
constructor(private orderService: OrderService) {} // ๐ฅ Circular!
}
@Injectable()
export class OrderService {
constructor(private userService: UserService) {} // ๐ฅ Circular!
}
// โ
Safe - use forwardRef or restructure!
import { forwardRef, Inject } from '@nestjs/common';
@Injectable()
export class UserService {
constructor(
@Inject(forwardRef(() => OrderService))
private orderService: OrderService
) {} // โ
Safe now!
}
๐ง Pitfall 3: Module Import Issues
// โ Wrong - service not provided in module
@Module({
controllers: [ProductsController],
// Missing providers array! ๐ฑ
})
export class ProductsModule {}
// โ
Correct - include all providers
@Module({
controllers: [ProductsController],
providers: [ProductsService], // ๐ฏ Don't forget this!
exports: [ProductsService] // ๐ค Export if needed elsewhere
})
export class ProductsModule {}
๐ ๏ธ Best Practices
- ๐ฏ Use DTOs: Always validate input with Data Transfer Objects
- ๐ Document APIs: Use Swagger decorators for API documentation
- ๐ก๏ธ Handle Errors: Implement proper exception filters
- ๐งช Test Everything: Write unit and integration tests
- โจ Keep Modules Small: Single responsibility principle
- ๐ Secure by Default: Use guards, pipes, and interceptors
- ๐ Monitor Performance: Use logging and metrics interceptors
- ๐จ Follow Naming: Use consistent naming conventions
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Task Management API
Create a complete task management system with NestJS:
๐ Requirements:
- โ CRUD operations for tasks
- ๐ท๏ธ Task categories and priorities
- ๐ค User assignment
- ๐ Due dates with status tracking
- ๐ Search and filtering
- ๐ก๏ธ Authentication guard
๐ Bonus Points:
- Add task status transitions (todo โ in-progress โ done)
- Implement task assignment notifications
- Create dashboard with statistics
- Add file attachments to tasks
๐ก Solution
๐ Click to see solution
// ๐ฏ src/tasks/dto/create-task.dto.ts
import { IsString, IsEnum, IsOptional, IsDateString } from 'class-validator';
export enum TaskPriority {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high',
URGENT = 'urgent'
}
export enum TaskStatus {
TODO = 'todo',
IN_PROGRESS = 'in_progress',
DONE = 'done'
}
export class CreateTaskDto {
@IsString()
title: string;
@IsString()
@IsOptional()
description?: string;
@IsEnum(TaskPriority)
priority: TaskPriority;
@IsString()
category: string;
@IsString()
emoji: string;
@IsDateString()
@IsOptional()
dueDate?: string;
@IsString()
@IsOptional()
assigneeId?: string;
}
// ๐ฆ src/tasks/entities/task.entity.ts
export class Task {
id: string;
title: string;
description?: string;
priority: TaskPriority;
status: TaskStatus;
category: string;
emoji: string;
dueDate?: Date;
assigneeId?: string;
createdAt: Date;
updatedAt: Date;
}
// ๐ง src/tasks/tasks.service.ts
import { Injectable } from '@nestjs/common';
import { CreateTaskDto } from './dto/create-task.dto';
import { Task, TaskStatus, TaskPriority } from './entities/task.entity';
@Injectable()
export class TasksService {
private tasks: Task[] = [];
private nextId = 1;
// โ Create task
create(createTaskDto: CreateTaskDto): Task {
const newTask: Task = {
id: this.nextId++,
...createTaskDto,
status: TaskStatus.TODO,
dueDate: createTaskDto.dueDate ? new Date(createTaskDto.dueDate) : undefined,
createdAt: new Date(),
updatedAt: new Date()
};
this.tasks.push(newTask);
return newTask;
}
// ๐ Get all tasks with filtering
findAll(filters?: {
status?: TaskStatus;
priority?: TaskPriority;
category?: string;
assigneeId?: string;
}): Task[] {
let filteredTasks = this.tasks;
if (filters?.status) {
filteredTasks = filteredTasks.filter(task => task.status === filters.status);
}
if (filters?.priority) {
filteredTasks = filteredTasks.filter(task => task.priority === filters.priority);
}
if (filters?.category) {
filteredTasks = filteredTasks.filter(task => task.category === filters.category);
}
if (filters?.assigneeId) {
filteredTasks = filteredTasks.filter(task => task.assigneeId === filters.assigneeId);
}
return filteredTasks;
}
// ๐ Get dashboard statistics
getStats() {
const total = this.tasks.length;
const completed = this.tasks.filter(t => t.status === TaskStatus.DONE).length;
const inProgress = this.tasks.filter(t => t.status === TaskStatus.IN_PROGRESS).length;
const overdue = this.tasks.filter(t =>
t.dueDate && t.dueDate < new Date() && t.status !== TaskStatus.DONE
).length;
return {
total,
completed,
inProgress,
overdue,
completionRate: total > 0 ? Math.round((completed / total) * 100) : 0
};
}
// ๐ Update task status
updateStatus(id: string, status: TaskStatus): Task | null {
const task = this.tasks.find(t => t.id === id);
if (!task) return null;
task.status = status;
task.updatedAt = new Date();
return task;
}
}
// ๐ฎ src/tasks/tasks.controller.ts
import {
Controller,
Get,
Post,
Body,
Param,
Patch,
Query,
UseGuards
} from '@nestjs/common';
import { TasksService } from './tasks.service';
import { CreateTaskDto } from './dto/create-task.dto';
import { TaskStatus, TaskPriority } from './entities/task.entity';
import { AuthGuard } from '../auth/auth.guard';
@Controller('tasks')
@UseGuards(AuthGuard) // ๐ก๏ธ Protect all routes
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Post() // ๐จ Create task
create(@Body() createTaskDto: CreateTaskDto) {
const task = this.tasksService.create(createTaskDto);
return {
message: `Task ${task.emoji} ${task.title} created! ๐`,
task
};
}
@Get() // ๐ Get filtered tasks
findAll(
@Query('status') status?: TaskStatus,
@Query('priority') priority?: TaskPriority,
@Query('category') category?: string,
@Query('assigneeId') assigneeId?: string
) {
const tasks = this.tasksService.findAll({
status,
priority,
category,
assigneeId
});
return {
message: 'Tasks retrieved successfully! โจ',
tasks,
count: tasks.length
};
}
@Get('stats') // ๐ Dashboard statistics
getStats() {
const stats = this.tasksService.getStats();
return {
message: 'Task statistics retrieved! ๐',
stats
};
}
@Patch(':id/status') // ๐ Update status
updateStatus(
@Param('id') id: string,
@Body('status') status: TaskStatus
) {
const task = this.tasksService.updateStatus(id, status);
if (!task) {
throw new HttpException('Task not found ๐', HttpStatus.NOT_FOUND);
}
return {
message: `Task status updated to ${status}! ๐`,
task
};
}
}
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create NestJS applications with proper architecture ๐ช
- โ Build REST APIs with controllers and services ๐ก๏ธ
- โ Implement authentication and guards for security ๐ฏ
- โ Use decorators effectively for clean code ๐
- โ Structure enterprise applications like a pro! ๐
Remember: NestJS is your gateway to building scalable, maintainable backend applications! It provides the structure and tools you need to create production-ready APIs. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered NestJS fundamentals!
Hereโs what to do next:
- ๐ป Practice with the task management exercise above
- ๐๏ธ Build a real API with database integration
- ๐ Explore NestJS modules: GraphQL, WebSockets, Microservices
- ๐ Learn about testing with Jest and Supertest
- ๐ Deploy your NestJS app to production
Remember: Every backend expert started with understanding frameworks like NestJS. Keep building, keep learning, and most importantly, have fun creating amazing APIs! ๐
Happy coding! ๐๐โจ