@notjustcoders/di-container
v1.0.7
Published
A lightweight, type-safe dependency injection framework for TypeScript with zero decorators.
Downloads
734
Maintainers
Readme
@notjustcoders/di-container
A lightweight, type-safe dependency injection (DI) framework for TypeScript with zero decorators. Perfect for clean architecture applications.
Features
✨ Zero Decorators - No decorator syntax required
🔒 Type-Safe - Full TypeScript support with autocomplete
🪶 Lightweight - Minimal runtime footprint
📦 Modular - Built-in module system for organizing dependencies
🔄 Lifecycle Management - Singleton and Transient scopes
🎯 Clean Architecture - Perfect for hexagonal/clean architecture patterns
⚡ Fast - Optimized for performance
Installation
npm install @notjustcoders/di-containeror
yarn add @notjustcoders/di-containeror
pnpm add @notjustcoders/di-containerQuick Start
1. Define Your Services
// services/UserRepository.ts
export interface IUserRepository {
findById(id: string): Promise<User>;
save(user: User): Promise<void>;
}
export class UserRepository implements IUserRepository {
async findById(id: string): Promise<User> {
// Implementation
}
async save(user: User): Promise<void> {
// Implementation
}
}
// services/UserService.ts
export class UserService {
constructor(private userRepository: IUserRepository) {}
async getUser(id: string): Promise<User> {
return this.userRepository.findById(id);
}
}2. Set Up the Container
import { Container, Lifecycle } from '@notjustcoders/di-container';
const container = new Container();
// Register interface with string token
container.register('IUserRepository', {
useClass: UserRepository,
lifecycle: Lifecycle.Singleton
});
// Register service with dependencies
container.register(UserService, {
useClass: UserService,
dependencies: ['IUserRepository'],
lifecycle: Lifecycle.Transient
});3. Resolve and Use
// Resolve by interface name
const userRepo = container.resolve('IUserRepository');
// Resolve by class
const userService = container.resolve(UserService);
await userService.getUser('123');Type Safety
Enable full type safety with TypeScript:
// Define your registry
interface AppRegistry {
'IUserRepository': IUserRepository;
'IEmailService': IEmailService;
}
// Create typed container
const container = new Container<AppRegistry>();
// Now you get autocomplete and type checking!
const repo = container.resolve('IUserRepository'); // Type: IUserRepositoryModule System
Organize your dependencies into modules:
import { ContainerModule, Lifecycle } from '@notjustcoders/di-container';
// Create a module
const userModule = new ContainerModule()
.register('IUserRepository', {
useClass: UserRepository,
lifecycle: Lifecycle.Singleton
})
.register('IUserService', {
useClass: UserService,
dependencies: ['IUserRepository'],
lifecycle: Lifecycle.Transient
});
// Register module in container
container.registerModule(userModule);Lifecycle Management
Singleton
One instance shared across the entire application:
container.register('IUserRepository', {
useClass: UserRepository,
lifecycle: Lifecycle.Singleton // Same instance every time
});Transient
New instance created every time:
container.register('IEmailService', {
useClass: EmailService,
lifecycle: Lifecycle.Transient // New instance each resolve
});Token Types
ioc-arise supports multiple token types:
String Tokens (for interfaces)
container.register('IUserRepository', {
useClass: UserRepository
});
const repo = container.resolve('IUserRepository');Class Constructors
container.register(UserService, {
useClass: UserService
});
const service = container.resolve(UserService);Symbol Tokens
const USER_REPO = Symbol('IUserRepository');
container.register(USER_REPO, {
useClass: UserRepository
});
const repo = container.resolve(USER_REPO);Child Containers
Create scoped containers for testing or isolation:
const parentContainer = new Container();
parentContainer.register('IConfig', {
useClass: ProductionConfig,
lifecycle: Lifecycle.Singleton
});
// Child inherits from parent
const childContainer = parentContainer.createChild();
childContainer.register('IUserService', {
useClass: UserService,
dependencies: ['IConfig'] // Resolved from parent
});Abstract Classes
Use abstract classes as dependencies:
abstract class AbstractRepository {
abstract findById(id: string): Promise<any>;
}
class UserRepository extends AbstractRepository {
async findById(id: string): Promise<User> {
// Implementation
}
}
// Register with string token
container.register('AbstractRepository', {
useClass: UserRepository
});
// Use in dependencies
container.register(UserService, {
useClass: UserService,
dependencies: ['AbstractRepository']
});Error Handling
ioc-arise provides clear error messages:
// Missing provider
container.resolve('NonExistent');
// Error: No provider found for token: NonExistent
// Circular dependencies
container.register('ServiceA', {
useClass: ServiceA,
dependencies: ['ServiceB']
});
container.register('ServiceB', {
useClass: ServiceB,
dependencies: ['ServiceA']
});
container.resolve('ServiceA');
// Error: Circular dependency detected: ServiceA -> ServiceB -> ServiceAIntegration with IOC-Arise CLI
For automatic container generation, use the @notjustcoders/ioc-arise CLI tool:
npm install -D @notjustcoders/ioc-ariseThe CLI analyzes your TypeScript code and generates container configuration automatically.
API Reference
Container
new Container<TRegistry>(parent?: Container<TRegistry>)
Creates a new container instance.
register<T>(token: Token<T>, provider: Provider<T>): void
Registers a provider in the container.
resolve<T>(token: Token<T>): T
Resolves an instance from the container.
registerModule(module: ContainerModule): void
Registers all providers from a module.
createChild(): IContainer<TRegistry>
Creates a child container that inherits from the parent.
ContainerModule
new ContainerModule()
Creates a new module instance.
register<T>(token: Token<T>, provider: Provider<T>): this
Registers a provider in the module (chainable).
Lifecycle
Lifecycle.Singleton
One instance shared across the application.
Lifecycle.Transient
New instance created every time.
Best Practices
- Use interfaces for dependencies - Improves testability and decoupling
- Prefer Singleton for stateless services - Better performance
- Use modules for organization - Group related providers
- Type your registry - Enable full type safety
- Register at application startup - Not at runtime during request handling
Examples
Check out the examples directory for complete working examples:
- minimal-todo - Getting started
- clean-architecture - Multi-module application
- abstract-classes-example - Abstract class dependencies
- simple-modules - Module system basics
Why @notjustcoders/di-container?
vs. Traditional DI Frameworks
- ❌ InversifyJS - Requires decorators and reflect-metadata
- ❌ TypeDI - Requires decorators
- ❌ TSyringe - Requires decorators and reflect-metadata
- ✅ @notjustcoders/di-container - No decorators, pure TypeScript
vs. Manual DI
- ❌ Manual - Verbose, error-prone, no lifecycle management
- ✅ @notjustcoders/di-container - Automatic, type-safe, with lifecycle support
License
MIT © Sid Ali Assoul
Contributing
Contributions are welcome! Please read our contributing guide.
