跪拜 Guibai
← All articles
Backend · NestJS

NestJS Under the Hood: Modules, Decorators, and DI as First-Class Architecture

By YIAN ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

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.

Summary

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.

Takeaways
A NestJS application is assembled from modules — each `@Module()` declares its own controllers, providers, and imported dependencies.
Controllers (`@Controller`, `@Get`, `@Post`) handle HTTP routing and request validation but contain no business logic.
Services (`@Injectable`) hold all business logic, database operations, and third-party calls; the controller merely delegates to them.
TypeScript's `private readonly` on constructor parameters auto-declares and assigns an instance property, eliminating manual boilerplate.
Decorators in NestJS are functions that attach metadata; the framework scans this metadata at startup to register routes, assemble modules, and resolve dependencies.
Dependency injection means developers never manually instantiate services — the DI container reads constructor type signatures and supplies the correct instances.
Removing a decorator removes the corresponding framework behavior, making the architecture explicit and convention-driven.
Conclusions

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.

Concepts & terms
Decorator Pattern in NestJS
A design pattern where functions (`@Module`, `@Controller`, `@Get`) attach metadata to classes and methods without modifying their source. NestJS scans this metadata at startup to automatically register routes, assemble modules, and wire dependencies.
Dependency Injection (DI) Container
A framework-managed registry that creates, supplies, and destroys object instances. In NestJS, declaring a type in a constructor tells the container which service to inject; the developer never calls `new` on a provider.
TypeScript Parameter Properties
A syntactic shorthand where adding `private`, `protected`, or `public` to a constructor parameter auto-declares a class property and assigns the argument to it, replacing explicit declaration and manual assignment.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