npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stevanfreeborn/hono-netdi

v1.0.0

Published

A dependency injection container for Hono.js, inspired by .NET's DI system.

Readme

hono-netdi

pull_request codecov publish semantic-release: angular NPM License NPM Version NPM Downloads

A powerful dependency injection middleware for Hono.js, inspired by .NET's dependency injection system. This library seamlessly integrates @stevanfreeborn/netdi with Hono applications, providing automatic service scope management and clean resource disposal for each HTTP request.

Features

  • 🔄 Automatic Service Scope Management - Creates and disposes service scopes per request
  • 🧹 Resource Cleanup - Ensures proper disposal of scoped services to prevent memory leaks
  • 🏗️ Type-Safe DI - Full TypeScript support with strongly-typed service resolution
  • 🚀 Hono Integration - Seamless integration with Hono's middleware system
  • 📦 Lightweight - Minimal overhead with clean, simple API
  • 🔧 Flexible - Supports all service lifetimes (singleton, scoped, transient)

Installation

npm install @stevanfreeborn/hono-netdi hono
yarn add @stevanfreeborn/hono-netdi hono
pnpm add @stevanfreeborn/hono-netdi hono

Quick Start

import { Hono } from 'hono';
import {
  ServiceCollection,
  createServiceIdentifier,
  injectable,
  injectServices,
  useService,
} from '@stevanfreeborn/hono-netdi';

// Define your service interface and implementation
interface IUserService {
  getUser(id: string): Promise<{ id: string; name: string }>;
}

@injectable()
class UserService implements IUserService {
  async getUser(id: string) {
    return { id, name: `User ${id}` };
  }
}

// Create service identifier
const IUserService = createServiceIdentifier<IUserService>();

// Configure dependency injection
const services = new ServiceCollection();
services.addScoped(IUserService, UserService);
const serviceProvider = services.build();

// Create Hono app with DI middleware
const app = new Hono();
app.use(injectServices(serviceProvider));

// Use services in your routes
app.get('/users/:id', async c => {
  const userService = useService(c, IUserService);
  const user = await userService.getUser(c.req.param('id'));
  return c.json(user);
});

export default app;

API Reference

injectServices(serviceProvider: IServiceProvider): MiddlewareHandler

Creates a Hono middleware that manages dependency injection service scopes for each request.

Parameters:

  • serviceProvider - The root service provider from which to create scoped instances

Returns:

  • A Hono middleware handler that manages service scope lifecycle

Behavior:

  • Creates a new service scope at the beginning of each request
  • Stores the scope in the Hono context for access by route handlers
  • Automatically disposes the scope when the request completes
  • Ensures proper cleanup even if errors occur during request processing

useService<T>(c: Context, serviceType: ServiceIdentifier<T>): T

Retrieves a service instance from the current request's dependency injection scope.

Parameters:

  • c - The Hono context containing the service scope
  • serviceType - The service identifier used to resolve the service instance

Returns:

  • The resolved service instance of type T

Throws:

  • Error - When service scope is not found in context (middleware not configured)
  • Error - When service scope is invalid
  • Error - When the requested service cannot be resolved

Required Decorators

This library works with netdi's decorator system. You must use the appropriate decorators:

@injectable()

Mark all service classes with the @injectable() decorator:

import { injectable } from '@stevanfreeborn/hono-netdi';

@injectable()
class UserService implements IUserService {
  async getUser(id: string): Promise<User> {
    // Implementation
  }
}

@inject()

Use @inject() for constructor parameters that should be injected:

import { injectable, inject } from '@stevanfreeborn/hono-netdi';

@injectable()
class UserService implements IUserService {
  constructor(
    @inject(IUserRepository) private userRepository: IUserRepository,
    @inject(ILogger) private logger: ILogger,
  ) {}
}

Note: Without these decorators, netdi cannot properly resolve dependencies and will throw runtime errors.

Note: The argument passed to the @inject() decorator should be the service identifier created with createServiceIdentifier() that was used to register the service.

Advanced Usage

Service Lifetimes

The library supports all service lifetimes provided by netdi:

