Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/maou-shonen/hono-simple-di

A small, type-safe DI library optimized for hono.js.
https://github.com/maou-shonen/hono-simple-di

dependency-injection hono honojs inversion-of-control ioc typescript

Last synced: 12 days ago
JSON representation

A small, type-safe DI library optimized for hono.js.

Awesome Lists containing this project

README

        

# Hono simple DI

[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![bundle][bundle-src]][bundle-href]
[![Codecov][codecov-src]][codecov-href]
[![License][license-src]][license-href]

A small, type-safe DI library optimized for Hono.js.

> [!IMPORTANT]
> This package is optimized for [Hono.js](https://github.com/honojs/hono) 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.

## Installation

```bash
# npm
npm install hono-simple-di
# pnpm
pnpm add hono-simple-di
# bun
bun add hono-simple-di
```

## Usage

### Basic usage

#### 1. Define a service

First, you define a service that you want to inject. This could be any class or function that handles your business logic.

```ts
// services/UserService.ts
export class UserService {
findOne(id: number) {
return { id, name: `User ${id}` };
}
}
```

#### 2. Create a Dependency

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).

```ts
import { Dependency } from "hono-simple-di";
import { UserService } from "./services/UserService";

// Define the dependency for UserService
const userServiceDep = new Dependency(() => new UserService());
```

#### 3. Inject dependency via middleware

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.

```ts
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);
});
```

#### 4. Override Service with injection

You can override the service instance at runtime using the injection method. This is useful in testing or when dynamically providing service instances.

```ts
// Inject a custom service instance
userServiceDep.injection({
findOne(id: number) {
return { id, name: "Injected User" };
},
});
```

---

### Reference another dependency

```ts
const postServiceDep = new Dependency(
async (c) => new PostService(await userServiceDep.resolve(c)),
);
```

---

### A service can also be something other than a class

For example, using headers from `c.req.headers`.

```ts
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}!`);
});
```

---

### Using request scope service

If you need a new instance of the service for each request (multi-instance), set the scope option to `request`.

```ts
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}`);
});
```

---

### Do not provide an initialization function

```ts
const userServiceDep = new Dependency(() => null);
```

## API

### `Dependency` Interface

```ts
interface Dependency {
constructor(
/** A function to initialize the service. */
private serviceInitializer: (c: Context) => MaybePromise,
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(
/** The key used to store the service in the context. */
contextKey?: ContextKey,
): MiddlewareHandler<{
Variables: {
[key in ContextKey]: Service
}
}>
}
```

[npm-version-src]: https://img.shields.io/npm/v/hono-simple-di
[npm-version-href]: https://npmjs.com/package/hono-simple-di
[npm-downloads-src]: https://img.shields.io/npm/dm/hono-simple-di
[npm-downloads-href]: https://npmjs.com/package/hono-simple-di
[codecov-src]: https://img.shields.io/codecov/c/gh/maou-shonen/hono-simple-di/main
[codecov-href]: https://codecov.io/gh/maou-shonen/hono-simple-di
[bundle-src]: https://img.shields.io/bundlephobia/minzip/hono-simple-di
[bundle-href]: https://bundlephobia.com/result?p=hono-simple-di
[license-src]: https://img.shields.io/github/license/maou-shonen/hono-simple-di.svg
[license-href]: https://github.com/maou-shonen/hono-simple-di/blob/main/LICENSE