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

launchpad-dependency-injection

v1.1.0

Published

A lightweight, type-safe dependency injection container for TypeScript with Koin-style property injection, decorator-based registration, and lifecycle management. Perfect for testing with easy mock injection!

Readme

TypeScript Dependency Injection Framework

A lightweight, type-safe dependency injection container for TypeScript with Koin-style property injection, decorator-based registration, and lifecycle management. Perfect for testing with easy mock injection!

Features

  • 🔒 Type-Safe - Full TypeScript type inference with no any types
  • 🎯 Koin-Style Injection - Clean property injection using get() function
  • 🧪 Test-Friendly - Trivial to test with mock dependencies via optional constructor params
  • 🔄 Lifecycle Management - Built-in singleton and transient lifecycle support
  • 🛡️ Error Handling - Descriptive errors for missing dependencies and circular dependency detection
  • 📦 ESM Native - Modern ES Modules support
  • 🪶 Lightweight - Minimal dependencies (only reflect-metadata)

Installation

yarn add launchpad-dependency-injection
# or
npm install launchpad-dependency-injection

Quick Start

import 'reflect-metadata';
import { Container, createToken, singleton, transient, get, setContainer } from 'dependency-injection';

// 1. Define your interfaces and create tokens
interface Logger {
  log(message: string): void;
}
const Logger = createToken<Logger>('Logger');

// 2. Implement your classes with decorators
@singleton()
class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(`[LOG] ${message}`);
  }
}

// 3. Use property injection with get()
@transient()
class UserService {
  private logger = get(Logger);

  getUser(id: number): void {
    this.logger.log(`Getting user ${id}`);
  }
}

// 4. Setup the container
const container = new Container();
setContainer(container);  // Set as global container
container.register(Logger, ConsoleLogger);
container.register(UserService, UserService);

// 5. Resolve and use
const userService = container.resolve(UserService);
userService.getUser(1); // [LOG] Getting user 1

Why Koin-Style? Testing Made Trivial!

The Koin-style property injection pattern makes testing incredibly easy. Simply use optional constructor parameters:

@transient()
class UserService {
  private logger: Logger;

  // Optional constructor params for testing
  constructor(logger?: Logger) {
    this.logger = logger ?? get(Logger);
  }
}

// Production: Container handles everything
const container = new Container();
setContainer(container);
container.register(Logger, ConsoleLogger);
const service = container.resolve(UserService);

// Testing: Just pass mocks - NO CONTAINER NEEDED!
const mockLogger = new MockLogger();
const service = new UserService(mockLogger);  // Easy!

No more coupling your tests to the DI framework. No more complex test setup. Just plain TypeScript constructors!

Core Concepts

Tokens

Tokens are type-safe identifiers for your dependencies. Use createToken<T>() to create tokens that carry TypeScript's phantom types:

interface Database {
  query(sql: string): any[];
}
const Database = createToken<Database>('Database');

Lifecycle Decorators

Control the lifecycle of your dependencies:

  • @singleton() - Single instance shared across all resolutions
  • @transient() - New instance created on every resolution
  • @injectable() - Base decorator (lifecycle determined at registration)
@singleton()
class DatabaseConnection {
  constructor() {
    console.log('Database connected'); // Only called once
  }
}

@transient()
class RequestHandler {
  constructor() {
    console.log('Handler created'); // Called every time
  }
}

Property Injection

Use the get() function to resolve dependencies within your classes:

@singleton()
class UserRepository {
  private db = get(Database);
  private logger = get(Logger);

  save(user: User): void {
    this.logger.log('Saving user...');
    this.db.query('INSERT INTO users ...');
  }
}

For test-friendly classes, use optional constructor parameters:

@singleton()
class UserRepository {
  private db: Database;
  private logger: Logger;

  // Optional params enable easy testing!
  constructor(db?: Database, logger?: Logger) {
    this.db = db ?? get(Database);
    this.logger = logger ?? get(Logger);
  }
}

