Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

NestJS is a Node.js framework for building server-side applications with TypeScript or JavaScript. Its architecture gives every feature a predictable shape: a module composes the feature, a controller handles requests, providers contain reusable behavior, and dependency injection connects those pieces. The current NestJS v11 First Steps documentation requires Node.js 20 or later.

This guide builds a small task API so you can see how the parts work together, then covers validation, testing, authentication, platform choices, builds, and production troubleshooting.

What is NestJS?

NestJS sits above an HTTP platform and supplies conventions for organizing a server application. Express is the default platform; Fastify is an officially supported alternative. Nest describes the relationship this way: “Nest provides a level of abstraction above these common Node.js frameworks (Express/Fastify), but also exposes their APIs directly to the developer.”

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

That distinction matters. Nest gives you modules, decorators, dependency injection, guards, pipes, interceptors, filters, and testing utilities. The underlying adapter still determines platform-specific middleware, plugins, request objects, and response behavior.

The framework’s stated design goal is a testable, scalable, loosely coupled and maintainable architecture inspired by Angular. Those qualities come from how you design the application; installing Nest does not create them automatically.

Create a NestJS v11 project

Prerequisites

  • Node.js 20 or newer, matching the current v11 First Steps requirement.
  • npm or another package manager that can run the generated scripts.
  • Basic TypeScript or modern JavaScript knowledge.

Check your runtime before creating the project:

node --version
npm --version

Use the CLI as a scaffold and workflow tool

The CLI is not required at runtime; it creates files and runs common development tasks.

npm install -g @nestjs/cli
nest new task-api
cd task-api
npm run start:dev

The generated application includes a root module, a controller, a service, an entry point, and sample tests. The entry point creates the application with NestFactory.create(AppModule) and starts listening on the configured port.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For the task feature in this guide, generate the main building blocks:

nest g module tasks
nest g controller tasks
nest g service tasks

Generation is available for controllers, modules, services/providers, guards, pipes, interceptors, middleware, filters, gateways, resolvers, and resources. Keep generated code only when it matches a boundary you can explain; deleting an unnecessary scaffold is better than retaining an empty abstraction.

How modules, controllers, providers and dependency injection fit together

Modules assemble the application

A module groups related controllers and providers. The root module imports feature modules, while each feature module declares the components that belong to it. This makes a feature an explicit composition boundary rather than a folder that merely happens to contain files.

import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';

@Module({
  controllers: [TasksController],
  providers: [TasksService],
  exports: [TasksService]
})
export class TasksModule {}

Export a provider only when another module must consume it. Otherwise keep it private to the feature. The root module imports the feature:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';

@Module({
  imports: [TasksModule]
})
export class AppModule {}

Controllers translate HTTP into application calls

Controllers handle incoming requests and return responses. Route decorators map methods to HTTP routes; controllers should coordinate the request rather than own all business rules.

import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  ParseIntPipe,
  Patch,
  Post
} from '@nestjs/common';
import { TasksService } from './tasks.service';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';

@Controller('tasks')
export class TasksController {
  constructor(private readonly tasksService: TasksService) {}

  @Get()
  findAll() {
    return this.tasksService.findAll();
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.tasksService.findOne(id);
  }

  @Post()
  create(@Body() dto: CreateTaskDto) {
    return this.tasksService.create(dto);
  }

  @Patch(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UpdateTaskDto
  ) {
    return this.tasksService.update(id, dto);
  }

  @Delete(':id')
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.tasksService.remove(id);
  }
}

Providers hold reusable behavior

A provider is an injectable class. Services commonly contain business operations or coordinate repositories and other services. This example uses memory so the architecture is visible; replace the array with a database repository in a real application.

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';

@Injectable()
export class TasksService {
  private nextId = 1;
  private readonly tasks: Array<{ id: number; title: string; done: boolean }> = [];

  findAll() {
    return this.tasks;
  }

  findOne(id: number) {
    const task = this.tasks.find(item => item.id === id);
    if (!task) throw new NotFoundException('Task not found');
    return task;
  }

  create(dto: CreateTaskDto) {
    const task = { id: this.nextId++, title: dto.title, done: false };
    this.tasks.push(task);
    return task;
  }

  update(id: number, dto: UpdateTaskDto) {
    const task = this.findOne(id);
    Object.assign(task, dto);
    return task;
  }

  remove(id: number) {
    const index = this.tasks.findIndex(item => item.id === id);
    if (index === -1) throw new NotFoundException('Task not found');
    this.tasks.splice(index, 1);
    return { deleted: true };
  }
}

Dependency injection connects consumers to providers

The controller receives TasksService in its constructor. It never calls new TasksService(). Nest’s runtime container sees the provider registered by TasksModule, creates it, and supplies the instance. The same mechanism lets you replace a repository, HTTP client, configuration service, or external integration in a test.

Validate request data with DTOs and ValidationPipe

TypeScript annotations disappear at runtime, so a type such as title: string does not reject malformed JSON by itself. DTO classes plus class-validator decorators describe runtime rules, and ValidationPipe applies them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { IsBoolean, IsOptional, IsString, MinLength } from 'class-validator';

export class CreateTaskDto {
  @IsString()
  @MinLength(1)
  title: string;
}

export class UpdateTaskDto {
  @IsOptional()
  @IsString()
  @MinLength(1)
  title?: string;

