Core Concepts Questions
NestJS interviews almost always start with the fundamentals: how the framework is structured, what each building block does, and why Nest sits on top of Express (or Fastify) rather than replacing it. These questions test whether you understand Nest as an opinionated, modular framework built around dependency injection — not just a router. The answers below are written the way you would explain them out loud, then backed with the exact code an interviewer expects to see.
What is NestJS and how does it differ from Express?
Express is a minimal, unopinionated HTTP library: you wire up routes, middleware, and structure entirely yourself. NestJS is a framework that uses Express (or Fastify) as its underlying HTTP engine but adds an opinionated architecture on top — modules, dependency injection, decorators, and a clear request lifecycle. The key talking point is that Nest gives you structure and testability out of the box, while Express gives you freedom and leaves organization to you.
| Aspect | Express | NestJS |
|---|---|---|
| Architecture | Unopinionated, ad hoc | Modular, enforced by @Module |
| Dependency management | Manual require/instantiation | Built-in IoC / DI container |
| Language | JS-first | TypeScript-first |
| HTTP layer | Itself | Adapter over Express or Fastify |
| Testing | Roll your own | First-class DI-based mocking |
A strong answer notes that Nest does not hide Express — you can still reach the raw
request/responseobjects and reuse any Express middleware. Nest is a layer of organization, not a reinvention of the HTTP stack.
What are the core building blocks of a Nest application?
Interviewers want the three primary concepts named clearly: modules, controllers, and providers. Modules organize the app into cohesive features; controllers handle incoming requests and shape responses; providers (typically services) hold the business logic and are injected wherever needed. Everything is glued together by the IoC container, which Nest bootstraps at startup.
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
What is a module and why is the AppModule special?
A module is a class annotated with @Module() that groups related controllers and providers into a single feature unit. Every Nest app has at least one — the root module (AppModule) — which Nest uses as the starting point to build the application graph: the internal map of modules and their relationships used to resolve dependencies. Feature modules are then imported into the root (directly or transitively), keeping the codebase organized by domain.
The decorator metadata has four properties worth knowing by name:
| Property | Purpose |
|---|---|
controllers | Controllers instantiated by this module |
providers | Providers managed by Nest’s injector here |
imports | Other modules whose exported providers are needed |
exports | Providers this module makes available to importers |
A common follow-up: providers are encapsulated to their module by default. To share a service, the owning module must list it in exports, and the consuming module must imports that module.
What is a controller’s responsibility?
A controller maps an HTTP method and path to a handler. Its job is purely routing and request/response shaping — it should delegate all real work to injected services. The handler return value is serialized automatically, so you rarely touch the raw response.
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll() {
return this.catsService.findAll();
}
@Post()
create(@Body() dto: CreateCatDto) {
return this.catsService.create(dto);
}
}
Output:
GET /cats -> 200 OK [{"id":1,"name":"Whiskers"}]
POST /cats -> 201 Created
What is a provider, and what does @Injectable do?
A provider is anything Nest can inject — most often a service, but also repositories, factories, or helpers. The @Injectable() decorator marks a class as a candidate for the IoC container so Nest can instantiate it and supply it through constructors. Without registering the provider in a module’s providers array, Nest cannot resolve it.
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
return cat;
}
findAll(): Cat[] {
return this.cats;
}
}
A useful detail: providers are singletons by default — one instance is shared across the entire application. You can change this with provider scope (REQUEST or TRANSIENT) when per-request state is required.
How does a request flow through a Nest application?
Even at the “core concepts” level, interviewers expect a rough ordering of the request lifecycle. A request passes through middleware, then guards, then interceptors (pre), then pipes, reaches the controller handler, flows back through interceptors (post), and any thrown error is caught by exception filters.
Request
-> Middleware
-> Guards
-> Interceptors (before)
-> Pipes
-> Controller handler (calls providers)
-> Interceptors (after)
-> Exception filters (on error)
Response
Best Practices
- Organize the app into feature modules; keep
AppModulethin and let itimportsdomain modules. - Keep controllers as a thin transport layer — inject services and delegate all logic to providers.
- Mark every injectable class with
@Injectable()and register it in exactly one module’sproviders. - Export only the providers other modules genuinely need; rely on module encapsulation for the rest.
- Prefer constructor injection over manual instantiation so dependencies stay mockable in tests.
- Return plain values from handlers and let Nest serialize them rather than reaching for raw
@Res().