API Reference

Container

register<T>(token: Token<T>, implementation: Constructor<T>): void

Register a dependency with the container. The lifecycle is determined by the decorator on the implementation class.

container.register(Logger, ConsoleLogger);

resolve<T>(token: Token<T>): T

Resolve and return an instance of the dependency. Returns a cached instance for singletons or creates a new instance for transients.

const logger = container.resolve(Logger);

isRegistered<T>(token: Token<T>): boolean

Check if a token is registered in the container.

if (container.isRegistered(Logger)) {
  // ...
}

clear(): void

Clear all singleton instances (useful for testing).

container.clear();

reset(): void

Reset the entire container (clears all bindings and singletons).

container.reset();

Functions

createToken<T>(description: string): Token<T>

Create a type-safe token for a dependency.

const MyService = createToken<MyService>('MyService');

setContainer(container: Container): void

Set the global active container. Call this once at application startup.

const container = new Container();
setContainer(container);

getContainer(): Container

Get the current active container. Throws NoActiveContainerError if no container has been set.

const container = getContainer();

get<T>(token: Token<T>): T

Resolve a dependency from the active container. This is the primary way to inject dependencies.

class UserService {
  private logger = get(Logger);
}

Decorators

@singleton()

Class decorator marking a class as a singleton (one instance for the lifetime of the container).

@singleton()
class DatabaseConnection {}

@transient()

Class decorator marking a class as transient (new instance on every resolution).

@transient()
class RequestHandler {}

@injectable()

Base class decorator marking a class as injectable (lifecycle determined elsewhere).

@injectable()
class Service {}

Advanced Usage

Multiple Dependencies

@singleton()
class UserService {
  private logger = get(Logger);
  private db = get(Database);
  private cache = get(Cache);

  // All dependencies resolved automatically!
}

Testing with Mocks

The optional constructor parameter pattern makes testing incredibly easy:

// Your service with test-friendly constructor
@transient()
class UserService {
  private logger: Logger;
  private db: Database;

  constructor(logger?: Logger, db?: Database) {
    this.logger = logger ?? get(Logger);
    this.db = db ?? get(Database);
  }

  getUser(id: number): User {
    this.logger.log(`Getting user ${id}`);
    return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
  }
}

// In your tests: just pass mocks, NO CONTAINER NEEDED!
class MockLogger implements Logger {
  logs: string[] = [];
  log(msg: string) { this.logs.push(msg); }
}

const mockLogger = new MockLogger();
const mockDb = new MockDatabase();
const service = new UserService(mockLogger, mockDb);

// Test the service
service.getUser(123);
assert(mockLogger.logs.includes('Getting user 123'));

Alternative: Testing with Container

You can also use a test container with mock implementations:

@singleton()
class MockLogger implements Logger {
  log(message: string): void {
    // Mock implementation
  }
}

const testContainer = new Container();
setContainer(testContainer);
testContainer.register(Logger, MockLogger);
testContainer.register(UserService, UserService);

const service = testContainer.resolve(UserService);
// Service now uses MockLogger

Error Handling

The framework provides descriptive errors:

// Dependency not found
try {
  container.resolve(UnregisteredToken);
} catch (error) {
  // DependencyNotFoundError: Dependency not found: UnregisteredService
}

// Circular dependency detected
try {
  // ServiceA depends on ServiceB, ServiceB depends on ServiceA
  container.resolve(ServiceA);
} catch (error) {
  // CircularDependencyError: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
}

Requirements

  • TypeScript 4.0 or higher
  • Enable experimentalDecorators and emitDecoratorMetadata in tsconfig.json:
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
  • Import reflect-metadata at the entry point of your application:
import 'reflect-metadata';

Examples

  • Basic Usage - Complete demonstration of Koin-style DI with lifecycle management
  • Testing Patterns - Shows how to test with mocks (production, testing, and hybrid approaches)

License

MIT © Parsa Nasirimehr

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.