Backend with NestJS
Backend with NestJS

Nest vocabulary: the words before the wiring

Lesson · ~25 min
After this unit you can
  • Explain what a controller, service, provider and module each do, and put a business rule in the service
  • Explain that decorators run once at startup, and write a route with @Controller, @Get, @Param, @Body
  • Choose the HTTP verb for an operation and explain why path and query values arrive as strings
  • Read constructor(private readonly x: T) as an injected field and explain why provider fields are shared
  • Tell a guard (yes/no gate) from an interceptor (wraps the call) and reach the request via switchToHttp()

Read

Simple
1 / 3

A whole Nest app in three files

Flag

A Nest backend is a program that waits for HTTP requests and answers them. A request is just a message: GET /users/42 means "give me the user with id 42". (This track is written against Nest 12, the current major.)

Everything else is Nest giving you a fixed place to put your code. Three files, three jobs:

// users.service.ts — the WORK. Plain class. Knows nothing about HTTP.
@Injectable()
export class UsersService {
  findOne(id: string) {
    return { id, name: "Ada" };
  }
}

// users.controller.ts — the DOOR. Maps a URL to a method call.
@Controller("users")            // every route in this class starts with /users
export class UsersController {
  constructor(private readonly users: UsersService) {}

  @Get(":id")                   // GET /users/42
  findOne(@Param("id") id: string) {
    return this.users.findOne(id);   // hand the work to the service
  }
}

// users.module.ts — the LIST. Tells Nest these two classes exist.
@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

Say each one out loud once and the rest of Nest gets much smaller:

  • A controller is the door: it receives requests and returns a response. Its job is translation — read the URL and body, call something, hand back the result.
  • A service is the work: business rules, database calls, calculations. No HTTP words in it.
  • A module is the list: "here are the classes in this feature." Nest reads it at startup to know what exists.
  • Provider is the general word for anything the module can hand to someone else. A service is the most common kind of provider. So: every service is a provider; not every provider is a service (a config object or a database connection can be one too).

Why the split? Because the work outlives the door. The same UsersService can be called by an HTTP controller today and by a scheduled job or a queue consumer tomorrow, and it's testable without pretending to be a web server.

Medium
1 / 2

constructor(private readonly x) — the line that confuses everyone

Flag

Half of "I don't understand Nest" is really "I don't understand this one TypeScript line".

export class UsersController {
  constructor(private readonly users: UsersService) {}

  findAll() {
    return this.users.findAll();
  }
}

Take it apart:

  • constructor — the method that runs when the object is created. Its parameters are what the class needs in order to exist.
  • private readonly users: UsersService — a parameter property. This one line does three things that you'd otherwise write out longhand:
// Longhand: exactly the same class.
export class UsersController {
  private readonly users: UsersService;      // 1. declare a field on the object
  constructor(users: UsersService) {         // 2. take it as an argument
    this.users = users;                      // 3. store it on the object
  }
}
  • private — "only code inside this class may touch this.users." It's a compile-time rule; TypeScript erases it, so nothing stops a caller at runtime. Use it to say intent.
  • readonly — you can't reassign this.users later. Same deal: compile-time only.
  • this — the object the method was called on. Inside findAll(), this is that controller instance, so this.users is the service that was handed to it. A plain local variable would vanish when the constructor returned; a field on this survives for the life of the object.
  • A method is just a function that lives on a class and can use this. findAll() above is a method; the arrow function you pass to .map() is not.

Why "declared, not constructed" matters

Notice what the constructor does not do: it never says new UsersService(). It only declares "I need a UsersService". Something else — Nest's injector — decides which object to pass in.

That's dependency injection, and its whole payoff shows up in tests:

// No Nest, no HTTP, no database.
const controller = new UsersController({ findAll: () => [{ id: "1" }] } as UsersService);
expect(controller.findAll()).toHaveLength(1);

If the controller had built its own service with new, you couldn't swap it — you'd be forced to boot the real thing (and its database) just to test one if.

Complex

The cast of characters around your handler

Flag

Your controller method is called the handler. Nest lets you put reusable code around every handler instead of copying it into each one. Those pieces have names, and the names are the whole problem — each is just a class with one method.