  @IsOptional()
  @IsBoolean()
  done?: boolean;
}

Install the validation packages, then configure the pipe globally in main.ts:

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true
  }));
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

whitelist removes properties without decorators, forbidNonWhitelisted turns unexpected properties into a client error, and transform enables the documented transformation behavior for DTOs and route parameters. Choose these settings deliberately for your API contract.

Run and test the CRUD endpoint

With the development server running, exercise the API with cURL:

curl http://localhost:3000/tasks
curl -X POST http://localhost:3000/tasks 
  -H 'Content-Type: application/json' 
  -d '{"title":"Write NestJS guide"}'
curl -X PATCH http://localhost:3000/tasks/1 
  -H 'Content-Type: application/json' 
  -d '{"done":true}'
curl -X DELETE http://localhost:3000/tasks/1

Unit-test a provider

Nest’s @nestjs/testing package creates a dependency-injection test environment. You can replace a provider with a small fake instead of contacting a database or external service.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { Test } from '@nestjs/testing';
import { TasksService } from './tasks.service';

describe('TasksService', () => {
  let service: TasksService;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [TasksService]
    }).compile();
    service = module.get(TasksService);
  });

  it('creates a task', () => {
    expect(service.create({ title: 'Test task' })).toMatchObject({
      title: 'Test task',
      done: false
    });
  });
});

Test the HTTP contract end to end

The generated project includes an end-to-end test setup using Jest and Supertest. Start the application with the test module, issue a real HTTP request against it, and assert status codes and response bodies. Override providers in the testing module when the endpoint depends on a live database, queue, mailer, or third-party API. Nest does not force a single testing framework, but its testing utilities and generated setup make Jest and Supertest the default path.

Add authentication without confusing it with authorization

The official authentication tutorial demonstrates a username/password check that returns a JWT, then protects routes with a Passport JWT strategy. In a typical design, a local strategy verifies credentials, a JWT strategy validates a bearer token, and a guard prevents unauthenticated access.

Authentication answers who the caller is. Authorization answers what that authenticated caller may do. The tutorial is an implementation example, not a complete production security policy. You still need decisions about signing-key storage, token lifetime and rotation, account recovery, refresh tokens, roles, permissions, and audit requirements.

Choose Express or Fastify deliberately

Choice What the documentation establishes Decision guidance
Express Default Nest HTTP platform Prefer when your team relies on Express middleware, plugins, examples or APIs.
Fastify Officially supported alternative Consider when its platform model and plugin ecosystem fit your application; audit adapter-specific integrations.

Nest’s abstraction lets most controllers and providers remain unchanged, but middleware, request/response objects and plugins can be adapter-specific. Do not claim a universal speed advantage: the documentation does not provide one benchmark that predicts every Nest workload. Measure your own routes, payloads and deployment conditions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build and start in CI

The CLI documents TypeScript (tsc), SWC and webpack builders. Select based on your configuration and type-checking requirements; do not assume a speed improvement without measuring your project. The CLI marks the legacy --webpack option deprecated in favor of --builder webpack.

npm run build
npm run start:prod

Keep development and production configuration separate, pin the Nest major version used by your application, and verify the Node.js runtime in CI is at least 20 for a v11 project.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

“Cannot resolve dependencies” during startup

The class is usually missing from a module’s providers, or the module that exports it is not listed in imports. Register the provider in its owning feature module and export it only when another module consumes it.

Validation appears to do nothing

Confirm that the request body uses a DTO class, decorators come from class-validator, the packages are installed, and ValidationPipe is applied globally or to the route. Interfaces and TypeScript-only annotations cannot perform runtime validation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A route parameter is still a string

HTTP path parameters arrive as strings. Use a parsing pipe such as ParseIntPipe, or enable and verify transformation behavior in your validation configuration.

Middleware or plugins break after switching adapters

Inspect every integration that calls Express or Fastify APIs directly. Replace platform-specific middleware with an adapter-compatible equivalent, and test error handling, request objects and response methods at the HTTP boundary.

Tests call real external services

Build the test module with provider overrides or fakes. Dependency injection is useful only when dependencies are represented as providers that the test can replace.

The CLI command works locally but not in CI

Check the Node.js version, install dependencies from the lockfile, and use the current builder syntax. Treat the CLI as a development tool and invoke the project’s local scripts in CI rather than depending on an unpinned global CLI.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Or skip the browser setup

If you need screenshots of a NestJS API site, documentation page or dashboard, ScreenshotNeo provides a single HTTP request instead of configuring a headless browser. Before capture it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

See the ScreenshotNeo API documentation for all options. cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 screenshots, and every feature is included on every plan. Create a free ScreenshotNeo account.

Frequently Asked Questions

How do I share a provider between feature modules?

Declare the provider in its owning module, add it to that module’s exports, and import the owning module wherever the consumer is declared. Avoid exporting providers merely for convenience; exports are part of your module’s public boundary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I replace Express with Fastify after starting a project?

Yes, but treat it as an adapter migration: install the supported Fastify platform package, change application bootstrap configuration, and audit middleware, plugins and any code that uses platform-specific request or response APIs.

The Bottom Line

NestJS is easiest to understand as a composition system: modules define boundaries, controllers translate transport requests, providers hold behavior, and dependency injection supplies those providers. Start with a feature module, enable runtime validation, test through the same container you run in production, and choose Express or Fastify according to compatibility and measured needs.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.