Complete Guide to NestJS: The Progressive Node.js Framework

Introduction to NestJS
NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. It uses modern JavaScript (or TypeScript), combining elements of Object-Oriented Programming (OOP), Functional Programming (FP), and Functional Reactive Programming (FRP).
Created by Kamil Myśliwiec in 2017, NestJS has quickly gained popularity among developers for its modular architecture and familiar approach to building server-side applications, especially for those coming from Angular background.
Key Features:
- Built with TypeScript (supports pure JavaScript)
- Modular architecture inspired by Angular
- Provides an out-of-the-box application architecture
- Integrates with popular technologies like TypeORM, Mongoose, GraphQL, WebSockets, etc.
- Uses Express.js (or Fastify) under the hood
- Excellent for building microservices
Why Choose NestJS Over Other Node.js Frameworks?
While Express.js and other Node.js frameworks are great for building simple applications, NestJS provides a more structured approach that becomes increasingly valuable as your application grows in complexity.
Feature | Express.js | NestJS |
---|---|---|
Architecture | Minimal, flexible | Structured, modular |
Scalability | Requires manual organization | Built-in modular system |
TypeScript Support | Possible but not built-in | First-class citizen |
Dependency Injection | Not included | Built-in |
Testing | Manual setup | Integrated testing utilities |
Learning Curve | Low | Moderate (easier for Angular developers) |
NestJS is particularly well-suited for:
- Enterprise-grade applications
- Complex backends that need to scale
- Teams that value maintainability and structure
- Projects that might grow in complexity over time
- Developers familiar with Angular's architecture
Core Concepts of NestJS
1. Modules
Modules are the fundamental building blocks of NestJS applications. Each NestJS application has at least one module (the root module), but well-structured applications have many feature modules.
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
Modules help organize the application into cohesive blocks of functionality. They can import other modules and export providers to make them available to other modules.
2. Controllers
Controllers are responsible for handling incoming requests and returning responses to the client.
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Post()
create(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}
@Get()
findAll() {
return this.catsService.findAll();
}
}
3. Providers (Services)
Providers are plain JavaScript classes that can be injected as dependencies. They handle business logic and data access.
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
4. Dependency Injection
NestJS has a built-in dependency injection system that makes it easy to manage dependencies between different parts of your application.
constructor(private catsService: CatsService) {}
The framework will automatically create and inject instances of the required classes.
Setting Up a NestJS Project
Installation
First, install the Nest CLI globally:
npm install -g @nestjs/cli
Then create a new project:
nest new project-name
Project Structure
A new NestJS project has the following structure:
src/ ├── app.controller.ts # Basic controller with a single route ├── app.controller.spec.ts # Unit tests for the controller ├── app.module.ts # Root module of the application ├── app.service.ts # Basic service └── main.ts # Application entry file
Running the Application
To start the development server with hot-reload:
npm run start:dev
The application will be available at http://localhost:3000
.
Building a REST API with NestJS
Let's build a complete CRUD API for a blog post system.
1. Generate Resources
nest generate module posts
nest generate controller posts
nest generate service posts
2. Create DTOs (Data Transfer Objects)
Create src/posts/dto/create-post.dto.ts
:
export class CreatePostDto {
readonly title: string;
readonly content: string;
readonly author: string;
}
Create src/posts/dto/update-post.dto.ts
:
import { PartialType } from '@nestjs/mapped-types';
import { CreatePostDto } from './create-post.dto';
export class UpdatePostDto extends PartialType(CreatePostDto) {}
3. Implement the Service
Update src/posts/posts.service.ts
:
import { Injectable } from '@nestjs/common';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
@Injectable()
export class PostsService {
private posts = [];
private idCounter = 0;
create(createPostDto: CreatePostDto) {
const newPost = { id: ++this.idCounter, ...createPostDto };
this.posts.push(newPost);
return newPost;
}
findAll() {
return this.posts;
}
findOne(id: number) {
return this.posts.find(post => post.id === id);
}
update(id: number, updatePostDto: UpdatePostDto) {
const postIndex = this.posts.findIndex(post => post.id === id);
if (postIndex > -1) {
this.posts[postIndex] = { ...this.posts[postIndex], ...updatePostDto };
return this.posts[postIndex];
}
return null;
}
remove(id: number) {
const postIndex = this.posts.findIndex(post => post.id === id);
if (postIndex > -1) {
return this.posts.splice(postIndex, 1)[0];
}
return null;
}
}
4. Implement the Controller
Update src/posts/posts.controller.ts
:
import { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Post()
create(@Body() createPostDto: CreatePostDto) {
return this.postsService.create(createPostDto);
}
@Get()
findAll() {
return this.postsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.postsService.findOne(+id);
}
@Put(':id')
update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
return this.postsService.update(+id, updatePostDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.postsService.remove(+id);
}
}
5. Update the Module
Update src/posts/posts.module.ts
:
import { Module } from '@nestjs/common';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';
@Module({
controllers: [PostsController],
providers: [PostsService],
})
export class PostsModule {}
6. Import the PostsModule in AppModule
Update src/app.module.ts
:
import { Module } from '@nestjs/common';
import { PostsModule } from './posts/posts.module';
@Module({
imports: [PostsModule],
})
export class AppModule {}
Advanced NestJS Features
1. Middleware
Middleware functions have access to the request and response objects, and the next middleware function in the application's request-response cycle.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(Request...);
next();
}
}
Apply middleware in your module:
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*');
}
}
2. Exception Filters
Nest comes with a built-in exceptions layer that handles all unhandled exceptions.
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
Use it in your controller:
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
3. Pipes
Pipes transform input data to the desired form or validate data.
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
if (!value) {
throw new BadRequestException('No data submitted');
}
return value;
}
}
Use it in your controller:
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
4. Guards
Guards determine whether a given request will be handled by the route handler or not.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise | Observable {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
Use it in your controller:
@UseGuards(AuthGuard)
@Get('profile')
getProfile() {
return this.userService.getProfile();
}
5. Interceptors
Interceptors can bind extra logic before or after method execution, transform the result returned from a function, or extend the basic function behavior.
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 {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(After... {Date.now() - now}ms)),
);
}
}
Use it in your controller:
@UseInterceptors(LoggingInterceptor)
@Get()
findAll() {
return this.catsService.findAll();
}
Database Integration
NestJS works with many databases and ORMs. Let's look at integrating with TypeORM (for SQL databases) and Mongoose (for MongoDB).
1. TypeORM Integration
Install required packages:
npm install @nestjs/typeorm typeorm mysql
Set up the connection in your module:
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [],
synchronize: true,
}),
],
})
export class AppModule {}
Create an entity:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
content: string;
@Column()
author: string;
}
Create a repository:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Post } from './post.entity';
@Injectable()
export class PostsService {
constructor(
@InjectRepository(Post)
private postsRepository: Repository,
) {}
findAll(): Promise {
return this.postsRepository.find();
}
findOne(id: number): Promise {
return this.postsRepository.findOne(id);
}
async remove(id: number): Promise {
await this.postsRepository.delete(id);
}
}
2. Mongoose Integration
Install required packages:
npm install @nestjs/mongoose mongoose
Set up the connection in your module:
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [MongooseModule.forRoot('mongodb://localhost/nest')],
})
export class AppModule {}
Create a schema and model:
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema()
export class Post extends Document {
@Prop()
title: string;
@Prop()
content: string;
@Prop()
author: string;
}
export const PostSchema = SchemaFactory.createForClass(Post);
Create a service:
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Post } from './schemas/post.schema';
@Injectable()
export class PostsService {
constructor(@InjectModel(Post.name) private postModel: Model) {}
async create(createPostDto: CreatePostDto): Promise {
const createdPost = new this.postModel(createPostDto);
return createdPost.save();
}
async findAll(): Promise {
return this.postModel.find().exec();
}
}
Authentication and Authorization
NestJS provides robust tools for implementing authentication and authorization. Let's implement JWT authentication.
1. Install Required Packages
npm install @nestjs/passport passport passport-jwt @nestjs/jwt bcrypt
2. Create Auth Module
nest generate module auth
nest generate service auth
nest generate controller auth
3. Implement User Service
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
private readonly users = [
{
userId: 1,
username: 'john',
password: '$2b$10$abcdefghijklmnopqrstuvwxyz123456',
},
{
userId: 2,
username: 'maria',
password: '$2b$10$abcdefghijklmnopqrstuvwxyz123456',
},
];
async findOne(username: string): Promise {
return this.users.find(user => user.username === username);
}
async validateUser(username: string, pass: string): Promise {
const user = await this.findOne(username);
if (user && await bcrypt.compare(pass, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
}
4. Implement JWT Strategy
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { jwtConstants } from './constants';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
5. Implement Auth Service
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(username: string, pass: string): Promise {
const user = await this.usersService.findOne(username);
if (user && await bcrypt.compare(pass, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { username: user.username, sub: user.userId };
return {
access_token: this.jwtService.sign(payload),
};
}
}
6. Implement Auth Controller
import { Controller, Post, Request, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@UseGuards(AuthGuard('local'))
@Post('login')
async login(@Request() req) {
return this.authService.login(req.user);
}
}
7. Protect Routes
@UseGuards(AuthGuard('jwt'))
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
Testing in NestJS
NestJS provides excellent testing utilities out of the box. Let's look at unit testing and e2e testing.
1. Unit Testing
Test a service:
import { Test, TestingModule } from '@nestjs/testing';
import { PostsService } from './posts.service';
describe('PostsService', () => {
let service: PostsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PostsService],
}).compile();
service = module.get(PostsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should create a post', () => {
const post = service.create({ title: 'Test', content: 'Content', author: 'Author' });
expect(post.title).toBe('Test');
});
});
2. E2E Testing
Test an entire application:
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
Deployment
To deploy a NestJS application:
1. Build the Application
npm run build
2. Run in Production
npm run start:prod
3. Using PM2 (Process Manager)
npm install pm2 -g
pm2 start dist/main.js
4. Docker Deployment
Create a Dockerfile
:
FROM node:16
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/main.js"]
Build and run:
docker build -t nest-app .
docker run -p 3000:3000 nest-app
Conclusion
NestJS is a powerful framework that brings structure and scalability to Node.js applications. Its modular architecture, dependency injection system, and extensive ecosystem make it an excellent choice for building enterprise-grade applications.
Key takeaways:
- NestJS provides a structured approach to building Node.js applications
- The framework is heavily inspired by Angular, making it familiar to frontend developers
- Modules, controllers, and providers are the core building blocks
- NestJS integrates well with databases, authentication systems, and other technologies
- The framework provides excellent testing utilities
- NestJS applications can be deployed using various methods
Whether you're building a small API or a large-scale enterprise application, NestJS provides the tools and architecture you need to create maintainable, scalable, and efficient server-side applications.
Next Steps
To continue your NestJS journey:
- Explore the official NestJS documentation
- Learn about microservices architecture with NestJS
- Implement GraphQL with NestJS
- Explore WebSockets for real-time applications
- Learn about advanced patterns like CQRS
- Contribute to the open-source NestJS ecosystem