Introduction
NestJS is a structured Node.js framework that leverages TypeScript and dependency injection. It enables developers to create maintainable APIs at scale. Unlike plain Express, NestJS enforces a modular architecture inspired by Angular. This tutorial guides you step by step through building a complete REST API with validation, services, and guards. You'll learn professional patterns used in real production environments.
Prerequisites
- Node.js 20+
- Advanced TypeScript
- Basic knowledge of REST and SQL
- npm or pnpm installed
Project Initialization
npm i -g @nestjs/cli
nest new nestjs-api-2026
cd nestjs-api-2026
npm install class-validator class-transformer @nestjs/configInitialize a NestJS project and install essential packages for validation and configuration. This lays the foundation for a clean architecture.
Entry Point Configuration
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.listen(3000);
}
bootstrap();Enable the global ValidationPipe to automatically transform and validate DTOs. This is the foundation of a secure and clean API.
Main Module Creation
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { UsersModule } from './users/users.module';
@Module({
imports: [ConfigModule.forRoot({ isGlobal: true }), UsersModule],
})
export class AppModule {}Structure the application with isolated modules. ConfigModule is loaded globally to access environment variables everywhere.
Users Controller Creation
import { Controller, Get, Post, Body } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
}The controller only handles HTTP routes. Business logic is delegated to the service via dependency injection.
Service and DTO Implementation
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
private users: any[] = [];
create(dto: CreateUserDto) {
const user = { id: Date.now(), ...dto };
this.users.push(user);
return user;
}
findAll() {
return this.users;
}
}The service contains business logic. DTOs ensure strict validation of incoming data.
Best Practices
- Always use DTOs with class-validator
- Clearly separate controllers, services, and repositories
- Use guards and interceptors for cross-cutting concerns
- Configure environment variables via ConfigModule
- Write unit tests for each service
Common Mistakes to Avoid
- Forgetting the @Injectable() decorator on services
- Not enabling the global ValidationPipe
- Mixing business logic into controllers
- Ignoring exception handling with filters
Going Further
Discover our advanced NestJS courses to master CQRS, microservices, and integration testing.