NestJS Introduction

What NestJS is, why teams choose it over plain Express, installing the CLI, and the generated project structure.

What is NestJS

NestJS is a structured, opinionated framework for building server-side Node.js applications, written in and designed for TypeScript. Under the hood it runs on top of a battle-tested HTTP framework — Express by default, or optionally Fastify — but instead of leaving architecture entirely up to you, the way Express does, Nest imposes a consistent shape: controllers, providers, and modules, wired together with dependency injection and decorators. Its architecture borrows heavily from Angular — if you've used Angular's modules, decorators, and DI container, Nest's mental model will feel immediately familiar, even though it's solving a server-side, not a UI, problem.

Why teams choose NestJS over plain Express

Express (covered in the Node.js track) is deliberately minimal: it gives you routing and middleware and gets out of your way. That's a strength for small services, but as an Express codebase grows, how you organize routes, share business logic, manage configuration, and test everything is left entirely up to the team — and every team ends up inventing its own conventions.

Nest bakes in answers to those questions:

Concern Plain Express NestJS
Project structure Whatever the team invents Enforced: modules, controllers, providers
Dependency injection Manual (pass dependencies around, or add a DI library) Built in, via decorators and a DI container
Validation Manual, or a library wired in by hand Declarative, via Pipes + DTO classes
Testing Manual mocking First-class testing module (@nestjs/testing) mirrors the DI container
Ecosystem Pick your own ORM/config/queue packages Official first-party packages (@nestjs/config, @nestjs/typeorm, @nestjs/microservices, @nestjs/graphql, ...)
Underlying HTTP layer Express Express or Fastify — swappable via an adapter

None of this makes Nest strictly "better" — a small script or a single-purpose microservice may not need any of this structure. Nest earns its keep on larger applications and larger teams, where a shared, enforced structure prevents the codebase from drifting into inconsistent patterns across dozens of contributors.

Installing Nest and creating a project

Bash
npm install -g @nestjs/cli
nest new my-api

Equivalent without a global install:

Bash
npx @nestjs/cli new my-api

The CLI asks which package manager to use, then scaffolds a runnable project with TypeScript, testing, linting, and formatting already configured.

Bash
cd my-api
npm run start:dev

start:dev runs the app with hot reload. By default it listens on http://localhost:3000.

The generated project structure

Plaintext
my-api/
├── src/
│   ├── main.ts                  # application entry point
│   ├── app.module.ts             # the root module
│   ├── app.controller.ts         # a sample controller
│   ├── app.controller.spec.ts    # its unit test
│   └── app.service.ts            # a sample provider (service)
├── test/
│   └── app.e2e-spec.ts           # end-to-end test
├── nest-cli.json
├── tsconfig.json
└── package.json

main.ts is where the application actually boots:

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

NestFactory.create(AppModule) builds the entire dependency graph starting from the root module, then hands back a fully wired application — every controller and provider Nest could discover through AppModule's imports has already been instantiated by this point.

A note on decorators

Nest relies heavily on decorators (@Controller(), @Injectable(), @Module(), and more) to attach metadata to classes, which its DI container reads at startup to build the dependency graph. This requires two settings the CLI already enables in tsconfig.json:

JSON
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

These enable TypeScript's legacy decorator implementation (not the newer TC39 standard decorators covered in the TypeScript track) together with the reflect-metadata package, which is how Nest reads a constructor's parameter types at runtime to know what to inject.

Common mistakes

  • Editing src/main.ts expecting route logic to live there — actual routes belong in controllers; main.ts only bootstraps the app.
  • Removing experimentalDecorators/emitDecoratorMetadata from tsconfig.json, or expecting Nest to work with the newer standard TypeScript decorators — Nest's DI depends on the legacy decorator plus reflect-metadata combination.
  • Forgetting to run start:dev during development and instead restarting start by hand after every change — hot reload exists specifically to avoid that.