# NestJS From Zero

> Learn the opinionated, TypeScript-first Node framework for structured backends: controllers and routing, providers and dependency injection, modules, DTOs and validation pipes, building a REST API with a service layer, guards and interceptors and middleware, and testing and production. Angular-style architecture over Express or Fastify.


---

# NestJS From Zero

NestJS is what you reach for when an [Express](/guides/express-from-zero) app grows up and the "middleware
soup" starts to hurt. It's an **opinionated, TypeScript-first** framework that brings a real architecture to
Node backends - borrowed, openly, from Angular: modules, controllers, providers, decorators, and a proper
dependency-injection container. Under the hood it runs on Express (or Fastify) but adds the structure and
conventions that keep large teams and large codebases sane. If you like strong typing and a place for
everything, Nest is the Node framework for you.

The mental model is three roles wired by DI. A **controller** handles HTTP - its methods are decorated with
`@Get()`/`@Post()` and map requests to responses. A **provider** (usually a `@Injectable()` service) holds
business logic and is **injected** into controllers (and other providers) by Nest's container - you never
`new` your dependencies. A **module** (`@Module()`) groups related controllers and providers and declares
what it exposes, so the app is a tree of modules. Hold "controllers handle HTTP, providers hold logic, DI
wires them, modules group them," and Nest's decorators stop looking like magic and become a clear structure.

> 📝 This teaches the **framework** - it assumes **TypeScript**: types, classes, decorators, generics
> ([TypeScript From Zero](/guides/typescript-from-zero)) on top of JavaScript/Node. It's most useful read
> after [Express](/guides/express-from-zero) (which it runs on and improves upon); the DI + decorator style
> echoes [Spring Boot](/guides/spring-boot-from-zero) and [ASP.NET Core](/guides/aspnet-core-from-zero).
> Nest runs on Node, so examples are shown with the commands to run them.

## How to read this

Read in order - it grows one service (a small **tasks API**) from a single controller to a structured,
tested, deployable REST API. Phases carry difficulty badges.

## The phases

**Part 1 - The building blocks (🟢 → 🟡)**
1. **[What NestJS Is & Your First App](01-what-nestjs-is.md)** 🟢 - the architecture, the CLI, and a running app.
2. **[Controllers & Routing](02-controllers-and-routing.md)** 🟡 - `@Controller`, route decorators, params, and responses.
3. **[Providers & Dependency Injection](03-providers-and-di.md)** 🟡 - `@Injectable` services and how Nest injects them.
4. **[Modules](04-modules.md)** 🟡 - `@Module`, imports/exports, and organizing the app.

**Part 2 - A real API (🟡 → 🔴)**
5. **[DTOs, Validation & Pipes](05-dtos-validation-pipes.md)** 🔴 - typed request bodies, `class-validator`, and the ValidationPipe.
6. **[Building a REST API](06-building-a-rest-api.md)** 🟡 - a full resource: controller + service + DTOs.
7. **[Guards, Interceptors & Middleware](07-guards-interceptors-middleware.md)** 🔴 - auth guards, the request pipeline, and cross-cutting logic.

**Part 3 - Ship it (🟡 → 🟢)**
8. **[Testing & Production](08-testing-and-production.md)** 🟡 - unit tests with the DI test module, e2e tests, and deployment.
9. **[Where to Go Next](09-where-to-go-next.md)** 🟢 - Nest vs Express/Fastify, TypeORM/Prisma, microservices, and what to build.

> The throughline: **controllers handle HTTP, providers hold logic, dependency injection wires them, and
> modules group them.** That structure is why Nest scales where bare Express sprawls.


---

# What NestJS Is & Your First App

Picture a small [Express](/guides/express-from-zero) app you wrote six months ago. One file. A few routes. Lovely. Now picture that same app today, after it grew: route handlers calling helper functions calling other helpers, middleware stacked five deep, business logic smeared across files because there was never an obvious place to put it. That's "middleware soup" - once an app gets big enough, it stops being fun. You spend more time finding where things live than writing them.

NestJS is the framework you reach for when you want that problem to never start. It's an **opinionated, TypeScript-first** Node framework that brings a real, enforced structure to your backend - borrowed openly from Angular. And here's the part that surprises people: it doesn't throw Express away. By default Nest runs *on top of* Express (you can swap in Fastify via an adapter later), so everything you know about Express is still true underneath. Nest adds the architecture on top.

If the idea of "a framework that takes over the structure of your app" feels abstract, that's exactly the inversion of control from [/guides/what-a-framework-even-is](/guides/what-a-framework-even-is) - your code stops being in charge and starts filling in slots the framework defines. And if you've seen [Spring Boot](/guides/spring-boot-from-zero) or ASP.NET, Nest's whole DI-and-decorators style will feel like coming home; it's the same idea, in TypeScript.

## The mental model: four roles, wired by decorators

Before any code, plant this - it's the single thing that makes Nest stop looking like magic. Everything in a Nest app is one of four roles:

💡 **Controllers handle HTTP. Providers hold logic. Dependency injection wires them together. Modules group them.**

Read that again, because the entire framework is an elaboration of that one sentence. A **controller** is the part that touches the web - it receives a request and returns a response. A **provider** (usually an `@Injectable()` service) holds the actual business logic, kept deliberately separate from the HTTP layer so it stays testable and reusable. **Dependency injection** is how a controller gets hold of the providers it needs - you never write `new SomeService()` yourself; Nest constructs and hands them to you. And a **module** (`@Module()`) is a box that groups related controllers and providers, so a big app becomes a tidy tree of modules instead of one sprawling pile.

📝 The way you *declare* which role a class plays is with **decorators** - `@Controller`, `@Get`, `@Injectable`, `@Module`. A decorator is the `@Something` you write just above a class or method; think of it as a label that tells Nest "treat this thing as *this kind* of thing." Those four roles are the spine of this whole guide. You'll meet controllers properly in Phase 2, providers and DI in Phase 3, and modules in Phase 4. For now, just hold the sentence.

## Scaffolding your first app

You don't build a Nest project by hand - there's a CLI that does the boring setup for you, including the TypeScript and decorator configuration Nest needs to run at all.

First, install the CLI globally, then generate a project:

```bash
npm i -g @nestjs/cli
nest new my-app
```

*What just happened:* the first line installs `@nestjs/cli` so the `nest` command is available everywhere on your machine. The second line scaffolds a brand-new project in a folder called `my-app` - it asks which package manager you want, then creates the folder structure, installs dependencies, wires up TypeScript with decorators enabled, and drops in a tiny working app. You went from nothing to a runnable backend without writing a line of code.

Open the `src/` folder and you'll find a handful of files that map directly onto the mental model:

- `main.ts` - the **bootstrap** file; the entry point that starts everything.
- `app.module.ts` - the root `AppModule` that groups the app together.
- `app.controller.ts` - an `AppController` (the HTTP layer).
- `app.service.ts` - an `AppService` (a provider holding logic).

That's the four roles, already laid out for you in a fresh project. Now start the dev server from inside the folder:

```bash
cd my-app
npm run start:dev
```

*What just happened:* `start:dev` runs Nest in watch mode - it compiles your TypeScript, starts the server (on port 3000 by default), and then *re-runs automatically* every time you save a file. Leave this running in a terminal while you work and you get instant feedback on every change.

## How an app actually boots

Open `main.ts` and you'll see the whole startup in a few lines. This is worth reading slowly, because it's the one place your code is still in charge before the framework takes over:

```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();
```

*What just happened:* `NestFactory.create(AppModule)` is the moment the framework wakes up. You hand it your root module, and Nest walks that module's tree - discovering every controller and provider, constructing them, and wiring all the dependency injection - then returns a fully assembled `app`. `app.listen(3000)` starts the HTTP server (the Express server, underneath) on port 3000. After `bootstrap()` runs, your code is no longer driving: Nest is listening for requests and will call back into *your* controllers when they arrive. That handoff is inversion of control in the flesh - `main.ts` is the last moment you're holding the wheel.

## Your first route

Now let's make the app actually respond to something of our own. Here's a controller that serves a list of tasks:

```typescript
import { Controller, Get } from '@nestjs/common';

@Controller('tasks')
export class TasksController {
  @Get()
  findAll() {
    return [{ id: 1, title: 'Learn Nest', done: false }];   // auto-serialized to JSON
  }
}
```

*What just happened:* `@Controller('tasks')` labels this class as the HTTP handler for the `/tasks` URL - that string is the route prefix. Inside it, `@Get()` marks `findAll()` as the method that runs when a `GET /tasks` request comes in. And notice what `findAll` *returns*: a plain JavaScript array. You didn't open a socket, parse a request, set a `Content-Type` header, or call `JSON.stringify`. Nest takes whatever you return and serializes it to JSON automatically, with a `200` status. You described *which URL runs which method, and what it gives back* - the framework owns everything around that. That's the controller role from the mental model, doing exactly its one job: handling HTTP.

⚠️ This is TypeScript, and Nest leans hard on it - specifically on **decorators**, which require `experimentalDecorators` and `emitDecoratorMetadata` turned on in the TypeScript config. The good news: `nest new` already set all of that up, so you don't have to touch it. But if you ever try to hand-roll a Nest project and get cryptic "decorators are not valid here" errors, that missing config is almost always why. If types, classes, and decorators feel shaky, spend a little time in [/guides/typescript-from-zero](/guides/typescript-from-zero) first - the rest of this guide assumes them.

## Our running example: a tasks API

You may have noticed the tasks theme. That's deliberate. Across this whole guide we'll grow one small, real service: a **tasks API**, where each task is shaped like `{ id, title, done }`. We'll start exactly where we are now - a controller returning a hard-coded array - and phase by phase turn it into a properly structured REST API: real routes and parameters (Phase 2), a service holding the logic (Phase 3), modules organizing it all (Phase 4), validated request bodies (Phase 5), and beyond. Every new Nest concept will land on this same example, so by the end you won't just know the pieces - you'll have watched them assemble into something you'd actually ship.

## Recap

- **NestJS is an opinionated, TypeScript-first Node framework** that brings Angular-style architecture to your backend. It runs *on top of* Express by default (Fastify optional), so Express isn't replaced - it's structured.
- **The whole framework is four roles:** controllers handle HTTP, providers hold logic, dependency injection wires them, and modules group them. Decorators (`@Controller`, `@Get`, `@Injectable`, `@Module`) are how you declare each role.
- **The CLI does the setup.** `npm i -g @nestjs/cli` then `nest new my-app` scaffolds the project (with TypeScript + decorators configured), and `npm run start:dev` runs it in watch mode on port 3000.
- **`main.ts` is the bootstrap.** `NestFactory.create(AppModule)` assembles the app from your module tree, and `app.listen(3000)` starts the server - the point where control passes from your code to the framework.
- **A controller maps a URL to a return value.** `@Controller('tasks')` + `@Get()` + a method that returns an object or array gets auto-serialized to JSON for you.

## Quick check

