Skip to content
Learni
View all tutorials
Backend

How to Build a Professional REST API with NestJS in 2026

Lire en français

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

terminal
npm i -g @nestjs/cli
nest new nestjs-api-2026
cd nestjs-api-2026
npm install class-validator class-transformer @nestjs/config

Initialize a NestJS project and install essential packages for validation and configuration. This lays the foundation for a clean architecture.

Entry Point Configuration

src/main.ts
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

src/app.module.ts
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

src/users/users.controller.ts
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

src/users/users.service.ts
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.