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

NestJS is a Node.js server-side framework that gives TypeScript and JavaScript applications a structured architecture. In NestJS 11, modules compose features, controllers translate HTTP requests into application calls, providers contain reusable behavior, and dependency injection connects those parts. The default HTTP adapter is Express; Fastify is an officially supported alternative.

This guide builds a small tasks API from an empty project, then adds validation, tests, authentication guidance, build choices, and production troubleshooting. Use Node.js 20 or later, as required by the current NestJS 11 First Steps documentation.

What is NestJS?

NestJS is an application framework for server-side Node.js development. It supports TypeScript and JavaScript and adds conventions above an HTTP platform while still exposing that platform’s APIs. As the official documentation puts it, “Nest provides a level of abstraction above these common Node.js frameworks (Express/Fastify), but also exposes their APIs directly to the developer.”

That distinction matters: Nest defines your application architecture, while Express or Fastify handles the underlying HTTP server. Nest’s design goal is a testable, scalable, loosely coupled and maintainable architecture inspired by Angular; those qualities still depend on how you design and operate your application.

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

Create a NestJS 11 project

Prerequisites

  • Node.js 20 or later.
  • npm or another package manager supported by your team.
  • Basic JavaScript or TypeScript knowledge.

Scaffold and run the application

  1. Install the CLI:
    npm install -g @nestjs/cli
  2. Create a project:
    nest new nest-tasks

    Choose your package manager when prompted.

  3. Enter the directory and start the development server:
    cd nest-tasks
    npm run start:dev
  4. Open the generated route at http://localhost:3000. The generated project includes an entry point, root module, sample controller, service, and tests.

The CLI is a scaffolding and workflow tool, not a runtime dependency your server must invoke. Useful commands include nest generate controller tasks, nest generate module tasks, nest generate service tasks, nest build, and nest start. It can also generate guards, pipes, interceptors, middleware, filters, gateways, resolvers, and resources.

How modules, controllers, providers, and dependency injection fit together

Modules assemble a feature

A module groups related controllers and providers and defines the boundary through which the application is composed. The root AppModule imports feature modules; a feature module declares the controllers that expose its routes and the providers that implement its behavior.

Controllers handle transport

A controller maps decorated methods to HTTP routes. It should read parameters and request bodies, call a provider, and return a response. Business rules do not belong in every route method.

Providers hold reusable behavior

A provider is an injectable class, commonly a service or repository. It can depend on other providers, such as a database client, without the controller constructing those dependencies itself.

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

Dependency injection connects them

Nest’s runtime container creates providers and supplies them to constructors. This keeps consumers focused on their work and lets tests replace real dependencies with fakes or mocks.

Build a small tasks CRUD feature

Generate the feature files

nest generate module tasks
nest generate controller tasks
nest generate service tasks

Replace the generated files with the following minimal in-memory implementation. It is intentionally database-free so the architecture is visible; restarting the process clears the data.

tasks.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';

export interface Task {
  id: number;
  title: string;
  completed: boolean;
}

@Injectable()
export class TasksService {
  private readonly tasks: Task[] = [];
  private nextId = 1;

  create(title: string): Task {
    const task = { id: this.nextId++, title, completed: false };
    this.tasks.push(task);
    return task;
  }

  findAll(): Task[] {
    return this.tasks;
  }

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

  update(id: number, changes: Partial<Pick<Task, 'title' | 'completed'>>): Task {
    const task = this.findOne(id);
    Object.assign(task, changes);
    return task;
  }

  remove(id: number): void {
    const index = this.tasks.findIndex(item => item.id === id);
    if (index === -1) throw new NotFoundException(`Task ${id} was not found`);
    this.tasks.splice(index, 1);
  }
}

tasks.controller.ts

import {
  Body, Controller, Delete, Get, Param, ParseIntPipe,
  Patch, Post,
} from '@nestjs/common';
import { TasksService } from './tasks.service';

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

  @Post()
  create(@Body() body: { title: string }) {
    return this.tasksService.create(body.title);
  }

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

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

  @Patch(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() body: { title?: string; completed?: boolean },
  ) {
    return this.tasksService.update(id, body);
  }

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