Make sure the mental model stuck before moving on:

```quiz
[
  {
    "q": "What does NestJS run on top of by default?",
    "choices": [
      "Its own from-scratch HTTP server that replaces Node's",
      "Express (with Fastify available as an alternative adapter)",
      "A browser engine",
      "Django"
    ],
    "answer": 1,
    "explain": "Nest runs on Express by default and adds architecture on top; you can swap in Fastify via an adapter. It doesn't replace Express - it structures it."
  },
  {
    "q": "In Nest's mental model, which role holds the business logic, kept separate from the HTTP layer?",
    "choices": [
      "The controller",
      "The module",
      "A provider (usually an @Injectable service)",
      "main.ts"
    ],
    "answer": 2,
    "explain": "Controllers handle HTTP, providers hold logic, DI wires them, and modules group them. Logic lives in providers so it stays testable and reusable."
  },
  {
    "q": "What happens when a controller method returns a plain object or array?",
    "choices": [
      "Nothing - you must call JSON.stringify and set headers yourself",
      "Nest auto-serializes it to JSON and sends it as the response body",
      "It throws an error because handlers must return strings",
      "It's logged to the console but never sent to the client"
    ],
    "answer": 1,
    "explain": "Nest serializes whatever a handler returns to JSON automatically with a 200 status - you don't touch sockets, headers, or stringify."
  }
]
```


---

# Controllers & Routing

In Phase 1 you got an app running and saw a controller answer a request. Now let's look closely at the thing doing the answering: once it clicks, the rest of Nest stops feeling like a pile of decorators and starts feeling like a layout you can predict.

## The mental model: a controller is a class of routes

Here's the one idea to hold onto: **a controller is a plain class whose methods are routes.** That's it. The class says "I'm in charge of this slice of the URL space." Each method says "I handle this HTTP verb on this sub-path." The decorators are just labels Nest reads to wire the method to a URL.

If you've written [Express](/guides/express-from-zero), you've done this with `app.get('/tasks', handler)`. Nest is the same routing - it literally runs on Express underneath - but instead of registering handlers by hand, you describe them with decorators and let Nest do the registration. The payoff is structure: related routes live together in one named class, and there's an obvious place for every endpoint.

> 📝 A second idea rides along with the first: **parameter decorators inject the pieces of the request you ask for.** You don't reach into a big `req` object and dig out `req.params.id` - you write `@Param('id') id: string` and Nest hands you exactly that. The request gets disassembled into the arguments you declared.

We'll build these ideas around the **tasks API** we're growing through this guide. A task is just `{ id, title, done }`. By the end of the guide it'll have a real service and database behind it; for now we're focused on the HTTP shell - the controller - so the method bodies will stay sketchy on purpose.

## `@Controller` and the route decorators

`@Controller('tasks')` declares a controller whose base path is `/tasks`. Every route method inside it is relative to that base. The method decorators - `@Get()`, `@Post()`, `@Put(':id')`, `@Patch(':id')`, `@Delete(':id')` - name the HTTP verb, and their argument is the **sub-path** appended to the base.

```typescript
import { Controller, Get, Post, Put, Patch, Delete } from '@nestjs/common';

@Controller('tasks')
export class TasksController {
  @Get()              // GET    /tasks
  findAll() { /* ... */ }

  @Get(':id')         // GET    /tasks/:id
  findOne() { /* ... */ }

  @Post()             // POST   /tasks
  create() { /* ... */ }

  @Patch(':id')       // PATCH  /tasks/:id
  update() { /* ... */ }

  @Delete(':id')      // DELETE /tasks/:id
  remove() { /* ... */ }
}
```

*What just happened:* the `'tasks'` on `@Controller` set the base path once, and each method decorator added a verb plus an optional sub-path. `@Get()` with no argument means "the base path itself" (`/tasks`), while `@Get(':id')` adds a route parameter to get `/tasks/:id`. The same `:id` sub-path shows up on `@Patch`, `@Delete`, and `@Put` because they all act on a single task by its id - that's the standard REST shape, and Nest just made it readable.

> ⚠️ Two methods that resolve to the same verb **and** the same path will collide - Nest matches in declaration order, so the first one wins and the second silently never runs. If a route "isn't being hit," check for a duplicate (and remember `@Get(':id')` will happily match `/tasks/anything`, so order your literal routes before your `:id` route when paths could overlap).

## Parameter decorators: pulling the request apart

A route method usually needs *something* from the request - which task id, what query filter, what body to save. Parameter decorators inject exactly those pieces:

- `@Param('id') id: string` - a route parameter from the path (`:id`).
- `@Query('done') done: string` - a single query-string value (`?done=true`).
- `@Body() body: CreateTaskDto` - the parsed request body.
- `@Headers('authorization') auth: string` - a request header.

```typescript
import { Controller, Get, Post, Param, Query, Body } from '@nestjs/common';
import { CreateTaskDto } from './dto/create-task.dto';

@Controller('tasks')
export class TasksController {
  @Get()
  findAll(@Query('done') done?: string) {
    // GET /tasks?done=true  →  done === 'true'
    return `all tasks, filtered by done=${done}`;
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    // GET /tasks/42  →  id === '42'  (a string!)
    return `one task with id ${id}`;
  }

  @Post()
  create(@Body() body: CreateTaskDto) {
    // POST /tasks  with a JSON body
    return body;
  }
}
```

*What just happened:* each decorated argument grabbed one slice of the incoming request. `@Query('done')` read `?done=...`, `@Param('id')` read the `:id` segment, and `@Body()` (with no argument) handed over the whole parsed JSON body. Notice `findOne` returns the value straight away - no response object in sight - and `create` echoes the body back. We typed the body as `CreateTaskDto`; that's a class describing the expected shape, which we'll define and *validate* properly in [Phase 5](05-dtos-validation-pipes.md). For now it's just a type annotation.

