What is NestJS?
NestJS is a framework for building server-side Node.js applications in TypeScript. It runs on top of Express by default (or Fastify) and adds structure: modules, controllers, providers and a dependency injection container.
Express leaves the architecture to you; Nest decides it: HTTP code in controllers, business logic in injectable services, and auth, validation and logging in guards, pipes and interceptors. The design borrows from Angular.
Use it when several people work on one backend and you want every module to look the same.
What is the difference between NestJS and Express?
Express is a minimal HTTP library; NestJS is a full framework that uses Express (or Fastify) underneath.
Express NestJS Structure Up to you Modules, controllers, providers Language JavaScript or TypeScript TypeScript first Dependency injection None built in Built in container Validation, auth Middleware you pick Pipes and guards Overhead Lowest A thin layer over the adapter app.use()still accepts Express middleware and@Req()gives you the Express request, so Nest is a layer on Express, not a replacement.What are decorators in NestJS and how do they work?
Decorators are functions attached to classes, methods, properties or parameters with
@. In Nest they store metadata that the framework reads at startup:@Controller('users')marks a class as a controller with a path,@Get(':id')maps a method to a route,@Injectable()marks a provider,@Body()tells Nest what to pass as an argument.They do nothing by themselves at runtime; Nest scans the metadata (through
reflect-metadata) when the app boots and wires routes and dependencies from it.Nest uses TypeScript's legacy decorators, so
tsconfig.jsonneedsexperimentalDecoratorsandemitDecoratorMetadata. The newer standard decorators have no parameter decorators, which Nest relies on. More in TypeScript decorators.What does the Nest CLI do, and which commands should you know?
The Nest CLI (
@nestjs/cli) creates projects, generates code with the conventional file layout, and builds and runs the app.npm i -g @nestjs/cli nest new shop # new project nest g resource orders # module, controller, service, DTOs, tests nest g module users # or controller, service, guard, pipe... nest start --watch # run with reload nest build # compile to dist/nest galso adds the new class to the right module's metadata, which is the part people forget when they create files by hand. In NestJS 12,nest newasks whether the project should be CommonJS or ES modules, andnest upgrademoves every@nestjs/*package to the matching major version.What does a new NestJS project contain?
nest newcreates a root module, one controller, one service and the bootstrap file:src/main.ts: creates the app withNestFactory.create(AppModule)and callslisten.src/app.module.ts: the root module that imports every feature module.src/app.controller.tsandapp.service.ts: an example route and the service it calls.*.spec.tsfiles: unit tests next to the code, andtest/for end-to-end tests.
As the app grows you add a folder per feature (
users/,orders/), each with its own module, controller, service and DTOs. Group by feature, not by type: acontrollers/folder holding every controller becomes hard to navigate.How do you read route params, query strings and the request body in a controller?
Use parameter decorators:
@Param()for path segments,@Query()for the query string,@Body()for the parsed JSON body, and@Headers()for headers. Each takes an optional key (@Param('id')) or returns the whole object without one, like Express'sreq.params,req.queryandreq.body.NestJSimport { Body, Controller, Get, HttpCode, Module, Param, Post, Query } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; // GET /tasks/2?verbose=true @Controller('tasks') class TasksController { private tasks = [{ id: 1, title: 'Learn modules' }, { id: 2, title: 'Learn DI' }]; @Get() all() { return this.tasks; } @Get(':id') one(@Param('id') id: string, @Query('verbose') verbose: string) { const task = this.tasks.find((t) => t.id === Number(id)); return { task, idType: typeof id, verbose }; } @Post() add(@Body() body: { title: string }) { const task = { id: this.tasks.length + 1, title: body.title }; this.tasks.push(task); return task; } @Post('reset') @HttpCode(204) reset() { this.tasks = []; } } @Module({ controllers: [TasksController] }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();The first request returns
"idType":"string": params and query values are always strings until a pipe converts them. Now sendPOST /taskswith the body{"title": "Ship it"}: it answers201with the new task, andPOST /tasks/resetanswers204with no body.What status code does a NestJS route return by default, and how do you change it?
200for every method exceptPOST, which returns201. Change it with@HttpCode(204)on the method, or throw an exception for errors (throw new NotFoundException()gives404).Returning a value lets Nest serialize it: objects and arrays become JSON, strings are sent as they are, and a returned promise or Observable is awaited first.
If you inject the response with
@Res(), Nest stops sending the response for that route and you must callres.json()yourself. Use@Res({ passthrough: true })when you only want to set a cookie or a header and still return the value normally.What happens in
main.tswhen a NestJS app starts?NestFactory.create(AppModule)builds the module graph, creates every provider and controller in dependency order, and returns an app.app.listen(3000)then runsapp.init()(lifecycle hooks such asonModuleInit) and starts the HTTP server.async function bootstrap() { const app = await NestFactory.create(AppModule); app.setGlobalPrefix('api'); app.enableCors({ origin: 'https://shop.example.com' }); app.enableShutdownHooks(); await app.listen(process.env.PORT ?? 3000); } bootstrap();Global settings (prefix, CORS, global pipes, versioning) go between
createandlisten. A failed dependency resolution throws insidecreate, so a wiring mistake stops the process before any request is served.Can NestJS run on Fastify instead of Express?
Yes. Install
@nestjs/platform-fastifyand pass an adapter:import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; const app = await NestFactory.create<NestFastifyApplication>( AppModule, new FastifyAdapter(), ); await app.listen(3000, '0.0.0.0');Controllers, guards, pipes and interceptors stay the same. What changes is anything that touches the raw request: Express middleware packages,
@Req()and@Res()objects, and file uploads (multer is Express only). Fastify has lower framework overhead, which shows in benchmarks of tiny handlers; in an app that waits on a database the difference is usually small. Note that Fastify listens onlocalhostby default, so pass'0.0.0.0'inside a container.Why does NestJS need
emitDecoratorMetadataandreflect-metadata?They are how Nest knows what to inject without being told. With
emitDecoratorMetadata(a tsconfig option), the TypeScript compiler records each decorated class's constructor parameter types asdesign:paramtypesmetadata, andreflect-metadatalets Nest read it at runtime.@Injectable() export class OrdersService { constructor(private readonly users: UsersService) {} } // compiled output carries: design:paramtypes = [UsersService]Consequences interviewers ask about: an
interfaceor atypeleaves nothing at runtime, so it cannot be an injection token (use@Inject(TOKEN)). And a build tool that skips decorator metadata (esbuild, which Vitest uses by default) breaks injection, which is why Nest projects build withtscor SWC.What changed in NestJS 11 and NestJS 12?
NestJS 11 moved the default HTTP platform to Express 5: route wildcards must be named (
/users/{*path}instead of/users/*), and Node 20 became the minimum.NestJS 12 changes how the packages ship:
- Every core package is an ES module. CommonJS apps keep working because Node 20.19+ and 22.12+ can
require()ES modules, which is why those are the minimum versions. @Body(),@Query()and@Param()take a{ schema }option for Standard Schema libraries (Zod, Valibot, ArkType), validated byStandardSchemaValidationPipe.- New ESM projects get Vitest; CommonJS projects keep Jest.
- 12.1 added
app.useSecurityHeaders()andapp.enableCsrfProtection().
Run
nest upgrade --dry-runto see what a migration touches.- Every core package is an ES module. CommonJS apps keep working because Node 20.19+ and 22.12+ can
NestJS Basics Interview Questions
What NestJS is, how it relates to Express, decorators, the CLI and a first controller.
11 questions