@talosjs/container package is the dependency injection (DI) engine of the framework. It wraps Inversify behind a small, typed API and exposes a single shared container singleton. Almost every artifact you build (controllers, services, middleware, and repositories) is registered into this container and resolved with its dependencies supplied through the constructor. You rarely call the container directly. The framework’s decorators register decorated classes for you, and @inject wires the graph together.
What the container gives you
One globalcontainer instance backs the whole app. A service resolved in a controller and the same service resolved in middleware share their registration, and, for singletons, their instance. You declare what a class needs with @inject(Token) in its constructor, and the container constructs the dependency tree instead of you new-ing collaborators by hand.
Each registration picks a lifetime. Singleton, Transient, or Request decide whether an instance is shared, recreated on every resolution, or recreated per request. Most of the time you never reach for container.add at all: the framework decorators (@decorator.service(), @decorator.middleware(), @decorator.repository(), and controller routing) register the class as a side effect of being applied. Resolution returns typed instances through get<T> and typed values through getConstant<T>, and failures throw a ContainerException.
How it works
The package keeps one internal Inversify container (sharedDI) that every Container instance delegates to. When a class is registered, the container marks it injectable, binds it to itself, and applies the chosen scope. When you resolve it, Inversify reads the @inject metadata on its constructor and builds the dependency graph.
The shared instance is exported for direct import:
The Container class
Container implements IContainer and forwards to the shared Inversify instance, so creating a new Container() and using the exported container operate on the same registrations. Use the exported singleton in application code.
Scopes
A scope decides how long a resolved instance lives. Pass anEContainerScope value as the second argument to add (or as the argument to a framework decorator). The default is Singleton.
Constructor injection with @inject
Declare dependencies as constructor parameters and annotate each with @inject(Token), where the token is the class (or a string alias). The container resolves and supplies them when it builds the class. inject is re-exported from @talosjs/container, so import it from there.
A service injected into a controller. The controller is constructed by the container during routing, so the service arrives resolved:
Aliases and constants
Beyond classes, the container can resolve values by a string or symbol identifier. This is how the framework exposes shared infrastructure under stable names. Repositories, for example, inject the active database via the"database" alias rather than a concrete class:
addConstant / getConstant. Constants hold any value (strings, objects, or resolved instances), and get<T> / getConstant<T> return them typed:
symbol identifier when you need a collision-proof token; use a string alias when readability and cross-package convention (like "database" or "mailer") matter more.
Auto-registration by the framework
You almost never callcontainer.add yourself. Each framework decorator registers its class into the shared container as a side effect of being applied, using the scope you pass (default singleton). After that, resolving the class, or injecting it elsewhere, needs nothing more from you.
injectable helper accepts a scope, so @decorator.service(EContainerScope.Transient) and similar control lifetime without ever touching the container API directly.
Error handling
When a binding is missing or a dependency cannot be constructed, resolution throws aContainerException carrying a message and an error code (SERVICE_RESOLVE_FAILED for get, CONSTANT_RESOLVE_FAILED for getConstant). Catch it to distinguish DI failures from other errors:
container.has(MyClass) to confirm registration while debugging.
Working with the container effectively
Pull collaborators through the constructor with@inject so they are resolved, scoped, and testable. Don’t instantiate them by hand inside methods. In nearly every case the decorators do the registering for you, so prefer @decorator.service(), @decorator.middleware(), and @decorator.repository() and reach for container.add only for values the framework does not register.
Most services are stateless, so leave them as singletons. Choose Transient or Request only when a fresh or per-request instance is genuinely required. Depend on a class or a stable alias such as "database" rather than reaching into another module’s globals; that keeps the dependency graph explicit. Call get<T> and getConstant<T> with the expected type so the compiler checks how you use the result. And when resolution can fail, wrap it and check instanceof ContainerException to tell DI errors apart from application errors.
Related
- Services: the unit of business logic resolved through the container.
- Repositories: data-access classes that inject the database via DI.
- Middleware: pipeline classes constructed with injected dependencies.
- Controllers: route handlers the container builds with their services.