> ⚠️ **Route params and query values arrive as strings - always.** `@Param('id') id: string` gives you `'42'`, not `42`. If you need a number, you parse it (or, better, let a `ParseIntPipe` do it for you - that's [Phase 5](05-dtos-validation-pipes.md)). Forgetting this is a classic first-week bug: `id === 42` is `false` when `id` is `'42'`.

There are two more decorators you'll see in other people's code: `@Req()` and `@Res()`, which hand you the raw Express `request` and `response` objects.

> 💡 Reach for `@Req()`/`@Res()` rarely, and `@Res()` almost never. The moment you grab the raw `res` and call `res.json()` yourself, you opt out of Nest's response handling - interceptors and some features stop applying to that route. The whole point of the parameter decorators is that you *don't* need the raw request. Ask for the pieces you want and let Nest manage the rest.

## Responses: just return a value

This is the part that surprises people coming from Express. In Nest you don't call `res.send()` - **you return a value, and Nest serializes it.** Return an object or array and Nest sends it as JSON; return a string and it sends text. The status code defaults to **200**, except `@Post()` which defaults to **201 Created** (the correct status for "I made a thing").

```typescript
import { Controller, Get, Post, Delete, Param, Body, HttpCode, Header } from '@nestjs/common';

@Controller('tasks')
export class TasksController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return { id, title: 'Write the docs', done: false }; // → 200, JSON
  }

  @Post()
  create(@Body() body: { title: string }) {
    return { id: '1', title: body.title, done: false };  // → 201, JSON
  }

  @Delete(':id')
  @HttpCode(204)                  // override: "deleted, no content to return"
  @Header('X-Deleted', 'true')   // set a custom response header
  remove(@Param('id') id: string) {
    return; // nothing to send back
  }
}
```

*What just happened:* `findOne` returned a plain object and Nest turned it into a `200` JSON response - no serialization code from us. `create` returned an object too, but because it's a `@Post()` the default status was `201`. On `remove` we *overrode* the default with `@HttpCode(204)` (the standard "success, empty body" status for deletes) and tacked on a custom header with `@Header(...)`. We never touched a response object once.

> 📝 Returning a value is the idiomatic Nest style, and it's why controllers read so cleanly - a method's signature tells you what it takes in, and its `return` tells you what goes out. You'll go a long way before you ever need the raw `res`.

## It's Express underneath

None of this is a parallel universe. When your app boots, Nest walks your controllers, reads the decorators, and registers each method as a route on Express (or Fastify, if you choose that adapter). `@Get(':id')` on `@Controller('tasks')` becomes, more or less, the `app.get('/tasks/:id', ...)` you'd have written by hand in [Express](/guides/express-from-zero). The decorators are a **declarative layer over the same routing** - Nest does the wiring so your code stays a clean description of intent.

That's the whole controller story: a class marks a base path, methods mark verbs and sub-paths, parameter decorators inject request pieces, and a returned value becomes the response. The method bodies have been stubs because the *logic* doesn't belong here - it belongs in a provider. Wiring those in is exactly where Phase 3 goes.

## Recap

- A **controller is a class whose decorated methods are routes**; `@Controller('tasks')` sets the base path and `@Get`/`@Post`/`@Put`/`@Patch`/`@Delete` set the verb plus an optional sub-path.
- **Parameter decorators inject request pieces**: `@Param('id')` (route param), `@Query('q')` (query string), `@Body()` (parsed body), `@Headers()` (a header).
- **Route params and query values are always strings** - parse them yourself or use a pipe (Phase 5).
- **Return a value and Nest serializes it** to JSON with a `200` (or `201` for `@Post`); override with `@HttpCode()` and set headers with `@Header()`.
- `@Req()`/`@Res()` exist for raw Express access, but avoid them - grabbing raw `res` opts you out of Nest's response handling.
- Under the hood it all compiles down to [Express](/guides/express-from-zero) routes; controllers are a declarative layer over routing you already understand.

## Quick check

```quiz
[
  {
    "q": "Given @Controller('tasks') with a method decorated @Get(':id'), which request does it handle?",
    "choices": ["POST /tasks", "GET /tasks", "GET /tasks/42", "GET /id"],
    "answer": 2,
    "explain": "The base path 'tasks' plus the sub-path ':id' makes GET /tasks/:id, so GET /tasks/42 matches with id = '42'."
  },
  {
    "q": "A method handles @Get(':id') with @Param('id') id. For GET /tasks/42, what is the value and type of id?",
    "choices": ["42 as a number", "'42' as a string", "{ id: 42 } as an object", "undefined"],
    "answer": 1,
    "explain": "Route params (and query values) always arrive as strings - you get '42', not 42. Parse it or use a pipe if you need a number."
  },
  {
    "q": "A @Post() method does `return { id: '1', title: 'x', done: false };` and nothing else. What does the client receive?",
    "choices": ["A 200 response with an empty body", "A 201 response with that object as JSON", "A 500 error because no response was sent", "A 204 response with no content"],
    "answer": 1,
    "explain": "Returning a value lets Nest auto-serialize it to JSON, and @Post defaults to 201 Created - no response object needed."
  }
]
```


---

# Providers & Dependency Injection

In Phase 2 you built a `TasksController` that did everything itself - held the array of tasks *and* handled the HTTP. That works for a demo and falls apart the moment the app grows. Here's the mental model that fixes it - the heart of how Nest is meant to be written.

**Controllers handle HTTP and delegate. Providers hold the logic. Dependency injection wires them together.**

Picture three roles. The controller is the receptionist: it greets the request, reads the URL and body, and hands the work off. The provider - almost always an `@Injectable()` **service** - is the specialist who actually does the work (business rules, data access, calculations). And the wiring between them? You don't do it by hand. You declare what you need as a **constructor parameter**, and Nest's container - its IoC ("Inversion of Control") container - builds the service and hands it to you. You never write `new TasksService()` yourself.

That last part trips people up at first, so sit with it: *you ask for the dependency by listing it in the constructor, and something else supplies it.* That inversion - the framework constructing your dependencies instead of you - is the whole game.

## Step one: pull the logic into a service

Let's split the Phase 2 controller. The data and behavior move into a service marked `@Injectable()`:

```typescript
import { Injectable } from '@nestjs/common';

@Injectable()
export class TasksService {
  private tasks = [];

  findAll() {
    return this.tasks;
  }

  create(title: string) {
    const task = { id: Date.now(), title, done: false };
    this.tasks.push(task);
    return task;
  }
}
```

*What just happened:* `@Injectable()` is the marker that tells Nest "this class can be managed by the container - you're allowed to inject it places." Inside, it's a plain TypeScript class holding the `tasks` array and two methods. No HTTP anywhere - no `@Get`, no `@Post`, no request objects. This class doesn't know or care that it's part of a web app, which is exactly the point: it's pure logic you could test or reuse on its own.

## Step two: inject it into the controller

Now the controller asks for the service and delegates to it:

```typescript
import { Controller, Get } from '@nestjs/common';

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

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

*What just happened:* The controller no longer owns any tasks. It declares one constructor parameter - `tasksService: TasksService` - and when Nest creates the controller, it sees that parameter, finds the `TasksService` instance it manages, and passes it in. The route handler shrank to a single line: read nothing, decide nothing, hand it to the service. That's a thin controller.

> 📝 The `private readonly tasksService: TasksService` in the constructor is TypeScript shorthand. Adding an access modifier (`private`, `public`, `readonly`) to a constructor parameter tells TS to both declare it as a field *and* assign it automatically. Without the shorthand you'd write `this.tasksService = tasksService` by hand. So one line declares the dependency, stores it as `this.tasksService`, and makes it read-only - all at once.

## Step three: register the provider (or Nest can't find it)

There's one piece that makes the magic work, and people forget it. For Nest to inject `TasksService`, the service has to be listed in a module's **`providers`** array. A module is the next phase's topic, but here's the shape so the picture is complete:

```typescript
import { Module } from '@nestjs/common';

@Module({
  controllers: [TasksController],
  providers: [TasksService],   // ← this is what makes TasksService injectable
})
export class TasksModule {}
```

*What just happened:* The `providers` array is the registry. When Nest boots, it reads this module, sees `TasksService` listed, constructs exactly one instance, and stores it in the container - ready to hand to anyone who asks for it in a constructor. Leave `TasksService` out of this array and the `@Injectable()` decorator alone won't save you: Nest won't know the service exists. (Full module mechanics - imports, exports, sharing providers across modules - come in [Phase 4](04-modules.md).)

## Scopes: one instance, shared by default

How many `TasksService` objects exist? By default: **exactly one**, for the whole application's lifetime. Providers are **singletons**. Every controller or other provider that injects `TasksService` gets the *same* instance - which is why the `tasks` array persists across requests in our demo. This is efficient (build it once) and it's the right default for the overwhelming majority of services.

Occasionally you need a fresh instance per HTTP request - say, a service that holds data specific to the current user's request and must not leak into another. For that there's request scope:

```typescript
import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
  // a new instance is created for every incoming request
}
```

*What just happened:* `Scope.REQUEST` tells Nest to construct a new instance per request instead of reusing one singleton. It's genuinely useful in narrow cases, but it's slower (Nest has to build the instance, and everything that depends on it, on every request) and you rarely need it. ⚠️ Reach for it only when you have a concrete reason; default to the singleton.

## Why bother? Swappable dependencies

Here's the payoff that makes DI worth the ceremony. Because the controller depends on the *class* `TasksService` rather than constructing one itself, you can hand it a *different* object that fits the same shape. In a test, you swap in a fake `TasksService` that returns canned data - no real database, no real state - and verify the controller behaves correctly in isolation. The controller can't tell the difference, because it never knew where its dependency came from in the first place.

> 💡 That's the real reason dependency injection exists: it decouples "what I need" from "who builds it," which is what makes code testable and changeable. It's the same DI you'd recognize from [Spring Boot](/guides/spring-boot-from-zero) and [ASP.NET Core](/guides/aspnet-core-from-zero) - different language, identical idea. We'll lean on exactly this swap-in-a-fake trick when we write tests in [Phase 8](08-testing-and-production.md).

## When the wiring breaks

You will eventually see this at startup:

```
Nest can't resolve dependencies of the TasksController (?).
Please make sure that the argument TasksService at index [0]
is available in the TasksModule context.
```

⚠️ This is Nest telling you it tried to build something and couldn't find one of its constructor dependencies. The usual cause is the one from step three: the provider isn't in any module's `providers` array (or isn't exported from the module it lives in, or two providers depend on each other in a circle). Don't panic at the wall of text - **read it**. It names the class it was building, and it names the dependency it couldn't resolve. Nine times out of ten the fix is "add the missing provider to `providers`." The error is doing you a favor by failing loudly at boot instead of silently at runtime.

## Recap

- A **provider** is a class - usually an `@Injectable()` **service** - that holds business logic and data access, kept separate from the controller so controllers stay thin (HTTP only).
- **Dependency injection** means you declare a dependency as a **constructor parameter** and Nest's IoC container builds and supplies it. You never `new` your dependencies.
- The `private readonly x: T` constructor shorthand both declares the dependency and stores it as a field in one line.
- A provider must be listed in a module's **`providers`** array, or Nest can't inject it.
- Providers are **singletons by default** (one shared instance) - the right default; `Scope.REQUEST` gives a per-request instance for the rare cases that need it.
- DI makes dependencies **swappable**, which is what makes controllers testable - the same idea you've seen in Spring and ASP.NET. A "Nest can't resolve dependencies of…" error at startup names the exact provider that's missing.

## Quick check

Three quick ones to make sure the core idea stuck:

```quiz
[
  {
    "q": "What makes TasksService eligible to be injected into a controller?",
    "choices": ["Calling new TasksService() in the controller", "Marking it @Injectable() and listing it in a module's providers array", "Exporting it as a default export", "Adding @Get() to its methods"],
    "answer": 1,
    "explain": "@Injectable() marks the class as manageable by the container, and listing it in a module's providers array is what actually registers it so Nest can resolve and inject it."
  },
  {
    "q": "How many instances of a default-scoped provider does Nest create for the whole app?",
    "choices": ["One per request", "One per controller that injects it", "Exactly one, shared application-wide (singleton)", "One per route handler call"],
    "answer": 2,
    "explain": "Providers are singletons by default: Nest builds one instance and shares it everywhere it's injected. Scope.REQUEST is the opt-in for per-request instances."
  },
  {
    "q": "You get 'Nest can't resolve dependencies of the TasksController' at startup. What's the most likely fix?",
    "choices": ["Rename the controller", "Add the missing provider to a module's providers array", "Remove the constructor parameter", "Switch the provider to Scope.REQUEST"],
    "answer": 1,
    "explain": "That error means a constructor dependency couldn't be found in the module context. The usual cause is a provider missing from the providers array; the message names which one."
  }
]
```


---

# Modules

You've got a `TasksController` handling HTTP and a `TasksService` holding the logic, with Nest's DI container wiring one into the other. But something has to *introduce* them to the container in the first place - to say "these two belong together, here's the controller, here's the provider it depends on." That's a **module**.

Here's the mental model to hold onto: **a module groups a feature's controllers and providers into one cohesive unit, and your whole app is a tree of modules with a single root at the top - `AppModule`.** Every controller and provider lives inside exactly one module. Nest walks that tree on startup, reads each module's metadata, and builds the DI container from it. Once you see the app as a tree of feature modules rather than a pile of files, the structure stops feeling arbitrary and starts feeling like a map.

📝 This is the piece that separates a Nest app from a sprawling Express app. In Express, structure is a convention you hope everyone follows. In Nest, structure is enforced by the framework: a feature is a module, and the module declares exactly what it owns and what it shares.

## A module is a class with one decorator

A module is an ordinary class annotated with `@Module()`. The decorator takes a metadata object that tells Nest what's inside. Here's the `TasksModule` that owns our tasks feature:

```typescript
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';

