TypeScript IoC Container
Advantages
- battle tested 💥
- written on
typescript - simple and lightweight ❤️
- clean API 💚
- supports
tagged scopes - fully test covered 💯
- can be used with decorators
@inject - can inject properties
- can inject lazy dependencies
- composable and open to extend
Quick Start
npm install ts-ioc-container reflect-metadatanpm install ts-ioc-container reflect-metadatayarn add ts-ioc-container reflect-metadatayarn add ts-ioc-container reflect-metadataPut this in your entrypoint file (should be the first line):
import 'reflect-metadata';import 'reflect-metadata';Configure tsconfig.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.
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:
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 });
});Documentation
Browse the documentation chapters in the navigation menu to learn about:
- Overview - Core architecture and class diagrams
- Container - Core container functionality, scopes, and instance management
- Registration - Registering providers with keys and scopes
- Provider - Dependency factories with singleton, args, visibility, alias, and decorator features
- Injector - Different injection strategies (Metadata, Simple, Proxy)
- Token - Token types for dependency keys (InjectionToken, SingleToken, GroupAliasToken, etc.)
- Hooks - Lifecycle hooks for instance initialization and cleanup (onConstruct, onDispose)
Core Architecture
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
Class Diagram
The following diagram shows the main classes and interfaces and their relationships: