Node.js makes it easy to start a server and hard to keep one organised. A few routes become a few dozen, shared logic drifts into copy pasted handlers, and six months later nobody is sure where a feature lives. NestJS is a framework that gives a Node backend a deliberate shape from the first file, borrowing ideas from Angular and from enterprise frameworks in other languages.
The shape has three parts, and each has one job. Controllers receive HTTP requests and return responses, and they should do nothing else. Services hold the business logic, such as talking to a database or applying a rule. Modules group controllers and services that belong together, so a feature can be understood and tested as one unit. When you want to find where something happens, you already know which kind of file to open.
Here is the smallest complete example: a controller that answers a GET request by asking a service for a greeting.
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
The decorators do the wiring. @Controller marks the class as a request handler, @get marks the method as the answer to a GET request, and @Injectable marks the service as something NestJS can create and hand to whoever needs it. The controller never builds the service itself; NestJS injects it through the constructor, which is the dependency injection pattern. That one habit is what makes NestJS code testable: in a test, you can hand the controller a fake service and check its behaviour without starting a server or touching a database.
NestJS also standardises the pieces every real backend needs. Guards handle authentication, pipes validate incoming data, interceptors transform responses, and exception filters turn errors into consistent responses. Because these are framework concepts rather than one off middleware, a new developer joining the project meets the same patterns in every module.
TypeScript is not optional decoration here. Types on controllers and services catch mismatched shapes at build time, and the decorators only make sense with the type metadata TypeScript provides. If you dislike TypeScript, NestJS will feel like friction from day one.
The honest trade
NestJS trades freedom for consistency. You write more files and more boilerplate than you would with a minimal framework like Express, and the decorator heavy style can feel like magic until you learn what each piece does. For a small API with three endpoints, that structure is overhead. For a backend that will grow to dozens of endpoints, several developers, and years of maintenance, the structure is exactly what keeps the project readable. The wrong time to adopt NestJS is a weekend prototype; the right time is the moment a prototype starts turning into a product.
Structure you can grow into
Start with one module, one controller, and one service, exactly like the example above, and add a second module only when a second feature arrives. If the shape still feels helpful at five modules, it will still feel helpful at fifty.
Originally published at https://practicaldevblog.blogspot.com/2026/11/nestjs-explained-node-backends-with.html
Top comments (0)