@Module({
  controllers: [TasksController],
  providers: [TasksService],
})
export class TasksModule {}
```

*What just happened:* We declared a feature unit. `controllers: [TasksController]` tells Nest "this module handles these HTTP routes." `providers: [TasksService]` tells Nest "this module owns this injectable - instantiate it and make it available to anything in this module that asks for it." The class body is empty because a module is pure configuration; the decorator's metadata *is* the module. When Nest boots `TasksModule`, it creates one `TasksService` instance and injects it into the `TasksController` constructor, exactly as you saw in [Phase 3](03-providers-and-di.md).

## Wiring it into the root

A module on its own does nothing - Nest only knows about it if it's reachable from the root. The root module, conventionally `AppModule`, is the trunk of the tree. Other modules become branches by being listed in its `imports`:

```typescript
import { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';

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

*What just happened:* `AppModule` doesn't declare any controllers or providers of its own here - it's a composition root. By putting `TasksModule` in its `imports`, we attach the tasks feature to the tree. Now when you start the app, Nest finds `AppModule`, follows `imports` to `TasksModule`, registers its controller and provider, and your `/tasks` routes go live. Add a `UsersModule` later and you wire it the same way: build the feature module, then list it in the root's `imports`.

💡 You almost never write this wiring by hand. Running `nest g module tasks` scaffolds `tasks.module.ts` *and* adds it to `AppModule.imports` automatically. `nest g resource tasks` goes further - it generates the module, controller, service, and DTOs, and wires them all together. The CLI exists precisely so module plumbing stays correct as the app grows.

## The four metadata fields

Everything a module can declare lives in four arrays. You'll use all of them as your app grows:

- **`controllers`** - the controllers that belong to this module. Nest instantiates them and registers their routes.
- **`providers`** - the providers (services, etc.) Nest can instantiate and inject *within this module*. This is the module's private toolbox.
- **`imports`** - other modules whose **exported** providers this module needs. Importing a module pulls its public providers into scope here.
- **`exports`** - the subset of *this* module's providers that you want to make available to modules that import it. The public face of the module.

The first two say "what this module contains." The last two say "how this module connects to others." Most feature modules start with just `controllers` and `providers` - you reach for `imports` and `exports` the moment one feature needs another's service.

## ⚠️ Encapsulation: providers are private by default

This is the single most common source of "Nest can't resolve dependencies" errors, so slow down here. **A provider is private to its module unless you explicitly `export` it.** Listing a service in `providers` makes it injectable *inside that module only* - not anywhere else in the app, no matter how the tree is shaped.

Say `TasksModule` needs `UsersService` (maybe a task records who created it). It is not enough to import `UsersModule`. The provider has to cross *two* gates: `UsersModule` must declare it in `exports`, **and** `TasksModule` must declare `UsersModule` in `imports`. Both, or it fails.

```typescript
// users.module.ts - UsersService must be EXPORTED to escape the module
@Module({
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// tasks.module.ts - and TasksModule must IMPORT UsersModule to receive it
@Module({
  imports: [UsersModule],
  controllers: [TasksController],
  providers: [TasksService], // TasksService can now inject UsersService
})
export class TasksModule {}
```

*What just happened:* We opened the door on both sides. `UsersModule` publishes `UsersService` by putting it in `exports` - without that line, the service stays sealed inside `UsersModule` and no amount of importing will reach it. `TasksModule` then pulls in that public provider by listing `UsersModule` in `imports`. Now `TasksService`'s constructor can declare `private users: UsersService` and Nest resolves it. Forget the `exports` line and you get the classic error: *"Nest can't resolve dependencies of the TasksService (?). Please make sure that the argument UsersService ... is available."* The fix is almost always "I imported the module but forgot to export the provider," or vice versa.

⚠️ The error message is your friend here - it names the failing provider and the module it tried to resolve in. When you hit it, check the export side first (it's the more commonly forgotten one), then the import side. The module boundary is doing exactly what it's designed to do: keeping internals private until you choose to share them.

## Why this matters: boundaries that scale

📝 On a tiny app, modules can feel like ceremony. Their payoff shows up as the app grows. A real Nest backend is a handful of focused feature modules - a `TasksModule`, a `UsersModule`, an `AuthModule` - each owning its controllers and services, each exposing only what others legitimately need. That gives you clear boundaries: you can read one module and understand a whole feature without the rest of the app leaking in. When `AuthModule` exports an `AuthService` and three other modules import it, the dependency is explicit and traceable - not a global singleton that anything can grab. This is the discipline that keeps large Nest codebases navigable where bare Express apps sprawl into tangled `require` graphs. And because the CLI scaffolds each module for you, the structure stays consistent no matter who on the team adds the next feature.

## Recap

- A **module** is a class with `@Module()` that groups a feature's `controllers` and `providers` into one cohesive unit.
- The app is a **tree of modules** with a single **root** (`AppModule`); feature modules join the tree by appearing in another module's `imports`.
- The four metadata fields: **`controllers`** and **`providers`** say what a module *contains*; **`imports`** and **`exports`** say how it *connects* to other modules.
- **Encapsulation**: a provider is private to its module unless `export`ed. Cross-module use requires the provider in the owner's `exports` *and* the owner module in the consumer's `imports` - missing either causes the #1 "can't resolve dependencies" error.
- The CLI (`nest g module`, `nest g resource`) scaffolds modules and wires them into `AppModule` for you, keeping structure consistent as the app grows.

## Quick check

```quiz
[
  {
    "q": "What is the relationship between AppModule and feature modules like TasksModule?",
    "choices": ["They are unrelated and discovered automatically by file name", "AppModule is the root of a tree; feature modules join it via imports", "Feature modules import AppModule to gain access to it", "AppModule must list every provider from every feature module"],
    "answer": 1,
    "explain": "The app is a tree of modules with AppModule as the root. A feature module becomes part of the app by being listed in another module's imports (usually AppModule's)."
  },
  {
    "q": "TasksService needs to inject UsersService, which lives in UsersModule. What must be true?",
    "choices": ["Just add UsersService to TasksModule's providers array", "UsersModule must export UsersService AND TasksModule must import UsersModule", "Nothing - providers are globally available across all modules", "AppModule must export UsersService to all children"],
    "answer": 1,
    "explain": "Providers are private to their module. To share one, the owning module must list it in exports, and the consuming module must list the owning module in imports. Both gates are required."
  },
  {
    "q": "What do a module's `controllers` and `providers` fields declare?",
    "choices": ["Other modules this one depends on", "Which providers this module shares with importers", "The controllers and injectables this module contains and owns", "The HTTP routes exposed to the public internet"],
    "answer": 2,
    "explain": "controllers and providers list what the module *contains*. imports/exports handle connections to other modules; controllers/providers handle what's inside this one."
  }
]
```


---

# DTOs, Validation & Pipes

Back in [Phase 2](02-controllers-and-routing.md), `@Body()` handed you whatever JSON the client sent - typed as `any`, trusted blindly. That's fine in a demo and a disaster in production. A client can post `{ "title": 12345 }`, or `{ }`, or `{ "title": "x", "isAdmin": true }` trying to sneak in a field you never meant to accept. Somebody has to check.

The naive instinct is to write `if (!body.title) throw new BadRequestException(...)` at the top of every handler. Do that across twenty endpoints and your controllers turn into validation sludge, and the rules drift because nobody keeps twenty copies in sync.

Nest's answer is to make validation **declarative and automatic**. Here's the whole mental model, and it has three moving parts:

- A **DTO** (Data Transfer Object) is a **class** that describes the shape of the request body. One file, one source of truth.
- **class-validator decorators** on its fields are the *rules* - `@IsString()`, `@MaxLength(120)`, and friends.
- The **ValidationPipe** is the *enforcer*. It runs *before* your handler, reads those rules, and rejects anything that breaks them with an automatic **400 Bad Request**.

> 💡 A **pipe** in Nest is anything that transforms or validates a method's input before the handler runs. The ValidationPipe is the famous one, but you'll meet smaller built-in pipes (like `ParseIntPipe`) at the end of this phase. Same slot in the pipeline, smaller job.

Wire those three together and you write **no validation code in the controller at all**. Let's build it for the tasks API.

## Step 1 - Install the libraries

The DTO decorators and the engine that reads them live in two packages:

```bash
npm i class-validator class-transformer
```

*What just happened:* `class-validator` provides the `@IsString()` / `@IsNotEmpty()` / etc. decorators and the logic to check a value against them. `class-transformer` turns the plain JSON object Nest received into a real instance of your DTO class (validators need an *instance* to inspect, not a bare object). The ValidationPipe leans on both - install them or it'll throw an unhelpful error at startup.

## Step 2 - Write the CreateTaskDto

A DTO is an ordinary class where every field carries its validation rules as decorators:

```typescript
import { IsString, IsNotEmpty, IsOptional, IsBoolean, MaxLength } from 'class-validator';

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

  @IsOptional()
  @IsBoolean()
  done?: boolean;
}
```

*What just happened:* Read the decorators as a sentence. `title` **must** be a string (`@IsString`), **must not** be empty (`@IsNotEmpty`), and **must** be at most 120 characters (`@MaxLength(120)`). `done` is **optional** (`@IsOptional` - the `?` makes it optional in TypeScript, the decorator tells the validator "skip the rest of my rules if it's missing"), and *if present* must be a boolean. This class is now both your TypeScript type **and** your validation rulebook in one place - that's the whole point.

## Step 3 - Turn on the ValidationPipe globally

A DTO with rules does nothing on its own; something has to enforce it. Switch on the ValidationPipe for the entire app in `main.ts`:

```typescript
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,
    }),
  );

  await app.listen(3000);
}
bootstrap();
```

*What just happened:* `useGlobalPipes` installs the ValidationPipe in front of **every** handler in the app. From now on, any `@Body() dto: CreateTaskDto` is validated automatically before your code runs. The two options are your security defaults:

- `whitelist: true` - silently **strips** any property not declared on the DTO. Client sends `{ title: "Buy milk", isAdmin: true }`? Your handler receives `{ title: "Buy milk" }`. The junk never reaches your logic.
- `forbidNonWhitelisted: true` - go further and **reject** the request with a 400 if it contains unknown properties, instead of quietly dropping them. Loud failure beats silent surprise.

## Step 4 - Use the DTO in the controller

Now the controller method just *declares* the DTO type. No `if` statements, no manual checks:

```typescript
import { Body, Controller, Post } from '@nestjs/common';
import { CreateTaskDto } from './dto/create-task.dto';

@Controller('tasks')
export class TasksController {
  @Post()
  create(@Body() createTaskDto: CreateTaskDto) {
    // If we reach this line, createTaskDto is already valid.
    return { received: createTaskDto };
  }
}
```

*What just happened:* The ValidationPipe sees the `CreateTaskDto` type annotation on `@Body()`, builds an instance from the incoming JSON, runs its rules, and - only if everything passes - calls `create()`. A POST with `{ "title": "" }` never reaches your method: the pipe short-circuits it into a `400 Bad Request` with a message like `"title should not be empty"`, generated for you. Your handler body is pure business logic, exactly as it should be.

## ⚠️ The two gotchas that bite everyone

> ⚠️ **A DTO must be a `class`, not a TypeScript `interface`.**

This is the single most common "why isn't my validation running?" bug. Watch what *doesn't* work:

```typescript
// ❌ This compiles fine and validates NOTHING.
export interface CreateTaskDto {
  title: string;
  done?: boolean;
}
```

*What just happened:* TypeScript `interface`s are a *compile-time* construct - they're erased entirely when your code becomes JavaScript. At runtime there is no `CreateTaskDto` for the ValidationPipe to inspect, and decorators can't even attach to an interface. class-validator reads metadata off a real **class** that exists at runtime, so the rules vanish along with the interface. Use a `class`. Always. (If your editor ever suggests "convert to interface," say no.)

> ⚠️ **`whitelist` decides the fate of unknown properties - choose deliberately.**

With `whitelist: true` alone, extra fields are **stripped** (gone, no error). Add `forbidNonWhitelisted: true` and extra fields are **rejected** with a 400. Without either, a malicious or buggy client's extra fields flow straight into your handler. For most APIs, turn both on - it closes a real mass-assignment hole.

## Step 5 - `ParseIntPipe`: validating route params

DTOs cover the request *body*. But route params like `:id` arrive as **strings** (a URL is text), and you usually want a number. That's a job for a tiny built-in pipe:

```typescript
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';

@Controller('tasks')
export class TasksController {
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    // id is a real number here, e.g. 42 - not "42"
    return { lookingUp: id };
  }
}
```

*What just happened:* `ParseIntPipe` sits between the route and your method. For `GET /tasks/42` it converts the string `"42"` into the number `42`, so `id` is genuinely typed and usable. For `GET /tasks/banana` it can't parse the value and throws an automatic **400** - your handler never runs. One pipe, both transformation and validation, no boilerplate. (Siblings: `ParseBoolPipe`, `ParseUUIDPipe`, and more.)

## Step 6 - `PartialType`: a DRY update DTO

Updating a task should accept the *same* fields as creating one, but all of them **optional** - a PATCH might change only the `title`. Rewriting the whole DTO with `@IsOptional()` on every field is duplication waiting to rot. Nest gives you a helper:

```typescript
import { PartialType } from '@nestjs/mapped-types';
import { CreateTaskDto } from './create-task.dto';

export class UpdateTaskDto extends PartialType(CreateTaskDto) {}
```

*What just happened:* `PartialType(CreateTaskDto)` generates a new class with **every field of `CreateTaskDto` made optional**, *keeping all the original validation rules*. So `title` stays "if present, a non-empty string ≤ 120 chars" - it's just no longer required. Your `UpdateTaskDto` is one line and can never drift out of sync with `CreateTaskDto`. Use it on your PATCH handler exactly like before: `@Body() updateTaskDto: UpdateTaskDto`.

> 💡 `PartialType` comes from `@nestjs/mapped-types`. If you're already using Swagger for API docs, import it from `@nestjs/swagger` instead - same behavior, plus it carries the field metadata into your generated docs. There's also `PickType`, `OmitType`, and `IntersectionType` in the same toolbox for composing DTOs.

You now have everything the tasks API needs to trust its inputs: `CreateTaskDto` and `UpdateTaskDto` guarded by a global ValidationPipe, and `ParseIntPipe` on the `:id` routes. In [Phase 6](06-building-a-rest-api.md) we'll plug these into a full resource - controller plus service - and the validation layer just quietly does its job.

## Recap

- A **DTO is a class** that describes the request body; **class-validator decorators** are its rules; the **ValidationPipe** enforces them before your handler runs - so controllers carry no validation code.
- Enable it once: `app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }))`. Invalid input becomes an automatic **400** with messages.
- **`whitelist`** strips unknown properties; **`forbidNonWhitelisted`** rejects them - solid security defaults against mass-assignment.
- A DTO **must be a `class`, never an `interface`** - interfaces are erased at runtime, leaving nothing for the validator to read.
- **`ParseIntPipe`** turns a string `:id` into a number and 400s on garbage; **`PartialType(CreateTaskDto)`** builds a DRY update DTO with all fields optional but the same rules.
- Install the engine with `npm i class-validator class-transformer`.

## Quick check

```quiz
[
  {
    "q": "Why must a NestJS DTO be a class and not a TypeScript interface?",
    "choices": ["Classes are faster to instantiate", "Interfaces can't have a constructor", "Interfaces are erased at runtime, so class-validator has no metadata to read", "Nest only imports files that export classes"],
    "answer": 2,
    "explain": "Interfaces are a compile-time-only construct and vanish in the compiled JS. class-validator needs a real class (with decorator metadata) that exists at runtime."
  },
  {
    "q": "With the global ValidationPipe configured as { whitelist: true, forbidNonWhitelisted: true }, what happens to a request body containing a property not declared on the DTO?",
    "choices": ["It is silently stripped before the handler runs", "The request is rejected with a 400", "It is passed through unchanged", "The server returns a 500"],
    "answer": 1,
    "explain": "whitelist alone would strip the unknown property; adding forbidNonWhitelisted makes the pipe reject the request with a 400 instead."
  },
  {
    "q": "What does PartialType(CreateTaskDto) produce for an UpdateTaskDto?",
    "choices": ["A class with all of CreateTaskDto's fields made optional, keeping their validation rules", "A copy of CreateTaskDto with all validation removed", "An interface version of CreateTaskDto", "A DTO with only the required fields"],
    "answer": 0,
    "explain": "PartialType makes every inherited field optional while preserving the original class-validator rules, so the update DTO stays DRY and in sync."
  }
]
```


---

# Building a REST API

This is the payoff phase. For five phases you've been collecting parts: a controller that handles HTTP ([Phase 2](02-controllers-and-routing.md)), a service that holds logic and gets injected ([Phase 3](03-providers-and-di.md)), a module that wires them together ([Phase 4](04-modules.md)), and DTOs with a ValidationPipe that guard the input ([Phase 5](05-dtos-validation-pipes.md)) - now we snap them into one complete, working resource.

Here's the mental model for a REST resource in Nest, and it's the same shape for every resource you'll ever build:

**A thin controller maps routes to method calls → a service holds the data and the logic → DTOs type the input → a module wires it all → and you throw HTTP exceptions for the error cases.**

That's the whole thing. The controller decides nothing; it reads the request and delegates. The service is where the work lives. When something goes wrong - a task that doesn't exist, say - you don't hand-craft a 404 response; you `throw` a built-in exception and Nest renders it for you.

> 📝 You don't have to assemble this by hand every time. `nest g resource tasks` scaffolds exactly this layout - controller, service, DTOs, module, and CRUD method stubs - in one command. We're building it manually here so you can see every piece and *why* it's there; once you understand it, let the generator do the typing.

## The service: where the data and logic live

Start with the service, because it's the heart of the resource. It owns an in-memory store and the five CRUD operations. For the error cases, it throws Nest's built-in HTTP exceptions:

```typescript
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';

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

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

  findAll() {
    return this.tasks;
  }

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

  create(dto: CreateTaskDto) {
    const task = { id: this.nextId++, ...dto, done: dto.done ?? false };
    this.tasks.push(task);
    return task;
  }

  update(id: number, dto: UpdateTaskDto) {
    const task = this.findOne(id); // reuses the 404 throw above
    Object.assign(task, dto);
    return task;
  }

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

*What just happened:* The service is pure logic with zero HTTP knowledge - no `@Get`, no request objects, exactly as Phase 3 promised. `findOne` is the piece to study: when the task isn't there, it `throw`s a `NotFoundException` instead of returning `null` or fabricating a response object. That throw is doing real work - Nest catches it and turns it into a `404 Not Found` with a clean JSON body, automatically. Notice `update` *reuses* `findOne`, so the "not found" rule lives in exactly one place. The `nextId` counter hands out simple ascending ids, and `done: dto.done ?? false` defaults the optional flag. This store is a stand-in for a real database - swap it for TypeORM or Prisma later (more on that below).

## The controller: thin routing to the service

Now the controller. Its only job is to map each HTTP route to a service call, lean on the pipes and DTOs from Phase 5, and pick the right status code:

```typescript
import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  Param,
  ParseIntPipe,
  Patch,
  Post,
} from '@nestjs/common';
import { TasksService } from './tasks.service';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';

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

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

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

  @Post()
  create(@Body() dto: CreateTaskDto) {
    return this.tasks.create(dto); // 201 Created by default on POST
  }

  @Patch(':id')
  update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateTaskDto) {
    return this.tasks.update(id, dto);
  }

  @Delete(':id')
  @HttpCode(204)
  remove(@Param('id', ParseIntPipe) id: number) {
    this.tasks.remove(id);
  }
}
```

*What just happened:* Every handler is one line, because every decision was made elsewhere. `@Param('id', ParseIntPipe)` turns the URL string `"42"` into the number `42` (and 400s on garbage); `@Body() dto: CreateTaskDto` arrives already validated by the global ValidationPipe - so the controller trusts its inputs without a single `if`. Two status-code details matter: a `@Post()` returns **201 Created** automatically (Nest knows POST means "made something new"), and `@HttpCode(204)` overrides the default on `@Delete` to return **204 No Content** - the correct "done, nothing to send back" status. The constructor injection (`private readonly tasks: TasksService`) is the Phase 3 wiring; the controller never `new`s the service.

## The module: wiring it together

Neither class does anything until a module registers them. This is the Phase 4 piece:

```typescript
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';

