Photo by chansu shin on Unsplash

On this page

Express and Fastify handle HTTP well, but they leave the shape of the application to you: where business logic lives, how objects get their dependencies, and where cross-cutting concerns such as authentication or validation run. Every team answers those questions differently. NestJS answers them once. It is a framework on top of an HTTP library that prescribes how code is split into units, how those units find each other, and in which order request-handling steps run. This first chapter of the NestJS series lays out those pieces before the following chapters take them one at a time.

What NestJS adds to Node.js

Nest is written in TypeScript and leans on decorators to attach metadata to classes. At startup, Nest reads that metadata and builds the application from it. Three ideas carry most of the weight:

  • An application graph. Code is grouped into modules that declare what they contain, what they need, and what they share. Nest knows the whole graph before the first request arrives.
  • Dependency injection. Classes ask for collaborators in their constructor. Nest creates the instances, usually once per application, and hands them over. Nothing calls new on a service by hand.
  • A request pipeline. Middleware, guards, interceptors, pipes, and exception filters each have a defined job and a defined position around the route handler. The request lifecycle chapter covers the order.

The same structure carries over to GraphQL resolvers, WebSocket gateways, and microservice message handlers. A guard written for an HTTP route also works in a GraphQL server built with NestJS.

Express or Fastify underneath

Nest does not parse HTTP itself. A platform adapter connects it to an HTTP library. @nestjs/platform-express is the default, and since NestJS 11 it uses Express 5. @nestjs/platform-fastify swaps in Fastify, which has a faster router and lower overhead per request.

src/main.ts
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
}
bootstrap();

Fastify listens on localhost only by default, so a container needs the explicit '0.0.0.0' host.

Code that only uses Nest’s own decorators runs on either adapter. Code that reaches for req and res directly, or uses Express middleware packages, is tied to the adapter it was written for. That is a reason to stay with Nest’s abstractions unless you need something only the underlying library provides.

The building blocks

A Nest application is a tree of modules with one root module at the top. Each module bundles controllers, which receive requests, and providers, which do the work. A module can export providers so that other modules that import it can inject them.

Diagram 1
flowchart TB
  app["AppModule (root)"]
  cats[CatsModule]
  users[UsersModule]
  config[ConfigModule]
  catsCtrl[CatsController]
  catsSvc[CatsService]
  usersSvc[UsersService]
  app --> cats
  app --> users
  app --> config
  cats --> catsCtrl
  cats --> catsSvc
  users --> usersSvc
  catsSvc -. injects .-> usersSvc

Here CatsService can inject UsersService only because UsersModule exports it and CatsModule imports UsersModule. Those declarations are what the next three chapters are about: modules, controllers, and providers.

Creating a project

The Nest CLI scaffolds a project, generates files, and builds. pnpm dlx runs it without a global install:

Terminal window
pnpm dlx @nestjs/cli new cats-api --package-manager pnpm --strict
cd cats-api
pnpm start:dev

--strict turns on stricter TypeScript checks such as strictNullChecks and noImplicitAny, which are off by default in the template. start:dev recompiles and restarts on every change, and the app listens on port 3000. The generated src folder is small:

File Role
main.ts Entry point that creates and starts the application
app.module.ts Root module
app.controller.ts One controller with a GET / route
app.service.ts One provider that the controller injects
app.controller.spec.ts A unit test for the controller

From there, nest g resource cats generates a module, a controller, a service, and DTOs for a new feature in one step. Run it through pnpm nest, because the CLI is a dev dependency of the project.

How the application starts

main.ts hands the root module to NestFactory, which walks the module graph, creates every provider in dependency order, registers the routes, and returns an application object. Settings that apply to the whole app go on that object before listen():

src/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.setGlobalPrefix('api');
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
app.enableShutdownHooks();
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

If a provider cannot be resolved, for example because a module forgot an import, NestFactory.create fails with a message that names the missing dependency and the module it was looked for in. That error appears at startup, not on the first request, and it is the most common error new Nest users see.

For production, pnpm build compiles to dist/, and pnpm start:prod runs node dist/main.

When NestJS is a good fit

Nest pays off when several people work on one server for a long time. The fixed layout makes an unfamiliar feature module look like every other one, dependency injection makes services easy to replace in tests, and the request pipeline gives authentication and validation one obvious place to live.

The cost is indirection. A route that would be five lines in Express involves a module, a controller, a service, and decorators. Cold start is slower because the module graph is built up front, which matters in short-lived serverless functions. For a small proxy or a single webhook endpoint, plain Express, Fastify, or a Next.js route handler is less to maintain.

Next: Modules in NestJS.

Check your understanding

Question 1 of 6

0 of 6 checked

  1. 1. Which three ideas carry most of the weight in NestJS, according to the article?

Search articles

Type a word to search published articles.

Open the search page