TypeScript IoC Container

Bash
npm install ts-ioc-container reflect-metadata
npm install ts-ioc-container reflect-metadata
Bash
yarn add ts-ioc-container reflect-metadata
yarn add ts-ioc-container reflect-metadata

Put this in your entrypoint file (should be the first line):

TypeScript
import 'reflect-metadata';
import 'reflect-metadata';

Configure tsconfig.json:

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

Basic Example: User Authentication

This example demonstrates a real-world pattern: an AuthService that depends on a UserRepository for database access. The container automatically wires up the dependencies.

TypeScript __tests__/readme/basic.spec.ts
import 'reflect-metadata';
import { Container, type IContainer, inject, Registration as R, select } from 'ts-ioc-container';

/**
 * User Management Domain - Basic Dependency Injection
 *
 * This example demonstrates how to wire up a simple authentication service
 * that depends on a user repository. This pattern is common in web applications
 * where services need database access.
 */
describe('Basic usage', function () {
  // Domain types
  interface User {
    id: string;
    email: string;
    passwordHash: string;
  }

  // Repository interface - abstracts database access
  interface IUserRepository {
    findByEmail(email: string): User | undefined;
  }

  // Concrete implementation
  class UserRepository implements IUserRepository {
    private users: User[] = [{ id: '1', email: 'admin@example.com', passwordHash: 'hashed_password' }];

    findByEmail(email: string): User | undefined {
      return this.users.find((u) => u.email === email);
    }
  }

  it('should inject dependencies', function () {
    // AuthService depends on IUserRepository
    class AuthService {
      constructor(@inject('IUserRepository') private userRepo: IUserRepository) {}

      authenticate(email: string): boolean {
        const user = this.userRepo.findByEmail(email);
        return user !== undefined;
      }
    }

    // Wire up the container
    const container = new Container().addRegistration(R.fromClass(UserRepository).bindTo('IUserRepository'));

    // Resolve AuthService - UserRepository is automatically injected
    const authService = container.resolve(AuthService);

    expect(authService.authenticate('admin@example.com')).toBe(true);
    expect(authService.authenticate('unknown@example.com')).toBe(false);
  });

  it('should inject current scope for request context', function () {
    // In Express.js, each request gets its own scope
    // Services can access the current scope to resolve request-specific dependencies
    const appContainer = new Container({ tags: ['application'] });

    class RequestHandler {
      constructor(@inject(select.scope.current) public requestScope: IContainer) {}

      handleRequest(): string {
        // Access request-scoped dependencies
        return this.requestScope.hasTag('application') ? 'app-scope' : 'request-scope';
      }
    }

    const handler = appContainer.resolve(RequestHandler);

    expect(handler.requestScope).toBe(appContainer);
    expect(handler.handleRequest()).toBe('app-scope');
  });
});
import 'reflect-metadata';
import { Container, type IContainer, inject, Registration as R, select } from 'ts-ioc-container';

/**
 * User Management Domain - Basic Dependency Injection
 *
 * This example demonstrates how to wire up a simple authentication service
 * that depends on a user repository. This pattern is common in web applications
 * where services need database access.
 */
describe('Basic usage', function () {
  // Domain types
  interface User {
    id: string;
    email: string;
    passwordHash: string;
  }

  // Repository interface - abstracts database access
  interface IUserRepository {
    findByEmail(email: string): User | undefined;
  }

  // Concrete implementation
  class UserRepository implements IUserRepository {
    private users: User[] = [{ id: '1', email: 'admin@example.com', passwordHash: 'hashed_password' }];

    findByEmail(email: string): User | undefined {
      return this.users.find((u) => u.email === email);
    }
  }

  it('should inject dependencies', function () {
    // AuthService depends on IUserRepository
    class AuthService {
      constructor(@inject('IUserRepository') private userRepo: IUserRepository) {}

      authenticate(email: string): boolean {
        const user = this.userRepo.findByEmail(email);
        return user !== undefined;
      }
    }

    // Wire up the container
    const container = new Container().addRegistration(R.fromClass(UserRepository).bindTo('IUserRepository'));

    // Resolve AuthService - UserRepository is automatically injected
    const authService = container.resolve(AuthService);

    expect(authService.authenticate('admin@example.com')).toBe(true);
    expect(authService.authenticate('unknown@example.com')).toBe(false);
  });

  it('should inject current scope for request context', function () {
    // In Express.js, each request gets its own scope
    // Services can access the current scope to resolve request-specific dependencies
    const appContainer = new Container({ tags: ['application'] });

    class RequestHandler {
      constructor(@inject(select.scope.current) public requestScope: IContainer) {}

      handleRequest(): string {
        // Access request-scoped dependencies
        return this.requestScope.hasTag('application') ? 'app-scope' : 'request-scope';
      }
    }

    const handler = appContainer.resolve(RequestHandler);

    expect(handler.requestScope).toBe(appContainer);
    expect(handler.handleRequest()).toBe('app-scope');
  });
});