@Module({
  controllers: [TasksController],
  providers: [TasksService],
})
export class TasksModule {}
```

*What just happened:* `controllers` registers the route handlers; `providers` registers `TasksService` so the DI container can build it and inject it into the controller. Leave `TasksService` out of `providers` and you'd hit that "Nest can't resolve dependencies of the TasksController" error from Phase 3. Import this `TasksModule` into your `AppModule` and the resource is live.

## Driving it with curl

With the app running (`npm run start:dev` from [Phase 1](01-what-nestjs-is.md)), here's the full resource in action. First, create a task:

```bash
curl -X POST http://localhost:3000/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "Buy milk"}'
```

```
HTTP/1.1 201 Created
{"id":1,"title":"Buy milk","done":false}
```

*What just happened:* The POST returned **201**, not 200 - that's Nest's default for `@Post`, signalling a resource was created. The body came back with the server-assigned `id` and the defaulted `done: false`. The ValidationPipe checked `{"title": "Buy milk"}` against `CreateTaskDto` and let it through.

Now ask for a task that doesn't exist:

```bash
curl -i http://localhost:3000/tasks/999
```

```
HTTP/1.1 404 Not Found
{"statusCode":404,"message":"Task 999 not found","error":"Not Found"}
```

*What just happened:* This is the `throw new NotFoundException(...)` from the service, rendered. You wrote one line - `throw` - and Nest produced the correct status code *and* a structured JSON error body with your message. You never touched a response object.

And here's what a bad request body looks like, courtesy of the Phase 5 ValidationPipe:

```bash
curl -i -X POST http://localhost:3000/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": ""}'
```

```
HTTP/1.1 400 Bad Request
{"statusCode":400,"message":["title should not be empty"],"error":"Bad Request"}
```

*What just happened:* The empty `title` violated `@IsNotEmpty()` on the DTO, so the pipe rejected the request with a **400** before `create()` ever ran - and the message names exactly which rule failed. The controller stayed blissfully ignorant; validation happened in the pipeline. Finally, `curl -i -X DELETE http://localhost:3000/tasks/1` returns a bare `204 No Content` with no body, exactly as `@HttpCode(204)` specified.

## The two ideas to carry forward

> 💡 **Built-in HTTP exceptions auto-map to the right status code and a JSON body - you throw, Nest renders.** `NotFoundException` → 404, `BadRequestException` → 400, `ForbiddenException` → 403, `UnauthorizedException` → 401, `ConflictException` → 409, and more. Reach for the named exception that fits the situation and stop thinking about response objects; the framework handles the HTTP translation.

> 💡 **The in-memory array is a database stand-in.** It's perfect for learning and prototyping, but it forgets everything on restart and won't survive more than one server process. The beauty of the service layer is that swapping it out is a *contained* change: the controller, DTOs, and module don't move - you replace the array and the CRUD bodies inside `TasksService` with real persistence. That's where an ORM comes in: see [how an ORM works](/guides/how-an-orm-works), then wire in TypeORM or Prisma.

You've now built a complete REST resource the way Nest intends: thin controller, logic-holding service, validated DTOs, a module that wires it, and exceptions that turn into proper HTTP responses. Every resource you build follows this exact template. In [Phase 7](07-guards-interceptors-middleware.md) we'll wrap cross-cutting concerns - auth, logging, request transformation - around this pipeline without touching the resource itself.

## Recap

