Initializers
If you are reading the docs for the first time, think about an initializer like this:
an initializer is startup code that discovers decorated controller methods and connects them to a real runtime system.
Examples:
- an HTTP initializer registers controller methods as Fastify routes
- a queue initializer registers methods as BullMQ workers
- a RabbitMQ initializer registers methods as consumers
- an event initializer subscribes methods to an event emitter
- a cron initializer schedules methods as jobs
So the job of an initializer is simple: take a decorated method and wire it into some external runtime.
NOTE
Initializers are singleton features and they run only for controllers. They execute during startup, so await app.init() must be called for them to run.
Full examples:
Decorators
Before talking about initializers, it helps to be precise about decorators.
In awilixify, the purpose of a decorator is only to add metadata. It does not register routes, start workers, subscribe to events, or schedule jobs. It only marks a method with information that some initializer may use later.
You can create your own decorator like this:
import { createDecoratorStateUpdater } from "awilixify";
type OnEventMethodState = {
event?: string;
};
const updater = createDecoratorStateUpdater("on-event", {
// function which creates initial state for decorated method
method: (): OnEventMethodState => ({}),
});
export const ON_EVENT_TOKEN = updater.token;
export function onEvent(event: string) {
return (_target: any, context: ClassMethodDecoratorContext) => {
updater.update(context, {
method: (previous) => ({
...previous,
event,
}),
});
};
}This decorator does not subscribe anything by itself. It only stores { event } metadata on the method.
That is the key mental model:
- decorators describe
- initializers wire
This is also why awilixify does not need a dedicated integration package for every technology it interacts with.
How Decorators And Initializers Work Together
- A decorator writes metadata onto a class or method.
- An initializer declares which decorator token it understands.
- During module startup, awilixify finds decorated controller methods.
- The initializer receives the method metadata and wires native runtime behavior.
So the decorator says what something is, and the initializer decides how that becomes real behavior.
HTTP Initializer Example
This is the HTTP initializer from the Fastify example:
import {
type Initializer,
InitializerContext,
resolveDecoratorState,
isResultLike,
} from "awilixify";
import {
rollUpHttpDecoratorState,
HTTP_DECORATOR_STATE_TOKEN,
} from "awilixify/http";
import type { FastifyReply, FastifyRequest } from "fastify";
import type { Deps } from "./http.module.js";
// that's awilixify internal token for http decorators.
// Created in same way like in example above with
// "createDecoratorStateUpdater"
type HttpToken = typeof HTTP_DECORATOR_STATE_TOKEN;
export class FastifyHttpInitializer implements Initializer<HttpToken> {
public readonly token = HTTP_DECORATOR_STATE_TOKEN;
constructor(private readonly app: Deps['app']) {}
// runs for every decorated method
initialize(context: InitializerContext<HttpToken>) {
const methodState = rollUpHttpDecoratorState(
context.decoratorState.root,
// decorated data by token a method has
context.metadata,
);
for (const verb of methodState.verbs) {
for (const path of methodState.paths) {
this.app.route({
method: verb,
url: path,
handler: async (req: unknown, res: unknown) =>
// invoke of actual decorated method
context.invoke(req, res),
preHandler: methodState.beforeMiddleware,
schema: methodState.schema,
});
}
}
}
}The important part is not Fastify itself. The important part is that the initializer reads decorator metadata and then uses the native Fastify API, app.route(...), to register the route exactly how you want.
That same pattern is what makes the event emitter, queue, RabbitMQ, and cron examples possible.
Why This Matters
Initializers let you avoid framework-specific integration packages while still keeping a clean, decorator-driven API.
Compared to a system that needs dedicated integration packages for each transport or framework:
- the abstraction stays small
- the runtime wiring remains explicit
- you keep direct access to the native underlying API
- you can customize behavior without fighting framework adapters
Modules And Reuse
An initializer is registered in a module, just like other module features.
It can also be exported from that module and then imported into another module. That means you can package behavior once and reuse it across the app.
For example:
- an HTTP module can export its HTTP initializer
- a queue module can export its queue-job initializer
- an event module can export its event-listener initializer
Then another module can import that module and get the behavior without re-declaring the initializer locally.
This makes initializers a good way to ship reusable integration behavior at module boundaries, not just local startup code.
import { createModule, type ModuleDef } from "awilixify";
class HttpInitializer {}
type HttpModuleDef = ModuleDef<{
initializers: {
http: HttpInitializer;
};
exportInitializerKeys: ["http"];
}>;
export const HttpModule = createModule<HttpModuleDef>({
name: "HttpModule",
initializers: {
http: HttpInitializer,
},
initializerExports: ["http"],
});
type AppModuleDef = ModuleDef<{
imports: [typeof HttpModule];
}>;
export const AppModule = createModule<AppModuleDef>({
name: "AppModule",
imports: [HttpModule],
});When To Reach For Initializers
Use an initializer when you need to register or connect something at startup:
- map decorators to framework routes
- subscribe handlers to an event bus
- register queue workers
- register message consumers
- schedule jobs
If the integration is about wiring a method into some external runtime, an initializer is usually the right tool.
For call-time method wrapping inside providers, see Interceptors.
