@nwire/container — alone
Typed dependency injection. Awilix under the hood with a small, focused surface that doesn't leak the wrapper. No decorators, no reflect-metadata, no class metadata fishing.
What it does
createContainer<TCradle>()returns a typedContainer<TCradle>.container.register(name, value)registers a binding — function values becomeasFunction(fn).singleton(), plain values becomeasValue(v).container.resolve<T>(name)returns the value by name.container.cradle.<name>exposes the same value with autocomplete via theTCradlegeneric.container.createScope()returns a child container for per-request scopes.container.list()enumerates every registration withkind: "singleton" | "transient" | "scoped".
Install
pnpm add @nwire/containerMinimal example
import { createContainer } from "@nwire/container/awilix"
interface Logger { info(msg: string): void }
interface Db { query(sql: string): Promise<unknown[]> }
interface Cradle {
logger: Logger
db: Db
}
const container = createContainer<Cradle>()
container.register("logger", console)
container.register("db", () => ({ query: async () => [] }))
const log = container.cradle.logger
const db = container.cradle.db
log.info("ready")
await db.query("select * from users")Typed resolve
const repo = container.resolve<UserRepo>("userRepo")container.cradle is the preferred form when you've supplied a TCradle generic — it gives compile-time autocomplete. container.resolve() is the right call when the name is dynamic.
Per-request scope
http.use((req, res, next) => {
const scope = container.createScope()
scope.register("requestId", req.headers["x-request-id"] ?? crypto.randomUUID())
req.scope = scope
next()
})
// Inside a handler:
const id = req.scope.resolve<string>("requestId")Scopes inherit from the parent and add their own bindings. Disposers registered on a scope fire when the scope is closed, not when the parent shuts down.
What this is not
- It's not a service locator — pass the container or its bindings, don't
import { container } from "./container"everywhere. - It's not an IoC framework with auto-wiring — you declare bindings explicitly. The reward is no
reflect-metadata, no decorators, no class-metadata edge cases. - It's not opinionated about lifetimes — function bindings default to singleton (cached after first resolve); use Awilix's full API via
container.raw.register({...})if you need scoped or pure transient.
When to use this and not the @nwire/app plugin form
The definePlugin(...) form in @nwire/app wraps provide + boot + shutdown + on together so a single closure packages binding + lifecycle. If your dependencies are static (created once, never replaced, no boot work, no shutdown work), @nwire/container alone is enough. The moment you need lifecycle, reach for @nwire/app.