- A REST resource is a **thin controller** (routes → calls) over a **service** (data + logic), with **DTOs** typing the input and a **module** wiring it - the same shape for every resource.
- The service owns the store and the CRUD methods, and **throws built-in HTTP exceptions** (like `NotFoundException`) for error cases instead of crafting responses by hand.
- The controller delegates in one-line handlers, using `@Param('id', ParseIntPipe)` and validated `@Body() dto`, returning **201** on POST and **204** on DELETE (via `@HttpCode(204)`).
- Built-in exceptions **auto-map to status + JSON**: `throw` the right one and Nest renders the response - `NotFoundException` → 404, `BadRequestException` → 400, and so on.
- `nest g resource tasks` scaffolds this entire layout in one command once you understand the pieces.
- The in-memory array is a **database stand-in**; swapping it for TypeORM/Prisma ([how an ORM works](/guides/how-an-orm-works)) is contained to the service, leaving the controller, DTOs, and module untouched.

## Quick check

```quiz
[
  {
    "q": "In the TasksService, what happens when findOne is called with an id that isn't in the store?",
    "choices": ["It returns null and the controller must build a 404", "It throws NotFoundException, which Nest auto-maps to a 404 response", "It returns an empty array", "It throws a generic Error that becomes a 500"],
    "answer": 1,
    "explain": "The service throws NotFoundException; Nest catches built-in HTTP exceptions and renders the correct status code (404) plus a JSON error body automatically."
  },
  {
    "q": "Why is the @HttpCode(204) decorator added to the remove() handler?",
    "choices": ["To make DELETE return 200 with the deleted task", "To override the default and return 204 No Content, the right status for a successful delete with no body", "To validate the id parameter", "To register the route in the module"],
    "answer": 1,
    "explain": "DELETE that succeeds with nothing to return should respond 204 No Content. @HttpCode(204) overrides Nest's default 200 for that handler."
  },
  {
    "q": "What stays the same when you later replace the in-memory array with a real database via an ORM?",
    "choices": ["Nothing - every layer must be rewritten", "The controller, DTOs, and module; only the service's store and CRUD bodies change", "Only the module changes", "The DTOs must be converted to interfaces"],
    "answer": 1,
    "explain": "The service layer contains the change: the controller, DTOs, and module don't move. You swap the array and the CRUD method bodies inside TasksService for real persistence."
  }
]
```


---

# Guards, Interceptors & Middleware

By [Phase 6](06-building-a-rest-api.md) the tasks API does real work: a controller, a service, DTOs that validate themselves. But every endpoint is still wide open - anyone who knows the URL can list, create, and delete tasks. And nothing is logged, so when something misbehaves in production you're flying blind.

The reflex from an Express background is to reach for middleware and stuff everything into it: auth checks, logging, response shaping, all in one `(req, res, next)` blob. That works, but it also turns into the same "middleware soup" that pushed you toward Nest in the first place.

Here's the mental model that replaces the soup, and it's the whole point of this phase: **Nest's request pipeline is a sequence of named building blocks, and each one has exactly one job.** A request doesn't just hit your handler - it flows through a fixed order of slots on the way in, and back out the way it came:

```mermaid
flowchart LR
  R[Request] --> M[Middleware]
  M --> G[Guards]
  G --> Ipre[Interceptors<br/>before]
  Ipre --> P[Pipes]
  P --> H[Route handler]
  H --> Ipost[Interceptors<br/>after]
  Ipost --> Resp[Response]
  H -. throws .-> F[Exception filters]
  F --> Resp
```

> 📝 The order is fixed: **middleware → guards → interceptors (pre) → pipes → handler → interceptors (post) → exception filters** (on error). You don't wire the order yourself - Nest enforces it. Your job is knowing *which slot* a given concern belongs in. That choice is the actual skill, and the rest of this phase is teaching your instinct for it.

You already know one of these slots: **pipes** (Phase 5) live just before the handler and validate/transform input. Now let's meet the others by securing and instrumenting the tasks API.

## Guards - deciding who gets in

A **guard** answers one yes/no question: *may this request proceed?* That's it. Authentication ("are you who you say you are?") and authorization ("are you allowed to do this?") are guard work. A guard implements `CanActivate` and returns `true` (let it through) or `false` (block it with a `403 Forbidden`) - or throws an exception for a more specific response.

Let's lock the tasks routes behind a simple API key:

```typescript
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest();
    return req.headers['x-api-key'] === process.env.API_KEY;
  }
}
```

*What just happened:* `canActivate` runs *before* the handler. The `ExecutionContext` is Nest's portable handle on the current request - `switchToHttp().getRequest()` pulls out the underlying HTTP request so we can read its headers. We compare the `x-api-key` header to the expected key. Return `true` and the request continues down the pipeline; return `false` and Nest stops everything and sends a `403` - your controller never runs. Because it's `@Injectable()`, a guard can have services injected into it, exactly like any provider (a real `AuthGuard` would inject a token-verification service here).

Now attach it. `@UseGuards()` works on a single route or the whole controller:

```typescript
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import { ApiKeyGuard } from './api-key.guard';

@UseGuards(ApiKeyGuard)
@Controller('tasks')
export class TasksController {
  @Get()
  findAll() {
    return this.tasksService.findAll();
  }

  @Post()
  create() {
    /* ... */
  }
}
```

*What just happened:* Putting `@UseGuards(ApiKeyGuard)` on the controller class applies it to **every** route inside - both `findAll` and `create` now require a valid `x-api-key` header. Put it on a single method instead and only that route is guarded. (To protect the entire app, register it once with `app.useGlobalGuards(new ApiKeyGuard())` in `main.ts`.) Notice the controller code itself didn't change - no `if (!authorized)` check anywhere. The guard owns that concern, and the handler stays pure business logic.

> ⚠️ A guard runs **after** middleware but **before** pipes. That ordering matters: there's no point validating a request body for someone who isn't allowed in. Auth first, then parse. Nest gets this right for you by putting guards earlier in the pipeline.

## Interceptors - wrapping the handler

A guard is a gate: in or out. An **interceptor** is a wrapper: it runs code *before* the handler **and** *after* it, with the handler sandwiched in the middle. That before-and-after shape is what makes interceptors perfect for logging, timing, caching, and reshaping the response.

The "after" part works through an RxJS stream - the handler's return value flows back as an observable you can tap into or transform. Here's a logging interceptor that times every request to the tasks API:

```typescript
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const req = context.switchToHttp().getRequest();
    const start = Date.now();

    // ── before the handler runs ──
    return next.handle().pipe(
      // ── after the handler returns ──
      tap(() => {
        const ms = Date.now() - start;
        console.log(`${req.method} ${req.url} - ${ms}ms`);
      }),
    );
  }
}
```

*What just happened:* Everything above `return next.handle()` runs **before** the handler - here we stamp the start time. `next.handle()` actually *invokes* the handler and returns its result as an observable. The `.pipe(tap(...))` hooks into that stream **after** the handler finishes, so we can measure elapsed time and log it. `tap` is the read-only RxJS operator - it observes without changing the value, so the response passes through untouched. (Swap `tap` for `map` and you could *transform* the response - e.g. wrap every payload in `{ data: ... }`.) Apply it just like a guard: `@UseInterceptors(LoggingInterceptor)` on a route, controller, or `app.useGlobalInterceptors(...)` for the whole app.

> 💡 The "two halves around one call" shape is the tell. Whenever you catch yourself wanting to do something *before and after* the handler - log it, time it, cache it, reshape its output - that's an interceptor, not middleware and not a guard.

## The rest of the pipeline: filters, pipes, middleware

Three slots remain. You've met one; here are quick mental models for all three so the picture is complete.

**Exception filters** decide how a thrown exception becomes an HTTP response. Nest's built-in filter already maps `HttpException`s sensibly (a `NotFoundException` → `404` with a tidy JSON body), so you only write your own when you want a custom error *shape* or centralized error logging:

```typescript
import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
} from '@nestjs/common';

@Catch(HttpException)
export class HttpErrorFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const res = host.switchToHttp().getResponse();
    const status = exception.getStatus();
    res.status(status).json({
      ok: false,
      status,
      message: exception.message,
      timestamp: new Date().toISOString(),
    });
  }
}
```

*What just happened:* The `@Catch(HttpException)` decorator says "this filter handles `HttpException`s." When one is thrown anywhere downstream, Nest routes it here instead of using the default, and we send our own JSON envelope (`{ ok: false, ... }`) with the right status code. `ArgumentsHost` is the same portable context idea as `ExecutionContext`, giving us `getResponse()` to write the reply. Register it with `@UseFilters(HttpErrorFilter)` or globally - and every error in the app now speaks one consistent format.

**Pipes** you already own from Phase 5: they validate and transform *input* right before the handler (`ValidationPipe` on a `@Body()` DTO, `ParseIntPipe` on an `:id` param). Same slot in the diagram, the "shape the input" job.

**Middleware** is the Express-native escape hatch - the classic `(req, res, next)` function. Unlike the others, you don't attach it with a decorator; you configure it in a module:

```typescript
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { TasksController } from './tasks.controller';

function requestId(req: any, res: any, next: () => void) {
  req.id = crypto.randomUUID();
  next();
}

@Module({ controllers: [TasksController] })
export class TasksModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(requestId).forRoutes(TasksController);
  }
}
```

*What just happened:* By implementing `NestModule`, the module gets a `configure(consumer)` hook. `consumer.apply(requestId).forRoutes(TasksController)` says "run this middleware before any route on `TasksController`." Middleware sits at the very front of the pipeline (before guards), so it's the right home for raw `req`/`res` work and for plugging in the Express ecosystem - `helmet`, `cors`, request-id stamping, body loggers. 📝 Reach for middleware for *low-level / Express-world* concerns; prefer guards, interceptors, and pipes for anything Nest-native, because they get the framework's DI, typing, and testability.

## 💡 The decision guide (the takeaway)

When a cross-cutting concern shows up, don't agonize - match it to its slot:

| You want to… | Reach for | Implements |
|---|---|---|
| Allow or deny a request (auth) | **Guard** | `CanActivate` |
| Do something before **and** after the handler (log, time, cache, reshape response) | **Interceptor** | `NestInterceptor` |
| Validate or coerce the incoming input | **Pipe** | `PipeTransform` |
| Turn a thrown error into a custom response | **Exception filter** | `@Catch()` |
| Raw `req`/`res` work or plug in Express middleware | **Middleware** | `(req, res, next)` |

> 💡 Memorize it as one sentence: **auth → guard; around the handler → interceptor; input → pipe; errors → filter; raw Express → middleware.** Once that mapping is automatic, Nest's pipeline stops feeling like a pile of decorators and starts feeling like a well-labelled toolbox.

That's the cross-cutting layer done. The tasks API now checks its callers (guard), times every request (interceptor), validates its input (pipes), and can speak a consistent error format (filter). In [Phase 8](08-testing-and-production.md) we make sure all of it actually works - and survives production.

## Recap

