L2 — + Container (DI)
L1 + @nwire/container for typed dependency injection. Use it the moment two handlers share a dependency.
What you add
bash
pnpm add @nwire/containerThe shape
ts
import { httpInterface, endpoint } from "@nwire/http"
import { Container, token } from "@nwire/container"
import { z } from "zod"
// Typed tokens — the only thing handlers depend on.
const Logger = token<{ info(msg: string): void }>("Logger")
const Db = token<{ query(sql: string): Promise<unknown[]> }>("Db")
const container = new Container()
container.provideValue(Logger, console)
container.provideFactory(Db, () => ({ query: async () => [] }))
const api = httpInterface({ container })
api.get("/users", async ({ resolve }) => {
const log = resolve(Logger)
const db = resolve(Db)
log.info("fetching users")
return db.query("select * from users")
})
await endpoint("hello", { port: 3000 }).serve(api).run()What's new vs L1
Containerholds bindings.token<T>(name)ties a type to a runtime identifier.- Handlers get
resolve(Token)— type-safe, noany, no string keys. provideValue/provideFactory/provideClasscovers the common shapes.- Bindings can be scoped (
transient/singleton/scopedper-request).
When to stay at L2
- Your dependencies are static — created once at boot, never replaced.
- You don't need lifecycle hooks beyond shutdown.
When to climb to L3
When a dependency needs a boot() (open a DB pool, fetch JWKS, warm a cache) or a shutdown() (close connections in reverse order), or when you want the same lifecycle hook to fire across many features, you want @nwire/app plugins.
Next
- L3 — + App — plugin lifecycle
- Reference: @nwire/container alone