tasks.module.ts and app.module.ts

// tasks.module.ts
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 {}

// app.module.ts
import { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';

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

exports is only needed when another module must inject TasksService. Keep providers private to their feature unless sharing is intentional.

Try the endpoints

curl -X POST http://localhost:3000/tasks 
  -H 'Content-Type: application/json' 
  -d '{"title":"Write API tests"}'

curl http://localhost:3000/tasks
curl http://localhost:3000/tasks/1
curl -X PATCH http://localhost:3000/tasks/1 
  -H 'Content-Type: application/json' 
  -d '{"completed":true}'
curl -i -X DELETE http://localhost:3000/tasks/1

The generated main.ts normally creates AppModule and listens on port 3000. Keep that bootstrap separate from feature code so deployment configuration can change without rewriting controllers.

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

Add runtime validation with DTOs

TypeScript annotations disappear at runtime; they do not reject malformed JSON by themselves. Install the validation packages:

npm install class-validator class-transformer

Create DTO classes and enable a global ValidationPipe in main.ts:

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
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();
// create-task.dto.ts
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';

export class CreateTaskDto {
  @IsString()
  @IsNotEmpty()
  @MaxLength(200)
  title: string;
}

// update-task.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateTaskDto } from './create-task.dto';
import { IsBoolean, IsOptional } from 'class-validator';

export class UpdateTaskDto extends PartialType(CreateTaskDto) {
  @IsOptional()
  @IsBoolean()
  completed?: boolean;
}

Use CreateTaskDto and UpdateTaskDto in the controller instead of anonymous body types. whitelist strips unknown properties, forbidNonWhitelisted rejects them, and transform enables documented transformations such as route-parameter conversion. Choose these settings deliberately for your API contract.

Testing NestJS applications

Unit-test a provider

Nest supplies @nestjs/testing utilities and generated projects commonly use Jest. The testing module creates the provider through the same dependency-injection mechanism used by the application.

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

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

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

  it('creates and returns a task', () => {
    const task = service.create('Test the service');
    expect(task.title).toBe('Test the service');
    expect(service.findAll()).toHaveLength(1);
  });
});

For a provider that calls a database or HTTP client, register a replacement with useValue or useFactory. This keeps unit tests deterministic and avoids live external services.

Run an HTTP end-to-end test

Nest integrates with Supertest in the generated end-to-end setup. A typical test boots the real module graph and exercises routing and pipes:

import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';

describe('Tasks API', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();
    app = moduleRef.createNestApplication();
    await app.init();
  });

  afterAll(() => app.close());

  it('rejects a task without a title', () => {
    return request(app.getHttpServer())
      .post('/tasks')
      .send({})
      .expect(400);
  });
});

Nest does not require a particular testing framework; the value comes from testing both isolated behavior and the assembled HTTP application.

Authentication and authorization

The official authentication tutorial demonstrates a username/password check that returns a JWT, then protects routes with a Passport JWT strategy. Treat that as an implementation example, not a complete production security policy.

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.
  • Authentication answers who the caller is.
  • Authorization decides what an authenticated caller may do, such as whether a user can update a particular task.
  • Production decisions still include key management, token lifetime, refresh and revocation behavior, account recovery, password hashing, transport security, and role or permission rules.

Keep credential verification in a provider, token extraction and signature checks in a strategy or guard, and resource-level authorization close to the feature whose data is being protected. Test both an unauthenticated request and an authenticated request that lacks permission.

Express or Fastify?

Express is Nest’s default HTTP platform. Fastify is an officially supported alternative. The right choice depends on compatibility and measured needs rather than a universal performance promise.

