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

@ntrg/simple-di

v1.0.2

Published

Lightweight dependency injection container for TypeScript

Readme

simple-di

A lightweight yet powerful Dependency Injection container for TypeScript and Node.js applications.

Supports:

  • Constructor injection
  • Decorators (@Injectable, @Inject, @Optional)
  • Singleton / Transient / Scoped lifetimes
  • Lazy injection
  • Factory providers
  • Value providers
  • Multi providers
  • Circular dependency detection
  • Zero framework lock-in

Features

  • Minimal and easy-to-understand API
  • Built with TypeScript
  • Uses reflect-metadata
  • Inspired by DI systems from ASP.NET Core, NestJS, and Angular
  • Works in Node.js, Express, React SSR, CLI apps, and more
  • Tiny and framework agnostic

Installation

npm install @ntrg/simple-di reflect-metadata

or

yarn add @ntrg/simple-di reflect-metadata

or

pnpm add @ntrg/simple-di reflect-metadata

Enable Decorators

tsconfig.json

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

Import Reflect Metadata

You must import reflect-metadata once at application startup.

import 'reflect-metadata';

Quick Start

import 'reflect-metadata';

import { Container, Injectable } from '@ntrg/simple-di';

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

@Injectable()
class UserService {
  constructor(private logger: Logger) {}

  getUsers() {
    this.logger.log('Fetching users...');
  }
}

const container = new Container();

container.register({
  provide: Logger,
  useClass: Logger,
});

container.register({
  provide: UserService,
  useClass: UserService,
});

const service = container.resolve(UserService);

service.getUsers();

Core Concepts

Providers

A provider describes how a dependency should be created.

Class Provider

container.register({
  provide: UserService,
  useClass: UserService,
});

Factory Provider

container.register({
  provide: 'API_URL',
  useFactory: () => {
    return process.env.API_URL;
  },
});

Value Provider

container.register({
  provide: 'APP_NAME',
  useValue: 'Simple DI',
});

Injection

Constructor Injection

@Injectable()
class Logger {}

@Injectable()
class UserService {
  constructor(private logger: Logger) {}
}

Custom Tokens

Useful for interfaces or primitive values.

const CONFIG = Symbol('CONFIG');

container.register({
  provide: CONFIG,
  useValue: {
    port: 3000,
  },
});

@Injectable()
class AppService {
  constructor(
    @Inject(CONFIG)
    private config: any,
  ) {}
}

String Tokens

container.register({
  provide: 'API_URL',
  useValue: 'https://api.example.com',
});

@Injectable()
class ApiService {
  constructor(
    @Inject('API_URL')
    private apiUrl: string,
  ) {}
}

Scopes

Singleton (Default)

Only one instance is created.

container.register({
  provide: Logger,
  useClass: Logger,
  scope: 'singleton',
});

Transient

Creates a new instance every resolution.

container.register({
  provide: Logger,
  useClass: Logger,
  scope: 'transient',
});

Scoped

Creates one instance per scope/container.

container.register({
  provide: RequestContext,
  useClass: RequestContext,
  scope: 'scoped',
});

Example:

const root = new Container();

const request1 = root.createScope();
const request2 = root.createScope();

const ctx1 = request1.resolve(RequestContext);
const ctx2 = request2.resolve(RequestContext);

console.log(ctx1 === ctx2); // false

Optional Dependencies

Use @Optional() when a dependency may not exist.

@Injectable()
class CacheService {}

@Injectable()
class UserService {
  constructor(
    @Optional()
    private cache?: CacheService,
  ) {}
}

Lazy Injection

Lazy injection helps avoid circular dependency issues and improves startup performance.

@Injectable()
class A {
  constructor(
    @Inject(() => B)
    private b: B,
  ) {}
}

@Injectable()
class B {
  constructor(private a: A) {}
}

Multi Providers

Register multiple providers under the same token.

const HOOKS = Symbol('HOOKS');

container.register({
  provide: HOOKS,
  useValue: 'hook-1',
  multi: true,
});

container.register({
  provide: HOOKS,
  useValue: 'hook-2',
  multi: true,
});

const hooks = container.resolve(HOOKS);

console.log(hooks);
// ['hook-1', 'hook-2']

Circular Dependency Detection

The container automatically detects circular dependencies.

@Injectable()
class A {
  constructor(private b: B) {}
}

@Injectable()
class B {
  constructor(private a: A) {}
}

Resolving A will throw an error like:

Circular dependency detected:
A -> B -> A

API Reference

Container

register(provider)

Registers a provider.

container.register({
  provide: Logger,
  useClass: Logger,
});

resolve(token)

Synchronously resolves a dependency.

const logger = container.resolve(Logger);

createScope()

Creates a child scoped container.

const scoped = container.createScope();

Decorators

@Injectable()

Marks a class as injectable.

@Injectable()
class UserService {}

@Inject(token)

Injects a custom token.

constructor(
  @Inject('API_URL')
  private url: string
) {}

@Optional()

Marks a dependency as optional.

constructor(
  @Optional()
  private cache?: CacheService
) {}

Types

type Scope = 'singleton' | 'transient' | 'scoped';
type Token<T = any> = Constructor<T> | symbol | string;

Example: Express.js

import express from 'express';
import 'reflect-metadata';

import { Container, Injectable } from '@ntrg/simple-di;

@Injectable()
class UserRepository {
  findAll() {
    return ['John', 'Jane'];
  }
}

@Injectable()
class UserController {
  constructor(private repo: UserRepository) {}

  getUsers(req, res) {
    res.json(this.repo.findAll());
  }
}

const container = new Container();

container.register({
  provide: UserRepository,
  useClass: UserRepository,
});

container.register({
  provide: UserController,
  useClass: UserController,
});

const app = express();

app.get('/users', (req, res) => {
  const controller = container.resolve(UserController);
  controller.getUsers(req, res);
});

app.listen(3000);

Best Practices

Use Symbols for Shared Tokens

export const DATABASE = Symbol('DATABASE');

Keep Business Logic Framework Independent

Dependency Injection helps separate business logic from infrastructure.


Prefer Constructor Injection

Constructor injection makes dependencies explicit and easier to test.


Testing Example

class FakeLogger {
  log() {}
}

const container = new Container();

container.register({
  provide: Logger,
  useClass: FakeLogger,
});

container.register({
  provide: UserService,
  useClass: UserService,
});

const service = container.resolve(UserService);

Roadmap

Planned features:

  • Async factories
  • Property injection
  • Module system
  • Lifecycle hooks
  • Auto registration
  • Request pipeline support
  • Devtools integration

Requirements

  • Node.js >= 16
  • TypeScript >= 6

License

MIT


Contributing

Contributions, issues, and feature requests are welcome.

If you find a bug or have an idea for improvement, feel free to open an issue or submit a pull request.


Author

Built with TypeScript for developers who want a clean and simple DI experience.