Express.js Integration

Here’s how to integrate the container with Express.js for request-scoped dependencies:

TypeScript Express.js integration
import express from 'express';
import { Container, type IContainer } from 'ts-ioc-container';

// Extend Express Request type
declare global {
namespace Express {
  interface Request {
    container: IContainer;
  }
}
}

// Application container (singleton services)
const appContainer = new Container({ tags: ['application'] });
// ... register your services

// Middleware: Create request scope
app.use((req, res, next) => {
req.container = appContainer.createScope({ tags: ['request'] });
res.on('finish', () => req.container.dispose());
next();
});

// Route handler
app.post('/login', (req, res) => {
const authService = req.container.resolve<AuthService>('IAuthService');
const result = authService.authenticate(req.body.email, req.body.password);
res.json({ success: result });
});
import express from 'express';
import { Container, type IContainer } from 'ts-ioc-container';

// Extend Express Request type
declare global {
namespace Express {
  interface Request {
    container: IContainer;
  }
}
}

// Application container (singleton services)
const appContainer = new Container({ tags: ['application'] });
// ... register your services

// Middleware: Create request scope
app.use((req, res, next) => {
req.container = appContainer.createScope({ tags: ['request'] });
res.on('finish', () => req.container.dispose());
next();
});

// Route handler
app.post('/login', (req, res) => {
const authService = req.container.resolve<AuthService>('IAuthService');
const result = authService.authenticate(req.body.email, req.body.password);
res.json({ success: result });
});

Browse the documentation chapters in the navigation menu to learn about:

This chapter provides an overview of the core architecture and design of the TypeScript IoC container library. Understanding these concepts will help you make the most of the library’s features.

The IoC container is built on four fundamental abstractions that work together to provide dependency injection:

Container

Manages the lifecycle and resolution of dependencies

Provider

Factory that creates dependency instances

Injector

Determines how dependencies are injected into constructors

Registration

Connects providers to containers with keys and configuration

Architecture Overview

graph TB Container[Container] Provider[IProvider] Injector[IInjector] Registration[IRegistration] Scope[Container Scope] Container -->|uses| Provider Container -->|uses| Injector Container -->|creates| Scope Registration -->|creates| Provider Container -->|manages| Registration Provider -->|resolves| Instance[Instance] Injector -->|injects| Instance style Container fill:#0366d6,color:#fff style Provider fill:#28a745,color:#fff style Injector fill:#ffc107,color:#000 style Registration fill:#17a2b8,color:#fff style Scope fill:#6f42c1,color:#fff style Instance fill:#dc3545,color:#fff

The following diagram shows the main classes and interfaces and their relationships:

classDiagram class IContainer { +resolve(key) T +createScope(options) IContainer +register(key, provider) this +addRegistration(registration) this +dispose() void +getInstances() Instance[] +hasTag(tag) boolean } class Container { -parent: IContainer -scopes: IContainer[] -instances: Instance[] -providers: Map -injector: IInjector -tags: Set +resolve(key) T +createScope(options) IContainer +register(key, provider) this +dispose() void } class IProvider { +resolve(container, options) T +hasAccess(options) boolean +pipe(mappers) IProvider +setAccessRule(rule) this +setArgs(fn) this +lazy() this } class Provider { -factory: ResolveDependency -argsFn: ArgsFn -accessRule: ScopeAccessRule -isLazy: boolean +resolve(container, options) T +pipe(mappers) IProvider } class SingletonProvider { -cache: Cache +resolve(container, options) T } class IInjector { +resolve(container, constructor, options) T } class MetadataInjector { +resolve(container, constructor, options) T -createInstance(container, constructor, options) T } class SimpleInjector { +resolve(container, constructor, options) T -createInstance(container, constructor, options) T } class ProxyInjector { +resolve(container, constructor, options) T -createInstance(container, constructor, options) T } class IRegistration { +when(predicates) this +bindToKey(key) this +bindTo(key) this +pipe(mappers) this +applyTo(container) void } class Registration { -provider: IProvider -key: DependencyKey -scopeRules: ScopeMatchRule[] +when(predicates) this +bindToKey(key) this +applyTo(container) void } IContainer <|.. Container IContainer <|.. EmptyContainer Container --> IProvider : uses Container --> IInjector : uses Container --> IRegistration : manages IProvider <|.. Provider IProvider <|.. SingletonProvider IInjector <|.. MetadataInjector IInjector <|.. SimpleInjector IInjector <|.. ProxyInjector IRegistration <|.. Registration Registration --> IProvider : creates Container --> Container : parent-child