Decision factor Express Fastify
Default in Nest Yes No
Existing middleware and plugin familiarity Often the simplest path when your team already uses Express APIs Use Fastify-compatible integrations and its platform APIs
Platform-specific behavior Express request/response APIs are available Fastify adapter APIs and plugin conventions apply
Performance decision Benchmark your real routes, payloads, middleware and deployment; the documentation does not establish one result for every Nest workload

Changing adapters can require integration changes even though controllers and providers remain conceptually the same. Decide early when your application depends heavily on middleware or platform-specific plugins.

Build choices in the Nest CLI

The CLI documents TypeScript (tsc), SWC and webpack builders. Select based on your configuration and type-checking workflow; do not assume a speed improvement without measuring your project.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Builder Use it when Check before adopting
tsc You want the conventional TypeScript compiler workflow Compiler options and emitted module format
SWC You have a workflow configured around SWC transpilation Where type checking runs, because transpilation and type checking are separate concerns
webpack You need bundling behavior configured for your deployment Use the current --builder webpack form; the legacy --webpack option is deprecated

Run nest build for a production build and nest start to launch the compiled application according to your project configuration.

Common problems and fixes

“Unsupported engine” or startup failure

Check node --version. NestJS 11’s current First Steps requirement is Node.js 20 or later. Upgrade the runtime used by your shell, CI job and deployment image, not only the one installed locally.

“Cannot resolve dependency”

Confirm the class has @Injectable(), appears in the module’s providers, and that its module is imported wherever it is consumed. If another module needs it, add it to exports; exporting a class without providing it does not register it.

Routes return 404

Check the controller’s @Controller() prefix, method decorator, imported feature module, and any global prefix configured in main.ts. Restart the development process after changing module metadata.

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

Validation accepts unexpected fields

Ensure the request uses DTO classes rather than interfaces or anonymous types, install both validation packages, and register ValidationPipe before listening. Interfaces cannot provide runtime metadata.

Fastify integration breaks

Audit Express-specific middleware and plugins. Replace them with Fastify-compatible integrations and check code that accesses platform-specific request or response methods.

Tests call real services

Override the external provider in the testing module with a mock using useValue or a factory. Keep end-to-end tests for the intentionally assembled application and isolate unit tests from databases and networks.

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

Capture a deployed NestJS page without writing browser automation

If you need a visual snapshot of a public NestJS-rendered page, an API response, or generated documentation, you can automate a browser yourself, but that means managing launchers, waits, consent dialogs and failed navigations.

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

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup 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 status.

One GET request returns PNG, JPEG, WebP or PDF. Replace the URL with your deployed NestJS page:

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

Python:

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

Node.js:

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

For all options, including full-page and element capture, waits, custom headers and cookies, PDF settings, caching, asynchronous jobs, bulk capture and usage reporting, see the ScreenshotNeo documentation. An MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Learning and operating beyond the first feature

Once the tasks feature works, split additional domains into feature modules rather than allowing a single controller or service to become a catch-all. Add repositories or database providers behind service interfaces, keep DTOs at the transport boundary, and make authorization decisions against the current user’s identity and resource ownership.

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

Nest’s official courses cover fundamentals, architecture, authentication, microservices and GraphQL; course availability and prices can change. Nest Enterprise offers architecture reviews, mentoring and security or performance guidance for teams that need those services. Nest Devtools can display an application graph of modules, providers, controllers, routes and events; its listed starting price was USD 5 per month when reviewed and may change. None is required to learn the framework.

Frequently Asked Questions

Can I use JavaScript instead of TypeScript with NestJS?

Yes. NestJS supports JavaScript as well as TypeScript, although DTO decorators and compile-time checking are most commonly used in TypeScript projects.

Does NestJS force me to use Express?

No. Express is the default adapter, and Fastify is officially supported. Adapter-specific middleware and APIs may need changes when you switch.

Where should database code live?

Put database access in an injectable repository or data provider and inject it into a service. Keep controllers focused on HTTP concerns and let the service coordinate application behavior.

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

Is a JWT strategy enough to secure an application?

No. It demonstrates authentication. You must still design authorization, key management, token lifetime, password handling, recovery and other production security controls.

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.