Dependency injection is the mechanism that makes Hexagonal Architecture testable. Without it, use cases would construct their own repositories, controllers would reach directly for ORM clients, and the domain would become tangled with infrastructure. Forge enforces constructor injection as the universal pattern — no service locators, noDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ronaldjdev/forge/llms.txt
Use this file to discover all available pages before exploring further.
container.resolve() inside business logic, no singleton globals. The specific container (or lack of one) varies by technology profile, but the principle is always the same: dependencies flow in through the constructor, declared as interfaces, with concrete implementations wired at the composition root. Forge auto-selects the strategy that fits your detected profile so cast, temper, and inspect all generate and validate the correct annotations for your stack.
tsyringe DI
tsyringe is the DI container used by Express and mixed Fastify profiles. It uses TypeScript decorators (@injectable(), @inject()) on classes and a per-feature di.ts file as the composition root for that slice.
Profiles using tsyringe: express-mongodb, express-prisma, express-drizzle
Token Pattern
Each feature defines its ownTOKENS symbol map and registers its classes with the tsyringe container in a di.ts file:
Class Annotations
Use cases, controllers, and repositories all carry@injectable() and constructor-parameter @inject() annotations:
Mongoose Special Case (R12b)
When using Mongoose with tsyringe, register models using{ useValue: model(...) } — not registerSingleton. Using registerSingleton with model() is a CRITICAL violation (R12b) because Mongoose model registration is not idempotent and will throw on repeated calls.
NestJS Built-In DI
NestJS profiles use the framework’s native dependency injection system. No external container is needed — NestJS manages the module tree and injects providers automatically. Profiles using NestJS DI:nestjs-mongodb, nestjs-postgres, nestjs-prisma
Module Structure
Each feature declares its providers in a NestJS@Module. The module exports providers that other modules may need to consume:
Class Annotations
Use@Injectable() from @nestjs/common on all providers, and @Inject() for constructor parameter injection when needed:
Mongoose Integration (NestJS)
Fornestjs-mongodb, Mongoose schemas are registered using MongooseModule.forFeature() inside a persistence sub-module:
Manual Constructor Injection
Some profiles — particularly smaller Express and Fastify stacks — use no DI container at all. Dependencies are passed explicitly through constructors, wired at the composition root (the routes file or the application entry point). Profiles using manual injection:express-postgres, fastify-mongodb, fastify-postgres, fastify-prisma
Wiring at the Routes Level
The routes file instantiates the full dependency chain and passes it to the controller:Class Definitions (No Decorators)
Classes are plain TypeScript without DI decorators — the dependency is simply a constructor parameter:Manual injection is perfectly valid for small-to-medium projects (under ~10 features). Forge’s
temper command will not add tsyringe decorators to a manual-injection profile — it applies the wiring pattern appropriate for your detected profile.Hardening DI with forge temper
The temper command applies the DI pattern prescribed by the active profile across all feature files in the project:
- tsyringe profiles: adds
@injectable()to classes missing it, adds@inject(TOKEN)to constructor parameters, and registers missing tokens indi.ts - NestJS profiles: ensures
@Injectable()is present on all providers and validates@Moduleprovider arrays - Manual profiles: verifies that constructors accept interfaces (not concrete classes) and that wiring happens at the composition root