Service Container
The Service Container is a powerful tool for managing class dependencies and performing dependency injection — matching the role of Laravel’s service container. The built-in implementation (BuiltinContainer) resolves constructor dependencies automatically using TypeScript’s design:paramtypes reflection metadata. Enable it in tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
And import reflect-metadata once at your entry point:
import "reflect-metadata"
Binding
Singleton
Resolved once; the same instance is returned on every subsequent make().
container.singleton(UserRepository, UserRepository)
Transient
A new instance is created on every make().
container.bind(RequestHandler, RequestHandler)
Constant value
Registers an existing value directly. Use this for configuration objects, third-party instances, or anything already constructed.
container.instance(ApiConfig, { url: "https://api.example.com", timeout: 5000 })
Fluent binding
The fluent API gives you more control over scope and factory behaviour:
// Bind to a class with explicit scope
container.bind(MyService).to(MyServiceImpl).inSingletonScope()
container.bind(MyService).to(MyServiceImpl).inTransientScope()
// Bind to a constant value
container.bind("config.url").toConstantValue("https://api.example.com")
// Bind to a factory that receives the container
container.bind(MyService).to((container) => {
const config = container.make(ApiConfig)
return new MyService(config.url)
})
Resolving
const service = container.make(UserRepository)
// Alias
const service = container.get(UserRepository)
Identifiers
Bindings can be keyed by class, string, or symbol:
container.singleton(UserRepository, UserRepository) // class key
container.instance("api.url", "https://api.example.com") // string key
container.instance(Symbol("db"), connection) // symbol key
Auto-wiring
When you bind a class, the container reads its constructor parameter types from metadata and resolves each one automatically:
class ApiClient {
constructor(protected config: ApiConfig, protected logger: Logger) {}
}
container.singleton(ApiConfig, ApiConfig)
container.singleton(Logger, Logger)
container.singleton(ApiClient, ApiClient)
// ApiConfig and Logger are injected automatically
const client = container.make(ApiClient)
Manual injection tokens
When a constructor parameter is typed as an interface or primitive, metadata can’t infer the token. Use @inject() to specify it explicitly:
import { inject } from "@raubjo/architect-core"
class ApiClient {
constructor(
@inject("api.url") protected url: string,
protected logger: Logger,
) {}
}
Checking bindings
container.bound(UserRepository) // true / false
container.has("api.url") // alias for bound()