NestJS Under the Hood: Modules, Decorators, and DI as First-Class Architecture
NestJS brings Angular-style modularity and dependency injection to Node backends, replacing the ad-hoc wiring common in Express and Koa with a compiler-enforced structure. For teams scaling beyond a handful of endpoints, this reduces the accidental complexity that accumulates when routing, business logic, and service instantiation are manually stitched together.
NestJS structures every application as a tree of modules, each declaring its own controllers, services, and imported dependencies through the `@Module` decorator. Controllers handle HTTP routing and parameter validation but delegate all business logic to services, keeping the request layer thin. Services contain the actual domain logic, database calls, and third-party integrations, making them reusable and testable.
This separation is enforced by decorators that act as machine-readable metadata. `@Controller` and its HTTP-method variants (`@Get`, `@Post`) register routes; `@Injectable` services are instantiated by the framework's dependency injection container, which reads constructor type signatures and supplies the correct instances. TypeScript's parameter property syntax (`private readonly`) further reduces boilerplate by auto-declaring and assigning constructor-injected fields.
The result is a convention-over-configuration architecture where deleting a decorator removes the corresponding functionality, and modules can be composed, imported, and tested in isolation — a pattern that scales from simple APIs to distributed microservice clusters.
NestJS's decorator system effectively turns classes into passive declarations — the framework reads them as configuration, not as imperative code, which is why deleting a decorator silently disables a route or a module.
The three-layer split (Module/Controller/Service) is not just organizational; it's enforced by the DI container and metadata scanner, making it harder to accidentally mix concerns compared to unopinionated frameworks where discipline is voluntary.
TypeScript's parameter property syntax is doing double duty here: it reduces boilerplate while also serving as the type hint that Nest's DI container uses to resolve the correct service — a tight coupling between language feature and framework behavior.