- Nest's request pipeline is a **fixed order of named slots**: middleware → guards → interceptors (pre) → pipes → handler → interceptors (post) → exception filters (on error). Each slot has **one job**.
- **Guards** (`CanActivate`) decide whether a request may proceed - auth/authz. Return `true`/`false` (or throw); a `false` becomes a `403`. Attach with `@UseGuards()`.
- **Interceptors** (`NestInterceptor`) wrap the handler, running code before and after via `next.handle().pipe(...)` - ideal for logging, timing, caching, and reshaping the response.
- **Exception filters** (`@Catch()`) customize how exceptions become responses; the default already handles `HttpException`s, so add one only for custom shapes or central logging.
- **Pipes** (Phase 5) validate/transform input; **middleware** (`configure` + `MiddlewareConsumer`) is the Express-style escape hatch for low-level `req`/`res` work.
- The skill is **picking the right slot**: auth → guard, around-the-handler → interceptor, input → pipe, errors → filter, raw Express → middleware.

## Quick check

```quiz
[
  {
    "q": "Which building block should you use to reject a request from an unauthenticated client?",
    "choices": ["An interceptor", "A guard", "A pipe", "An exception filter"],
    "answer": 1,
    "explain": "Guards (CanActivate) decide whether a request may proceed - authentication and authorization. Returning false yields a 403; the handler never runs."
  },
  {
    "q": "You want to log how long every request takes - measuring before the handler and after it returns. Which slot fits?",
    "choices": ["A pipe, because it runs before the handler", "Middleware, because it sees raw req/res", "An interceptor, because it wraps the handler before and after", "A guard, because it runs first"],
    "answer": 2,
    "explain": "Interceptors run code both before and after the handler via next.handle().pipe(...), which is exactly the shape needed for timing and logging."
  },
  {
    "q": "What is the correct order of the Nest request pipeline?",
    "choices": ["pipes → guards → middleware → handler", "guards → middleware → interceptors → handler", "middleware → guards → interceptors → pipes → handler", "middleware → pipes → guards → handler"],
    "answer": 2,
    "explain": "Nest runs middleware → guards → interceptors (pre) → pipes → handler → interceptors (post) → exception filters on error. Auth (guards) happens before input validation (pipes)."
  }
]
```


---

# Testing & Production

Back in [Phase 3](03-providers-and-di.md) there was a promise: dependency injection isn't ceremony for its own sake - it exists to make your code testable, and this is where that promise pays off. The same mechanism Nest uses to wire `TasksService` into `TasksController` in production is the mechanism that lets you, in a test, swap that real service for a fake one and check the controller's behavior in isolation.

Here's the mental model for the whole testing half of this phase: **a test builds its own tiny Nest app.** You hand `@nestjs/testing` a list of providers - some real, some fake - it spins up a DI container exactly like the real one, and you pull pieces out and poke at them. Because the wiring is identical to production, what passes in the test reflects how things behave for real. The only thing you change is *which* objects get injected.

## Unit testing: a real class with fake neighbors

A unit test isolates one class. For a service that has no dependencies, that means building a testing module with just the real class and reading it back out.

```typescript
import { Test } from '@nestjs/testing';
import { TasksService } from './tasks.service';

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

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

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

  it('creates a task', () => {
    const t = service.create({ title: 'x' });
    expect(t.id).toBeDefined();
  });
});
```

*What just happened:* `Test.createTestingModule({ providers: [TasksService] })` describes a miniature module - same shape as the `@Module()` decorator from [Phase 4](04-modules.md), just built in code. Calling `.compile()` actually constructs the DI container (it's async, so we `await` it inside `beforeEach`, which Jest runs before every test). Then `moduleRef.get(TasksService)` reaches into that container and hands back the real, fully-constructed instance - the same object Nest would build at runtime. From there it's a plain object: call `create`, assert on what comes back. No HTTP, no server, just the logic.

> 📝 Jest is the default test runner - the Nest CLI wires it up when you scaffold the project, so `npm test` runs your `*.spec.ts` files with zero setup. The `describe`/`it`/`expect`/`beforeEach` functions above are all Jest.

### Testing a controller with a mock service

A controller is harder to isolate because it *depends* on a service. You don't want the real `TasksService` in a controller test - it carries its own state and logic, and a bug there would fail the controller's test for the wrong reason. So you inject a fake. This is the swap [Phase 3](03-providers-and-di.md) was building toward.

```typescript
import { Test } from '@nestjs/testing';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';

describe('TasksController', () => {
  let controller: TasksController;

  const mockTasks = {
    findAll: () => [{ id: 1, title: 'seed', done: false }],
  };

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [TasksController],
      providers: [{ provide: TasksService, useValue: mockTasks }],
    }).compile();

    controller = moduleRef.get(TasksController);
  });

  it('returns the tasks the service gives it', () => {
    expect(controller.findAll()).toEqual([{ id: 1, title: 'seed', done: false }]);
  });
});
```

*What just happened:* The line that matters is `{ provide: TasksService, useValue: mockTasks }`. It tells the container: "when something asks for `TasksService`, hand it `mockTasks` instead." The controller's constructor still says `tasksService: TasksService` - it has no idea it got a fake, because, as Phase 3 put it, *it never knew where its dependency came from in the first place.* Now `controller.findAll()` exercises only the controller's own code (does it call the service and return the result?), with the service's behavior pinned to known canned data. That's a true unit test of the controller.

> 💡 This is the whole argument for DI in one example. Without it, the controller would `new TasksService()` internally and you'd have no seam to insert a fake. With it, the fake slides in cleanly. The deeper discipline of *what* to test and how to run it all in CI is its own topic - see [Testing in CI](/guides/testing-in-ci).

## e2e testing: boot the real app and make requests

Unit tests check pieces in isolation. **End-to-end (e2e) tests check the whole thing wired together** - routing, pipes, guards, the lot - by starting an actual HTTP server in memory and sending it real requests. Nest scaffolds a `test/` folder with `*.e2e-spec.ts` files and an `npm run test:e2e` script for exactly this.

The tool is **supertest**, which fires HTTP requests at your running app and lets you assert on the responses.

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

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

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

    app = moduleRef.createNestApplication();
    await app.init();
  });

  it('GET /tasks returns 200', () => {
    return request(app.getHttpServer()).get('/tasks').expect(200);
  });

  afterAll(async () => {
    await app.close();
  });
});
```

*What just happened:* This time the testing module imports the whole `AppModule`, so the real controllers and services are all present. `createNestApplication()` turns that container into an actual app, and `await app.init()` boots it (running the same startup it would in production). `app.getHttpServer()` exposes the underlying HTTP server, and `request(...).get('/tasks').expect(200)` sends a genuine GET and asserts the status code - supertest never opens a network port, it talks to the server object directly, which keeps it fast. `afterAll` closes the app so the test process can exit cleanly. Nothing is mocked here: a passing e2e test means the real request actually flowed through routing and into your code and came back right.

## Config: one place for environment values

Your production app needs a database URL, secrets, a port - values that differ between your laptop and the server, and that must never be hard-coded. The wrong way is to sprinkle `process.env.DATABASE_URL` across a dozen files. The right way in Nest is `@nestjs/config`.

You load it once in your root module:

```typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [ConfigModule.forRoot()],
})
export class AppModule {}
```

*What just happened:* `ConfigModule.forRoot()` reads a `.env` file and the real environment variables at startup and folds them into a single `ConfigService` that the DI container now knows how to inject. `forRoot()` is the convention for "configure this module once for the whole app" - you'll see the same pattern in database modules later.

Then anywhere you need a value, you inject `ConfigService` - the same constructor injection you already know:

```typescript
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class TasksService {
  constructor(private readonly config: ConfigService) {}

  private get dbUrl() {
    return this.config.get<string>('DATABASE_URL');
  }
}
```

*What just happened:* `config.get('DATABASE_URL')` pulls the value through the one service that owns config, instead of the service reaching out to the global `process.env` itself. That gives you a single, typed, injectable, *mockable* source of truth - in a test you can supply a fake `ConfigService` the same way you faked `TasksService` above.

> ⚠️ Don't scatter `process.env.SOMETHING` across your codebase. The moment a typo'd key or a missing variable causes a bug, you'll be hunting through every file that read it. Centralize on `ConfigModule` + `ConfigService` and there's exactly one place to look - and one place to validate that required values are actually present at boot.

## Going to production: ship the compiled build

In development you've been running `npm run start:dev`, which uses ts-node and a file watcher to recompile and restart as you save. That's wonderful for iterating and wrong for production - it carries the TypeScript toolchain, recompiles at runtime, and restarts on file changes you don't want in a live server.

📝 For production you compile *ahead of time* and run plain JavaScript. `npm run build` (which runs `nest build`) compiles your TypeScript into a `dist/` folder, and you run the output directly:

```bash
npm run build
NODE_ENV=production node dist/main.js
```

*What just happened:* `nest build` does the TypeScript-to-JavaScript compile once, writing `dist/`. Then `node dist/main.js` runs that compiled entry point - no ts-node, no watcher, no recompile. Setting `NODE_ENV=production` tells your app (and many libraries) to use production behavior, like trimming verbose logging.

A few things belong in your `main.ts` bootstrap before you ship - most you've already met:

```typescript
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import helmet from 'helmet';
import { AppModule } from './app.module';

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

  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  app.use(helmet());
  app.enableCors();
  app.enableShutdownHooks();

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
```

*What just happened:* The global `ValidationPipe` ([Phase 5](05-dtos-validation-pipes.md)) enforces your DTO rules on every incoming request app-wide. `helmet()` sets a batch of security-related HTTP headers. `enableCors()` controls which browsers' origins may call your API. `enableShutdownHooks()` is the production-specific one: it makes Nest listen for termination signals (like the `SIGTERM` a container sends when it's stopping) and run any cleanup - closing database connections, finishing in-flight work - before the process dies, so deploys don't drop requests or leak connections.

In a real deployment you'd run `node dist/main.js` inside a container (Docker), behind a reverse proxy (nginx, or your platform's load balancer) that terminates TLS and forwards traffic. That last mile - Dockerfile, env management, picking a host, wiring CI - is a guide of its own: [Ship Your Side Project](/guides/ship-your-side-project) walks the full path from working code to a public URL.

## Recap

- A test builds its **own DI container** with `Test.createTestingModule({...}).compile()`, then `moduleRef.get(X)` pulls instances out - the same wiring as production, so passing tests reflect real behavior.
- **Unit-test a controller** by providing `{ provide: TasksService, useValue: mockTasks }` - the DI swap from Phase 3, letting you isolate the controller from the real service. Jest is the default runner (`npm test`).
- **e2e tests** import `AppModule`, call `createNestApplication()` + `app.init()`, and hit `app.getHttpServer()` with **supertest** to check the whole request pipeline end to end (`npm run test:e2e`).
- Centralize configuration with `@nestjs/config`: `ConfigModule.forRoot()` once, then inject `ConfigService` and call `config.get(...)` - never scatter `process.env` across files.
- For production, `npm run build` compiles to `dist/` and you run `node dist/main.js` (not `start:dev`); enable the global `ValidationPipe`, `helmet`, CORS, `enableShutdownHooks()`, set `NODE_ENV=production`, and run behind a container/reverse proxy.

## Quick check

```quiz
[
  {
    "q": "In a controller unit test, why do you provide { provide: TasksService, useValue: mockTasks }?",
    "choices": ["To make the test run faster by skipping compilation", "To swap the real service for a fake so the controller is tested in isolation", "Because controllers cannot be tested with the real service at all", "To register a new route on the controller"],
    "answer": 1,
    "explain": "DI lets you substitute a fake service for the real one. The controller's constructor still asks for TasksService but receives the mock, so the test exercises only the controller's own logic against known data."
  },
  {
    "q": "What does an e2e test do that a unit test does not?",
    "choices": ["Runs without Jest", "Boots the whole app and sends real HTTP requests through the full pipeline via supertest", "Avoids using the DI container", "Only tests private methods"],
    "answer": 1,
    "explain": "An e2e test imports AppModule, calls createNestApplication() and app.init(), then uses supertest against app.getHttpServer() to exercise routing, pipes, guards, and handlers together - not one class in isolation."
  },
  {
    "q": "How should you run a NestJS app in production?",
    "choices": ["npm run start:dev, which uses ts-node and a watcher", "node dist/main.js after npm run build compiles TypeScript to dist/", "ts-node src/main.ts directly", "nest start --watch on the source files"],
    "answer": 1,
    "explain": "start:dev is for development (ts-node + file watcher). In production you compile ahead of time with npm run build (nest build) into dist/ and run the plain JavaScript with node dist/main.js."
  }
]
```


---

# Where to Go Next

Stop for a second and look at what you can actually do now. You can scaffold a Nest app with the CLI, write controllers that map HTTP to methods with route decorators, build `@Injectable()` providers that hold your logic and let the container hand them to you instead of `new`-ing them yourself, group everything into modules that import and export cleanly, validate request bodies with DTOs and the ValidationPipe, assemble a full CRUD resource as controller + service + DTOs, guard routes and shape the pipeline with interceptors and middleware, and test it all with Nest's DI-aware test module before shipping it to production. That's a structured, real REST API.

And here's the quieter win. Nest's decorators looked like magic in Phase 1, and now they don't. You can see the architecture underneath: **controllers handle HTTP, providers hold logic, dependency injection wires them, and modules group them.** Once that picture is in your head, a 200-file Nest codebase reads the same way a 5-file one does - same four roles, repeated. That's the whole point of an opinionated framework, and you now think in it.

This last phase isn't another resource - it's the map: where Nest sits among the other Node frameworks, the official ecosystem you'll add next, and one concrete thing to go build.

## NestJS vs the field

You learned [Express](/guides/express-from-zero) - or could've - and you've heard of [Fastify](/guides/fastify-from-zero). These tools aren't really fighting over the same spot. They're aimed at different sizes of problem and different tastes, and choosing on purpose beats choosing by reputation.

```mermaid
flowchart TD
  Start[Need a Node web service?] --> Size{How big and how structured?}
  Size -- Small service, assemble it myself --> Express[Express]
  Size -- Need raw speed + schemas --> Fastify[Fastify]
  Size -- Large app or team, want structure --> Nest[NestJS]
  Nest --> Runtime{Want that speed too?}
  Runtime -- Yes --> NestFast[Nest on Fastify]
