A small, type-safe DI library optimized for Hono.js.
Important
This package is optimized for Hono.js and is not designed for large projects. If you require advanced DI features such as automatic circular injection, dynamic binding, and multi-binding, etc. you may need a dedicated DI library.
# npm
npm install hono-simple-di
# pnpm
pnpm add hono-simple-di
# bun
bun add hono-simple-di
First, you define a service that you want to inject. This could be any class or function that handles your business logic.
// services/UserService.ts
export class UserService {
findOne(id: number) {
return { id, name: `User ${id}` };
}
}
Next, you create a dependency for your service, specifying how it should be initialized. You can also choose whether it should be a singleton (default) or multi-instance (per request).
import { Dependency } from "hono-simple-di";
import { UserService } from "./services/UserService";
// Define the dependency for UserService
const userServiceDep = new Dependency(() => new UserService());
Use the middleware method to inject the dependency into your Hono.js context. Once injected, the service will be accessible through the context's c.get method.
import { Hono } from "hono";
import { userServiceDep } from "./dependencies";
const app = new Hono()
// Use the dependency as middleware
.use(userServiceDep.middleware("userService"))
.get("/", (c) => {
// Retrieve the injected service
const { userService } = c.var;
// or const userService = c.get('userService')
const user = userService.findOne(1);
return c.json(user);
});
You can override the service instance at runtime using the injection method. This is useful in testing or when dynamically providing service instances.
// Inject a custom service instance
userServiceDep.injection({
findOne(id: number) {
return { id, name: "Injected User" };
},
});
const postServiceDep = new Dependency(
async (c) => new PostService(await userServiceDep.resolve(c)),
);
For example, using headers from c.req.headers
.
const uaDep = new Dependency(
(c) => new UAParser(c.req.header("User-Agent") ?? ""),
{
scope: "request",
},
);
const app = new Hono()
.use(uaDep.middleware("ua"))
.get("/", (c) => {
const ua = c.get("ua");
return c.text(`You are running on ${ua.getOS().name}!`);
});
If you need a new instance of the service for each request (multi-instance), set the scope option to request
.
const requestIdDep = new Dependency((c) => Math.random(), {
scope: "request",
});
const app = new Hono()
// Inject a unique ID for each request
.use(requestIdDep.middleware("requestId"))
.get("/id", (c) => {
const requestId = c.get("requestId");
return c.text(`Request ID: ${requestId}`);
});
const userServiceDep = new Dependency<UserService | null>(() => null);
interface Dependency<Service> {
constructor(
/** A function to initialize the service. */
private serviceInitializer: (c: Context) => MaybePromise<Service>,
private opts?: {
/**
* The scope of the dependency.
* @default 'default'
* @remarks
* - 'default': Service will be initialized only once.
* - 'request': Service is initialized once per request and reused across requests.
*/
scope?: Scope
},
): Dependency
/**
* Injects a service instance directly. Useful for overriding the default service.
* @param service - The service instance to be injected.
* @returns this - The instance of the dependency for chaining.
*/
injection(service: Service): this
/**
* Clear injected service.
*/
clearInjected(): this {
this.service = undefined;
return this;
}
/**
* Creates a middleware that injects the service into the context.
* @param contextKey - Optionally override the key used to store the service in the context.
* @returns MiddlewareHandler - A Hono.js middleware function.
*/
middleware<ContextKey extends string>(
/** The key used to store the service in the context. */
contextKey?: ContextKey,
): MiddlewareHandler<{
Variables: {
[key in ContextKey]: Service
}
}>
}