Skip to content
NestJS ns getting-started 5 min read

What is NestJS?

NestJS is a progressive Node.js framework for building efficient, scalable server-side applications. Where minimal frameworks like Express hand you a few primitives and leave the architecture entirely to you, Nest provides an opinionated, batteries-included structure inspired by Angular — modules, dependency injection, decorators, and a clear separation of concerns out of the box. It is written in TypeScript (and fully supports plain JavaScript), and it runs on top of a proven HTTP layer such as Express or Fastify. This page explains what problems NestJS solves, who reaches for it, and the core philosophy that makes large codebases stay maintainable.

The problem NestJS solves

Express is wonderfully flexible, but that flexibility becomes a liability as a team and codebase grow. Two Express projects rarely look alike: one puts everything in app.js, another invents its own folder layout, a third bolts on a homegrown dependency container. There is no shared vocabulary for where code should live or how pieces should wire together.

NestJS answers that with a consistent architecture that every Nest developer recognizes. Controllers handle incoming requests, providers (services) hold business logic, and modules group related features together. The framework’s built-in dependency injection container wires these pieces for you, so classes declare what they need rather than constructing it manually.

// cats.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.catsService.findOne(Number(id));
  }
}

The @Controller('cats') decorator maps the class to the /cats route, @Get(':id') binds a method to GET /cats/:id, and @Param('id') extracts the route parameter — all declarative, all type-checked.

Built on Express or Fastify

NestJS is not a replacement for the underlying HTTP server; it is a layer of architecture on top of one. By default Nest uses Express as its platform, so the entire Express middleware ecosystem (CORS, Helmet, body parsing) remains available. If you need more throughput, you can swap to the Fastify adapter with a single change at bootstrap and keep the rest of your code identical.

AspectExpress adapter (default)Fastify adapter
Package@nestjs/platform-express@nestjs/platform-fastify
EcosystemLargest Express middleware ecosystemFastify plugins
PerformanceSolid, widely battle-testedHigher raw request throughput
When to chooseDefault for most appsHigh-load, latency-sensitive APIs

Because the platform is abstracted behind Nest’s own request/response model, your controllers and services usually do not care which engine is running underneath.

Inspired by Angular

If you have used Angular on the frontend, NestJS will feel immediately familiar. It borrows Angular’s module system, decorator-driven metadata, and dependency injection — applied to the server. A @Module declares which controllers and providers belong to a feature, and which other modules it imports.

// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

A provider is just a class marked @Injectable(). Nest instantiates it once (a singleton by default) and injects it wherever it is requested via the constructor.

// cats.service.ts
import { Injectable } from '@nestjs/common';

export interface Cat {
  id: number;
  name: string;
}

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [{ id: 1, name: 'Whiskers' }];

  findOne(id: number): Cat | undefined {
    return this.cats.find((cat) => cat.id === id);
  }
}

You never write new CatsService(). You declare the dependency in the constructor and the DI container supplies it — which is what makes Nest applications so easy to test.

Core philosophy: modularity, DI, and testability

Three principles run through everything in NestJS:

  • Modularity — features are encapsulated in modules with explicit imports and exports, so the boundaries of each part of the system are visible and enforceable.
  • Dependency injection — classes depend on abstractions, not concrete instances, which decouples your code and centralizes configuration in the module metadata.
  • Testability — because dependencies are injected, you can swap a real provider for a mock in a test with one line.
// cats.service.spec.ts
import { Test } from '@nestjs/testing';
import { CatsService } from './cats.service';

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

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [CatsService],
    }).compile();

    service = moduleRef.get(CatsService);
  });

  it('returns a cat by id', () => {
    expect(service.findOne(1)?.name).toBe('Whiskers');
  });
});

Output:

PASS  src/cats/cats.service.spec.ts
  CatsService
    ✓ returns a cat by id (3 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

Tip: Nest’s @nestjs/testing package builds a full DI container for tests, so you exercise your providers exactly as they run in production — but you can override any provider with .overrideProvider() to inject a stub.

Who uses NestJS and what you can build

NestJS targets teams building serious backends where structure and long-term maintainability matter more than the absolute minimum boilerplate. It is widely adopted at companies running large TypeScript codebases and microservice fleets.

  • REST and GraphQL APIs — first-class support for both, including code-first GraphQL.
  • Microservices — built-in transport layers for TCP, Redis, NATS, Kafka, gRPC, and more.
  • Real-time apps — WebSocket gateways with the same decorator model as controllers.
  • Enterprise monoliths — large applications where the module system keeps hundreds of features organized.

Best Practices

  • Organize code by feature module (a controller, service, and module per domain) rather than by technical layer.
  • Always inject dependencies through the constructor instead of instantiating classes directly, so DI and testing work as intended.
  • Keep controllers thin — they should validate and delegate, while providers hold the business logic.
  • Use TypeScript strictly; Nest’s decorators and DI rely on accurate types and experimentalDecorators/emitDecoratorMetadata being enabled.
  • Export only the providers other modules genuinely need, and import modules explicitly to keep boundaries clear.
  • Start on the default Express adapter and switch to Fastify only when profiling shows you need the extra throughput.
Last updated June 14, 2026
Was this helpful?