import { ServiceCollection, createServiceIdentifier, injectable } from '@stevanfreeborn/hono-netdi';

@injectable()
class ConfigService {
  getConnectionString(): string {
    return process.env.DB_CONNECTION_STRING || '';
  }
}

@injectable()
class UserService {
  getUsers(): Promise<User[]> {
    // Implementation here
    return Promise.resolve([]);
  }
}

@injectable()
class Logger {
  log(message: string): void {
    console.log(message);
  }
}

const IConfigService = createServiceIdentifier<ConfigService>();
const IUserService = createServiceIdentifier<UserService>();
const ILogger = createServiceIdentifier<Logger>();

const services = new ServiceCollection();

// Singleton - One instance for the entire application
services.addSingleton(IConfigService, ConfigService);

// Scoped - One instance per request scope
services.addScoped(IUserService, UserService);

// Transient - New instance every time it's requested
services.addTransient(ILogger, Logger);

Service Dependencies

Services can depend on other services through constructor injection using the @inject() decorator:

import { injectable, inject, createServiceIdentifier } from '@stevanfreeborn/hono-netdi';

interface ILogger {
  log(message: string): void;
}

interface IUserRepository {
  findById(id: string): Promise<User>;
}

interface IUserService {
  getUser(id: string): Promise<User>;
}

const ILogger = createServiceIdentifier<ILogger>();
const IUserRepository = createServiceIdentifier<IUserRepository>();
const IUserService = createServiceIdentifier<IUserService>();

@injectable()
class Logger implements ILogger {
  log(message: string) {
    console.log(`[${new Date().toISOString()}] ${message}`);
  }
}

@injectable()
class UserRepository implements IUserRepository {
  constructor(@inject(ILogger) private logger: ILogger) {}

  async findById(id: string): Promise<User> {
    this.logger.log(`Finding user with id: ${id}`);
    // Database logic here
    return { id, name: `User ${id}` };
  }
}

@injectable()
class UserService implements IUserService {
  constructor(
    @inject(IUserRepository) private userRepository: IUserRepository,
    @inject(ILogger) private logger: ILogger,
  ) {}

  async getUser(id: string): Promise<User> {
    this.logger.log(`Getting user with id: ${id}`);
    return await this.userRepository.findById(id);
  }
}

const services = new ServiceCollection();
services.addSingleton(ILogger, Logger);
services.addScoped(IUserRepository, UserRepository);
services.addScoped(IUserService, UserService);

Multiple Service Implementations

Register multiple implementations of the same interface:

import { injectable, createServiceIdentifier } from '@stevanfreeborn/hono-netdi';

interface INotificationService {
  send(message: string): Promise<void>;
}

@injectable()
class EmailNotificationService implements INotificationService {
  async send(message: string) {
    console.log(`Email: ${message}`);
  }
}

@injectable()
class SmsNotificationService implements INotificationService {
  async send(message: string) {
    console.log(`SMS: ${message}`);
  }
}

const EmailNotification = createServiceIdentifier<INotificationService>();
const SmsNotification = createServiceIdentifier<INotificationService>();

services.addScoped(EmailNotification, EmailNotificationService);
services.addScoped(SmsNotification, SmsNotificationService);

// Use in routes
app.post('/notify', async c => {
  const emailService = useService(c, EmailNotification);
  const smsService = useService(c, SmsNotification);

  await emailService.send('Hello via email!');
  await smsService.send('Hello via SMS!');

  return c.json({ success: true });
});

Factory Registration

Register services using factory functions for complex initialization:

import { injectable, inject, createServiceIdentifier } from '@stevanfreeborn/hono-netdi';

interface IDatabaseConfig {
  connectionString: string;
  timeout: number;
}

interface IDatabase {
  query(sql: string): Promise<any[]>;
}

@injectable()
class Database implements IDatabase {
  constructor(@inject(IDatabaseConfig) private config: IDatabaseConfig) {}

  async query(sql: string): Promise<any[]> {
    // Database query implementation
    return [];
  }
}

const IDatabaseConfig = createServiceIdentifier<IDatabaseConfig>();
const IDatabase = createServiceIdentifier<IDatabase>();

