Fastify Adapter
@inferdi/fastify is a Fastify v5 plugin. In scoped mode it exposes the root as app.di, creates one request scope in onRequest, exposes it as request.di, and disposes it in onResponse.
Install
bash
pnpm add @inferdi/inferdi @inferdi/fastify fastifyts
import Fastify, { type FastifyRequest } from 'fastify'
import { Container } from '@inferdi/inferdi'
import { inferdiFastify } from '@inferdi/fastify'Request Scope
Publish your concrete container types with module augmentation:
ts
type RequestContext = {
requestId: string
ip: string
}
class Users {
constructor(readonly request: RequestContext) {}
profile(id: string) {
return { id, requestId: this.request.requestId }
}
}
const root = new Container()
.declareScopeInputs<{ request: RequestContext }>()
.registerClass('users', Users, ['request'], 'scoped')
const app = Fastify()
type RootContainer = typeof root
const openRequestScope = (request: FastifyRequest) => root.createScope({
request: {
requestId: request.id,
ip: request.ip
}
})
type RequestContainer = ReturnType<typeof openRequestScope>
declare module 'fastify' {
interface FastifyInstance {
di: RootContainer
}
interface FastifyRequest {
di: RequestContainer
}
}
await app.register(inferdiFastify, {
container: root,
createScope: (_root, request) => openRequestScope(request),
disposeRootOnClose: true
})
app.get('/users/:id', async (request) => {
const { id } = request.params as { id: string }
return request.di.get('users').profile(id)
})Fastify's app.register cannot infer the plugin generics deeply enough for inline hooks, so annotate hook parameters when you need concrete scope types.
Options
| Option | Default | Description |
|---|---|---|
container | required | Root container exposed as app.di. |
scopePerRequest | true | Set false for root-only mode. |
createScope | root.createScope() | Custom request scope creation. May be async. |
setupScope | none | Runs after creation and before exposure. May be async. |
disposeScope | scope.dispose() | Custom disposal. May be sync or async. |
autoDispose | true | false or predicate false transfers ownership to application code. |
disposeRootOnClose | false | Dispose root during fastify.close(). |
onDisposeError | request.log.error | Sink for request-scope disposal failures. |
Root-Only Mode
Use root-only mode when handlers only need singleton services:
ts
await app.register(inferdiFastify, {
container: root,
scopePerRequest: false,
})
app.get('/health', async function () {
return this.di.get('health').check()
})Root-only mode installs no request decoration and no request lifecycle hooks.
Lifecycle Notes
request.diis exposed only after setup succeeds.- Setup failure disposes the half-built scope and surfaces only the original setup error.
- Cleanup hooks observe
request.diwhile they run. - A failed request ignores
skipInferdiDisposeand still disposes, subject toautoDispose. - Client abort cleanup runs in
onRequestAbortafter a scope has been exposed. - Root disposal errors propagate through
fastify.close()only whendisposeRootOnCloseis enabled.
With autoDispose: false or a predicate returning false, request.di remains available after response or abort cleanup so application code can dispose the retained scope.
