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
npm install -g @nestjs/cli
nest new my-api
Equivalent without a global install:
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.
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
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:
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:
{
"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.tsexpecting route logic to live there — actual routes belong in controllers;main.tsonly bootstraps the app. - Removing
experimentalDecorators/emitDecoratorMetadatafromtsconfig.json, or expecting Nest to work with the newer standard TypeScript decorators — Nest's DI depends on the legacy decorator plusreflect-metadatacombination. - Forgetting to run
start:devduring development and instead restartingstartby hand after every change — hot reload exists specifically to avoid that.