services.addSingleton<IDatabaseConfig>(IDatabaseConfig, () => ({
  connectionString: process.env.DB_CONNECTION_STRING!,
  timeout: 30000,
}));

services.addScoped(IDatabase, provider => {
  const config = provider.getService(IDatabaseConfig);
  return new Database(config);
});

Error Handling

The middleware automatically handles service scope disposal even when errors occur:

app.get('/error-example', async c => {
  const userService = useService(c, IUserService);

  try {
    // This might throw an error
    const user = await userService.getUser('invalid-id');
    return c.json(user);
  } catch (error) {
    // Service scope will still be properly disposed
    return c.json({ error: 'User not found' }, 404);
  }
});

Custom Middleware Order

The injectServices middleware should be registered early in your middleware chain:

const app = new Hono();

// Register DI middleware first
app.use(injectServices(serviceProvider));

// Then other middleware
app.use(cors());
app.use(logger());

// Routes can now use services
app.get('/', c => {
  const service = useService(c, IMyService);
  return c.json(service.getData());
});

Best Practices

Service Interface Design

Define clear interfaces for your services:

// ✅ Good - Clear interface with specific methods
interface IUserService {
  getUser(id: string): Promise<User>;
  createUser(data: CreateUserRequest): Promise<User>;
  updateUser(id: string, data: UpdateUserRequest): Promise<User>;
  deleteUser(id: string): Promise<void>;
}

// ❌ Avoid - Vague or overly broad interfaces
interface IService {
  doSomething(data: any): any;
}

Use Decorators Properly

Always use @injectable() on service classes and @inject() for dependencies:

import { injectable, inject, createServiceIdentifier } from '@stevanfreeborn/netdi';

const ILogger = createServiceIdentifier<ILogger>();

@injectable()
class UserService {
  constructor(@inject(ILogger) private logger: ILogger) {}

  async getUser(id: string): Promise<User> {
    this.logger.log(`Getting user ${id}`);
    // Implementation
  }
}

Service Lifetime Selection

Choose appropriate service lifetimes:

// Singleton - For stateless services, configuration, caches
services.addSingleton(IConfigService, ConfigService);
services.addSingleton(ILogger, Logger);

// Scoped - For services that maintain state per request
services.addScoped(IUserService, UserService);
services.addScoped(IDatabaseContext, DatabaseContext);

// Transient - For lightweight, stateless services
services.addTransient(IValidator, Validator);
services.addTransient(IMapper, Mapper);

Dependency Management

Keep dependencies minimal and well-defined:

// ✅ Good - Clear, minimal dependencies
@injectable()
class UserService implements IUserService {
  constructor(
    @inject(IUserRepository) private userRepository: IUserRepository,
    @inject(ILogger) private logger: ILogger,
  ) {}
}

// ❌ Avoid - Too many dependencies (consider refactoring)
@injectable()
class UserService implements IUserService {
  constructor(
    @inject(IRepo1) private repo1: IRepo1,
    @inject(IRepo2) private repo2: IRepo2,
    @inject(IService1) private service1: IService1,
    @inject(IService2) private service2: IService2,
    @inject(IService3) private service3: IService3,
    // ... too many dependencies
  ) {}
}

Troubleshooting

Service scope not found error

Solution: Ensure injectServices middleware is registered before routes that use useService:

// ✅ Correct order
app.use(injectServices(serviceProvider));
app.get('/', c => useService(c, IMyService));

// ❌ Wrong order
app.get('/', c => useService(c, IMyService));
app.use(injectServices(serviceProvider));

Service not registered error

Solution: Ensure the service is registered in your service collection:

const services = new ServiceCollection();
services.addScoped(IMyService, MyService); // Register the service
const serviceProvider = services.build();

Decorator errors

Solution: Ensure your tsconfig.json has decorators enabled:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

Related Projects

  • @stevanfreeborn/netdi - The core dependency injection container
  • Hono.js - The fast, lightweight web framework this middleware is designed for

Support

If you encounter any issues or have questions:

  1. Check the troubleshooting section above
  2. Search existing GitHub issues
  3. Create a new issue with a minimal reproduction case