NameOne-line jobThe method you write
MiddlewareAnything framework-level, before Nest is involveduse(req, res, next)
GuardMay this request continue? Yes/nocanActivate(context) → boolean | Promise<boolean> | Observable<boolean>
InterceptorWrap the call — do something before and afterintercept(context, next)
PipeCheck/convert one argumenttransform(value, metadata)
Exception filterTurn a thrown error into a response for the current transportcatch(error, host)

They run in a fixed order around the handler:

middleware → guard → interceptor(before) → pipe → HANDLER → interceptor(after)
                                    ↘ (anything thrown) ↘ filter

The filter isn't a step everyone passes through — it's the branch taken when something throws. A request that succeeds never touches it.

Middleware, and the "consumer"

Middleware is the Express-style (req, res, next) function — it runs before Nest's own layers and knows nothing about which handler will eventually run. You attach it by implementing configure on the module, and the object you're handed is the MiddlewareConsumer. Odd name, boring meaning: it's the little builder API for saying which routes get this middleware.

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(RequestIdMiddleware).forRoutes("*");   // "consume" this middleware, for every route
  }
}

Guard, and what "context" means

A guard answers one question: should this request be allowed to proceed? Return true and it continues; return false and Nest responds 403. It may also return a promise or an Observable of that boolean, which is what lets a guard hit a database or verify a token before deciding.

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const expected = process.env.API_KEY;
    if (!expected) return false;                 // no key configured -> fail closed, not open

    const request = context.switchToHttp().getRequest();
    return request.headers["x-api-key"] === expected;
  }
}

That first line matters: without it, an unset API_KEY makes undefined === undefined true and the guard waves everyone through. A comparison-based check should always confirm it has something to compare against.

The context is Nest's envelope around "the thing currently being handled". It's not HTTP-specific, because the same guard could be protecting a WebSocket message or a queue message — so before you can touch request, you have to say which world you're in: context.switchToHttp() (or switchToWs(), switchToRpc()). It also carries getHandler() / getClass(), which is how a guard reads decorators you put on the route, like @Roles("admin").

The other three, briefly

  • An interceptor sees both sides of the handler, so it's where timing, logging, caching and "wrap every response in { data }" live.
  • A pipe runs on a single argument on its way in — "42" → 42, or reject the body as a 400 when it doesn't match the DTO.
  • An exception filter is the last stop for a failed request: whatever was thrown anywhere in that chain becomes a status code and a response body here (JSON by default — a filter can render or redirect instead).

You don't need to be able to write these yet. You need to be able to read a codebase and say "that's a guard, so it's about permission" — the units after this one build each one properly.

Quiz

You're adding "a customer can't have more than 3 open orders". Which file?

Flag

What's the relationship between a "service" and a "provider"?

Flag

When does the @Get(":id") decorator actually run?

Flag

@Controller("users") on the class, @Post(":id/orders") on the method. What URL does that answer?

Flag

A client wants to change only an order's note field, leaving everything else alone. Which verb?

Flag

@Param("id") id: string gets /users/42. What's in id, and why does the type say string?

Flag

What does constructor(private readonly users: UsersService) {} do, beyond taking an argument?

Flag

A service sets this.currentUser = user at the top of a method, awaits a DB call, then reads this.currentUser. What's wrong?

Flag

In plain terms, what separates a guard from an interceptor?

Flag

Why does a guard write context.switchToHttp().getRequest() instead of just taking req?

Flag

Fill in the blank

Mark this class as something the injector may create and hand out.

Flag
@()
export class UsersService {
  findAll() { return []; }
}

Answer GET /users/42: the class prefix, the verb decorator with a route parameter, and the decorator that reads it.

Flag
@("users")
export class UsersController {
  @(":id")
  findOne(@("id") id: string) {
    return { id };
  }
}

Create an order from the request's JSON body.

Flag
@()
create(@() dto: CreateOrderDto) {
  return this.orders.create(dto);
}

Give this controller the service in one line — declared, never constructed.

Flag
export class UsersController {
  constructor( readonly users: UsersService) {}

  findAll() {
    return .users.findAll();
  }
}

Register the controller and the service for this feature.

Flag
@Module({
  : [UsersController],
  : [UsersService],
})
export class UsersModule {}

Reach the HTTP request from inside a guard (this one admits read requests only).

Flag
canActivate(context: ExecutionContext): boolean {
  const request = context.().getRequest();
  return request.method === "GET";
}

Further reading