Table of Contents
Introduction
NestJS is a cutting-edge framework that revolutionizes the way developers build scalable and maintainable server-side applications. With its roots in modern JavaScript and TypeScript, NestJS offers a robust architecture inspired by Angular, making it the go-to choice for building enterprise-grade APIs and microservices.
In this guide, we'll take you from a complete beginner to a confident NestJS developer. Whether you're looking to build a simple API or a complex microservice architecture, this post covers everything you need to get started.
What is NestJS?
NestJS is a progressive Node.js framework that leverages the power of TypeScript to build efficient, reliable, and scalable server-side applications. It brings together elements of object-oriented programming, functional programming, and reactive programming to provide an out-of-the-box application architecture.
Designed with developers in mind, NestJS simplifies the development process by offering features such as dependency injection, modularity, and a unified programming model—making it easier to manage and scale your applications.
Installation and Setup
Getting started with NestJS is simple. First, ensure you have Node.js installed. Then, install the Nest CLI globally using npm:
$ npm i -g @nestjs/cli
Next, create a new NestJS project:
$ nest new my-nestjs-app
This command generates a project with a well-organized structure, including a default module, controller, and service to get you started.
Building Your First Module
In NestJS, the application is divided into modules. Each module encapsulates related components like controllers and providers. Let's create a simple "Hello" module.
// src/hello/hello.module.ts
import { Module } from '@nestjs/common';
import { HelloController } from './hello.controller';
import { HelloService } from './hello.service';
@Module({
controllers: [HelloController],
providers: [HelloService],
})
export class HelloModule {}
Create a controller to handle HTTP requests:
// src/hello/hello.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HelloService } from './hello.service';
@Controller('hello')
export class HelloController {
constructor(private readonly helloService: HelloService) {}
@Get()
getHello(): string {
return this.helloService.getHello();
}
}
And a service to contain your business logic:
// src/hello/hello.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class HelloService {
getHello(): string {
return 'Hello, NestJS!';
}
}
Running Your Application
With your module in place, start your NestJS application by running:
$ npm run start
Open your browser and navigate to http://localhost:3000/hello
to see the message Hello, NestJS!
displayed.
Advanced Topics
Once you’re comfortable with the basics, you can explore advanced features of NestJS, such as:
- Middleware: Enhance your request/response pipeline.
- Guards: Implement robust authentication and authorization.
- Interceptors: Transform responses or handle exceptions globally.
- Pipes: Validate and transform incoming data.
These features empower you to build complex, production-ready applications with ease.
Integrating with a Database
Most real-world applications need a persistent storage layer. NestJS integrates seamlessly with ORMs like TypeORM.
For example, here’s how you can set up a connection to a PostgreSQL database:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432', 10),
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASS || 'password',
database: process.env.DB_NAME || 'test',
entities: [User],
synchronize: true, // Note: Disable in production!
}),
],
})
export class AppModule {}
And a simple entity definition:
// src/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
Gotcha: Make sure to disable synchronize
in production environments to avoid accidental data loss.
Middleware and Custom Decorators
Middleware allows you to intercept requests before they reach your routes. Here’s a simple logging middleware:
// src/logger.middleware.ts
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(`[2025-04-13T03:49:18.549Z] ${req.method} ${req.url}`);
next();
}
}
Apply the middleware in your module:
// src/app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
@Module({
// ... your module configuration
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}
You can also create custom decorators. For example, extract the user agent from requests:
// src/user-agent.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const UserAgent = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.headers['user-agent'];
},
);
Use it in your controller:
// src/hello/hello.controller.ts (updated)
import { Controller, Get } from '@nestjs/common';
import { UserAgent } from '../user-agent.decorator';
import { HelloService } from './hello.service';
@Controller('hello')
export class HelloController {
constructor(private readonly helloService: HelloService) {}
@Get()
getHello(@UserAgent() ua: string): string {
console.log('User Agent:', ua);
return this.helloService.getHello();
}
}
Error Handling and Logging
NestJS comes with powerful exception filters that let you centralize error handling. Here’s an example of a custom HTTP exception filter:
// src/http-exception.filter.ts
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 request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const message = exception.message;
console.error(`[2025-04-13T03:49:18.549Z] Error: `, message);
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
});
}
}
Apply it globally during app initialization:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
Performance and Caching
To improve performance, consider using NestJS’s built-in CacheModule.
// src/app.module.ts (with caching)
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
CacheModule.register({
ttl: 5, // seconds
max: 100, // maximum number of items in cache
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Use caching in your services to store frequently accessed data.
Microservices and Deployment
NestJS isn’t just for monolithic applications—it also supports a microservices architecture. Here’s a quick example of setting up a TCP-based microservice:
// src/main.micro.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
},
);
await app.listen();
}
bootstrap();
Deployment Tip: Always use environment variables for configuration and secure your credentials.
Gotchas and Common Pitfalls
As you dive deeper into NestJS, keep these common pitfalls in mind:
- Circular Dependencies: Avoid circular imports by using
forwardRef()
when necessary. - Async Initialization: Ensure asynchronous providers (like database connections) are handled properly.
- Environment Configuration: Never hardcode sensitive information. Use environment variables and configuration modules.
- Error Handling: Centralize your exception handling to avoid leaking internal errors.
Paying attention to these details early on will save you debugging time and keep your application robust.
Testing and Best Practices
NestJS places a strong emphasis on testability. With built-in support for Jest, you can write unit and integration tests effortlessly.
For instance, here’s how you might test the Hello controller:
// src/hello/hello.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { HelloController } from './hello.controller';
import { HelloService } from './hello.service';
describe('HelloController', () => {
let helloController: HelloController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [HelloController],
providers: [HelloService],
}).compile();
helloController = app.get<HelloController>(HelloController);
});
it('should return "Hello, NestJS!"', () => {
expect(helloController.getHello()).toBe('Hello, NestJS!');
});
});
Embracing modular architecture, dependency injection, and TDD will help ensure your application remains scalable and maintainable.
Conclusion
NestJS offers an exceptional framework for building robust and scalable server-side applications. By following this guide, you’ve taken your first steps on a journey from beginner to proficient NestJS developer.
Continue exploring the vast ecosystem of NestJS, experiment with advanced features, and build applications that stand the test of time.
Happy coding, and welcome to the world of NestJS!