```

A line on each:

- **Express** - minimal and everywhere. A thin layer over `node:http` that gives you routing and the middleware chain, then leaves the rest to you. The biggest ecosystem and the most likely thing you'll meet in a Node job. See [Express From Zero](/guides/express-from-zero).
- **Fastify** - built for speed and built around *schemas*. You declare a JSON schema for a route, and Fastify uses it for both validation and fast serialization. See [Fastify From Zero](/guides/fastify-from-zero).
- **NestJS** - opinionated and TypeScript-first. Dependency injection, modules, controllers, decorators - the architecture you spent this guide learning. More to set up, more guardrails once you're moving. (You're here.)

> 💡 How to pick plainly: Nest shines for **large, team-built apps** where the structure earns its keep - when ten people touch the same codebase, having one obvious place for everything is worth a lot. For a tiny single-purpose service, that same structure can feel like overhead, and bare Express or Fastify will get you there with less ceremony. The senior instinct isn't crowning a winner - it's asking "best for *this* job?" and answering truthfully. You can do that now.

> 📝 You don't always have to choose between Nest's structure and Fastify's speed. **Nest runs on Express by default**, but swap in `@nestjs/platform-fastify` and the very same controllers, providers, and modules run on top of Fastify instead - you keep Nest's architecture and pick up Fastify's throughput. The framework and the underlying HTTP engine are two separate decisions.

## The ecosystem you'll reach for

Nest ships the architecture, and a constellation of official `@nestjs/*` packages slot into it cleanly. You won't need all of these on day one, but you'll recognize the shape of each.

- **A real database, via an ORM.** Every API in this guide stored tasks in memory - gone the moment you restart. The first thing most Nest apps grow is persistence, and the official integrations are **TypeORM** (`@nestjs/typeorm`), **Prisma**, and **Mongoose** (`@nestjs/mongoose`) for MongoDB. They all do the same core job: turn rows (or documents) into objects and back. Understand the concept before you pick one - [How an ORM Works](/guides/how-an-orm-works).
- **API docs, free from your decorators.** **`@nestjs/swagger`** reads the DTOs and decorators you already wrote and generates an OpenAPI spec - interactive docs that stay in sync with your code instead of drifting from it.
- **Config.** **`@nestjs/config`** loads environment variables into a typed, injectable config service, so secrets and settings stop being scattered `process.env` reads.
- **Auth.** Wire login through **Passport** (`@nestjs/passport`) and, for token APIs, JWT - each strategy plugs into the exact **guards** you met in Phase 7. The guard checks the request and either lets it through or rejects it.
- **GraphQL.** Prefer a graph to REST? **`@nestjs/graphql`** offers a **code-first** approach: you write resolver classes and decorated types, and Nest generates the schema. The concept first: [GraphQL Explained](/guides/graphql-explained).
- **Beyond HTTP.** Nest has built-in transports for **microservices** (services talking over TCP, Redis, NATS, message queues) and **WebSockets** (real-time, push-style connections) - the same controllers-and-providers model, different doorway in.

## What to build

Reading more won't make this stick. Building one real thing will. So here's the assignment, and it's deliberately concrete.

Take the **tasks API** you grew across this guide and carry it all the way home:

- **Swap the in-memory store for a real database** through TypeORM or Prisma so tasks survive a restart. Because your logic already lives in a service the controller injects, this mostly changes the bottom layer - the provider - not the controller on top. ([How an ORM Works](/guides/how-an-orm-works) explains the concept first.)
- **Add JWT auth** with `@nestjs/passport` and a guard from Phase 7, so each request proves who it is and tasks belong to a user.
- **Generate docs** with `@nestjs/swagger` from the DTOs you already wrote, and open the interactive page to see your own API described back to you.
- **Deploy it** somewhere you can hit from your phone, the way Phase 8 showed.

And if a future service is genuinely small - one endpoint, no team, no growth in sight - practice the clear-eyed call from earlier in this phase: weigh whether Nest's structure earns its overhead, or whether [Express](/guides/express-from-zero) or [Fastify](/guides/fastify-from-zero) is the lighter, better fit. Knowing when *not* to reach for Nest is as senior as knowing how to use it.

If the tasks API feels too familiar, build something small and new end to end - a **notes API** or a **URL shortener**. Same muscles: modules, a controller, a service, DTOs and validation, a guard, tests, deploy.

The clear-eyed close is the same idea you've held since Phase 0. Controllers handle HTTP, providers hold logic, dependency injection wires them, and modules group them - that's the architecture that scales past where bare Express sprawls, and it's the architecture you now build with by reflex. Go give the tasks API a database, lock it behind auth, document it, deploy it, and show someone. You're ready.

## Recap

1. **You can ship a structured Nest API** - controllers, DI'd providers, modules, DTO validation, full CRUD, guards and interceptors, tested and deployed - and you understand *why* each piece exists.
2. **The four roles are the whole model** - controllers handle HTTP, providers hold logic, DI wires them, modules group them. A huge Nest codebase is that pattern repeated.
3. **Choose a framework on purpose** - Nest for structure on a large app or team, Express for minimal and ubiquitous, Fastify for speed plus schema-driven validation. None is "best" in the abstract.
4. **Nest and its HTTP engine are separate choices** - run Nest on Express by default, or on Fastify via `@nestjs/platform-fastify` to keep the structure and gain the speed.
5. **The official ecosystem fills the gaps** - TypeORM/Prisma/Mongoose for persistence, `@nestjs/swagger` for docs, `@nestjs/config`, Passport + JWT for auth, `@nestjs/graphql` for GraphQL, and built-in microservice and WebSocket transports.
6. **Build and finish one thing** - carry the tasks API to a database, JWT auth, Swagger docs, and a deploy; or weigh Nest against a lighter framework when the service is small.

## Quick check

Three decisions to take with you as you leave this guide:

```quiz
[
  {
    "q": "You're building a large API with a team of ten, and you also want Fastify's throughput. What's the move?",
    "choices": [
      "Abandon Nest and rewrite everything in raw Fastify",
      "Use NestJS for structure and swap in @nestjs/platform-fastify so the same controllers and providers run on Fastify",
      "Use Express, since it's the most popular",
      "You can't have both structure and speed"
    ],
    "answer": 1,
    "explain": "The framework and the HTTP engine are separate choices. Nest runs on Express by default, but @nestjs/platform-fastify lets the very same controllers, providers, and modules run on Fastify - you keep Nest's architecture and gain Fastify's speed."
  },
  {
    "q": "You're adding a real database to the tasks API. Because your logic lives in a service the controller injects, what mostly changes?",
    "choices": [
      "Every controller must be rewritten from scratch",
      "Mainly the provider (service) swaps its in-memory store for an ORM like TypeORM or Prisma; the controller on top stays roughly the same",
      "You must switch from Nest to Express",
      "Nothing - Nest persists data automatically"
    ],
    "answer": 1,
    "explain": "Dependency injection kept the HTTP layer separate from where data lives. The controller still routes, validates, calls the service, and responds; you change the service's bottom layer from an in-memory object to an ORM plus a database."
  },
  {
    "q": "Which official package generates interactive API documentation from the DTOs and decorators you already wrote?",
    "choices": [
      "@nestjs/config",
      "@nestjs/passport",
      "@nestjs/swagger",
      "@nestjs/graphql"
    ],
    "answer": 2,
    "explain": "@nestjs/swagger reads your existing decorators and DTOs to produce an OpenAPI spec and interactive docs that stay in sync with the code. @nestjs/config handles env vars, @nestjs/passport handles auth, and @nestjs/graphql adds a GraphQL layer."
  }
]
```
