Type-Safe IoC Container for Node.js, TypeScript and JavaScript, inspired by .NET Core
npm install @nodelibraries/iocA lightweight, production-ready Inversion of Control (IoC) container inspired by .NET Core's dependency injection system.
Built with TypeScript for full type safety • Zero dependencies • No decorators required
📖 Documentation • 💡 Examples • 📚 API Reference • ⭐ GitHub
    
---
- 🎯 Fully Type-Safe - Complete TypeScript support with type inference and compile-time safety
- 📦 Zero Dependencies - Lightweight, pure TypeScript implementation
- 🚫 No Decorators - Clean, readable code without decorator pollution
- 🔄 Multiple Lifetimes - Singleton, Scoped, and Transient service lifetimes
- 🏗️ Automatic DI - Seamless dependency resolution and injection
- 🏭 Factory Pattern - Support for factory functions and async initialization
- 🔢 Multiple Implementations - Register and retrieve multiple implementations of the same interface
- 🔑 Keyed Services - Key-based service lookup with getRequiredKeyedService
- ✅ TryAdd Pattern - Safe registration without overriding existing services
- 🛡️ Scope Validation - Detect lifetime mismatches at build time
- 🔍 Service Checking - Check service existence with isService() without resolving
- 🗑️ Service Management - Remove, replace, and manage services dynamically
- 🔄 Lifecycle Hooks - onInit() and onDestroy() callbacks for initialization and cleanup
- 💎 Value Registration - Register pre-created values (JSON, primitives, instances)
- 🔄 Circular Dependencies - Automatic resolution for all lifetimes (including Transient, unlike .NET Core)
- 📊 Dependency Tree Visualization - Visualize and analyze service dependency trees
- 🔍 Circular Dependency Detection - Detect and visualize all circular dependencies
- 📜 Full JavaScript Support - All features work in JavaScript (CommonJS and ES Modules)
- 🔧 JSDoc Support - Comprehensive JSDoc comments for IntelliSense and autocomplete
- ⚠️ Runtime Validation Recommended - Use validateOnBuild and validateScopes for better error detection
---
``bash`
npm install @nodelibraries/ioc
`typescript
import { ServiceCollection, ServiceProvider } from '@nodelibraries/ioc';
// Define interfaces
interface ILogger {
log(message: string): void;
}
interface IUserService {
getUsers(): string[];
}
// Implement services
class Logger implements ILogger {
log(message: string) {
console.log([LOG] ${message});
}
}
class UserService implements IUserService {
constructor(private logger: ILogger) {}
getUsers(): string[] {
this.logger.log('Fetching users...');
return ['Alice', 'Bob'];
}
}
// Setup container
const services = new ServiceCollection();
const ILoggerToken = Symbol('ILogger');
const IUserServiceToken = Symbol('IUserService');
// Register services
// ⚠️ IMPORTANT: If a class constructor has parameters (dependencies), you MUST provide them in the dependencies array.
services.addSingleton
// ⚠️ IMPORTANT: The order of dependency tokens must match the constructor parameter order.
services.addScoped
// Build provider
const provider = services.buildServiceProvider();
// Use services
const scope = provider.createScope();
const userService = await scope.getRequiredService
const users = userService.getUsers();
// Cleanup
await scope.dispose();
`
`javascript
const { ServiceCollection, ServiceProvider } = require('@nodelibraries/ioc');
class Logger {
log(message) {
console.log([LOG] ${message});
}
}
class UserService {
constructor(logger) {
this.logger = logger;
}
getUsers() {
this.logger.log('Fetching users...');
return ['Alice', 'Bob'];
}
}
const services = new ServiceCollection();
const ILoggerToken = Symbol('ILogger');
const IUserServiceToken = Symbol('IUserService');
// ⚠️ IMPORTANT: If a class constructor has parameters (dependencies), you MUST provide them in the dependencies array
services.addSingleton(ILoggerToken, Logger);
// ⚠️ IMPORTANT: The order of dependency tokens must match the constructor parameter order.
services.addScoped(IUserServiceToken, UserService, [ILoggerToken]);
const provider = services.buildServiceProvider();
(async () => {
const scope = provider.createScope();
const userService = await scope.getRequiredService(IUserServiceToken);
const users = userService.getUsers();
await scope.dispose();
})();
`
---
- 📖 Getting Started Guide - Learn the basics
- 📚 API Reference - Complete API documentation
- 💡 Examples - 19+ practical examples with code
- 🔍 JavaScript Support - JavaScript-specific documentation
---
No decorators, no annotations, no framework lock-in. Your code remains pure and framework-agnostic.
`typescript`
// Clean, simple registration
services.addSingleton
services.addScoped
Built from the ground up for TypeScript. Full type inference, autocomplete, and compile-time safety.
`typescript`
// Full type safety with autocomplete
const logger = await provider.getRequiredService
logger.log('Hello'); // ✅ TypeScript knows this method exists
Battle-tested features including scope validation, lifecycle hooks, and comprehensive error handling. Enable validation in development to catch issues early:
`typescript`
// Build with validation (recommended for development)
const provider = services.buildServiceProvider({
validateScopes: true, // Catch lifetime mismatches (e.g., scoped service in singleton)
validateOnBuild: true, // Validate all dependencies exist at build time
});
> Note: Both options default to false. Enable them explicitly for validation. For detailed explanations and examples, see the documentation.
Circular dependencies are automatically resolved for all service lifetimes, including Transient services (which .NET Core doesn't support).
`typescript
// Circular dependencies work seamlessly for Singleton, Scoped, and Transient
class ServiceA {
constructor(private serviceB: ServiceB) {}
}
class ServiceB {
constructor(private serviceA: ServiceA) {} // ✅ Works for all lifetimes!
}
services.addSingleton(ServiceA, ServiceA, [ServiceB]);
services.addSingleton(ServiceB, ServiceB, [ServiceA]);
// ✅ No errors - circular dependencies are automatically resolved
`
---
| Lifetime | Description | Use Case |
| ------------- | --------------------------------------- | ------------------------------------------- |
| Singleton | One instance for the entire application | Loggers, Configuration, Caches |
| Scoped | One instance per scope | Request-scoped services, Unit of Work |
| Transient | New instance every time | Validators, Calculators, Stateless services |
`typescript
// 1. Class registration (no dependencies - constructor has no parameters)
services.addSingleton(Logger);
// 2. Interface registration with dependencies
// ⚠️ IMPORTANT: If UserService constructor requires ILogger, you MUST provide [ILoggerToken]
services.addScoped
// 3. Factory pattern (supports async initialization)
services.addSingleton
const config = await provider.getRequiredService
return new HttpClient(config.apiUrl);
});
// 4. Value registration (pre-created instances)
services.addValue
// 5. Keyed services (multiple implementations with keys)
services.addKeyedSingleton
services.addKeyedSingleton
// 6. TryAdd pattern (safe registration - won't override existing)
services.tryAddSingleton
// 7. Service management
services.remove(ILoggerToken); // Remove service
services.replace(ILoggerToken, NewLogger); // Replace with new implementation
`
`typescript
// 1. Optional resolution (returns undefined if not found)
const logger = await provider.getService
if (logger) {
logger.log('Service found');
}
// 2. Required resolution (throws if not found)
const userService = await provider.getRequiredService
// 3. Get all implementations (for multiple registrations)
const writers = await provider.getServices
// Returns array of all registered implementations
// 4. Keyed service resolution
const cache = await provider.getRequiredKeyedService
// 5. Check if service exists (without resolving)
if (await provider.isService
// Service is registered
}
`
---
Visualize and analyze your service dependency trees:
`typescript
// Visualize dependency tree as formatted string
console.log(services.visualizeDependencyTree(IUserServiceToken));
// Output:
// └── Symbol(IUserService) [SINGLETON]
// ├── Symbol(IUserRepository) [SINGLETON]
// │ └── Symbol(IDatabase) [SINGLETON]
// └── Symbol(ILogger) [SINGLETON]
// Get tree as structured object
const tree = services.getDependencyTree(IUserServiceToken);
console.log(tree.dependencies); // Array of dependency nodes
`
Detect and visualize all circular dependencies in your service collection:
`typescript`
// Detect all circular dependencies
const circularDeps = services.getCircularDependencies();
if (circularDeps.length > 0) {
console.log(services.visualizeCircularDependencies());
// Output:
// Found 1 circular dependency/ies:
// Circular Dependency 1:
// Symbol(ServiceA) → Symbol(ServiceB) → Symbol(ServiceA)
}
Handle service initialization and cleanup with lifecycle hooks:
`typescript
class DatabaseConnection {
async onInit() {
// Called after instance creation
await this.connect();
}
async onDestroy() {
// Called when scope/provider is disposed
await this.disconnect();
}
}
services.addScoped(DatabaseConnection);
const scope = provider.createScope();
const db = await scope.getRequiredService(DatabaseConnection);
// onInit() is automatically called
await scope.dispose();
// onDestroy() is automatically called
`
---
We provide 19+ comprehensive examples covering all features:
| Category | Examples | Topics |
| ----------------- | -------- | ------------------------------------------------------------------------------------------ |
| Basic | 1-3 | Basic usage, interface registration, string tokens |
| Core Concepts | 4-6 | Service lifetimes, lifecycle hooks, value registration |
| Advanced | 7-13 | Generic types, factory pattern, multiple implementations, keyed services, scope validation |
| Complex | 14-15 | Circular dependencies, complex dependency chains |
| Real-World | 16-17 | Service management, Express.js integration |
| Analysis | 18-19 | Dependency tree visualization, circular dependency detection |
Run an example:
`bash`
npx ts-node examples/1-basic.ts
See examples/README.md for detailed descriptions and running instructions.
---
This container is inspired by .NET Core's dependency injection system but designed for TypeScript/Node.js.
| Feature | .NET Core DI | @nodelibraries/ioc |
| ----------------------------- | ------------ | -------------------------- |
| Singleton Lifetime | ✅ | ✅ |
| Scoped Lifetime | ✅ | ✅ |
| Transient Lifetime | ✅ | ✅ |
| Factory Pattern | ✅ | ✅ |
| Multiple Implementations | ✅ | ✅ |
| Keyed Services | ✅ | ✅ |
| TryAdd Pattern | ✅ | ✅ |
| Scope Validation | ✅ | ✅ |
| Circular Dependencies | ⚠️ May fail | ✅ Works for all lifetimes |
| Dependency Tree Visualization | ❌ | ✅ |
| Circular Dependency Detection | ❌ | ✅ |
---
- Node.js >= 18.0.0 (LTS recommended)
- TypeScript >= 5.0.0 (optional, but recommended)
---
ISC License - see LICENSE file for details.
---
ylcnfrht
- GitHub: @ylcnfrht
- ☕ Buy me a coffee - If you find this project helpful, consider supporting it!
---
- 📖 Documentation
- 💬 GitHub Issues
- 📧 GitHub Discussions
- ☕ Buy me a coffee - If you find this project helpful, consider supporting it!
---
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create your feature branch (git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature'
3. Commit your changes ()git push origin feature/amazing-feature`)
4. Push to the branch (
5. Open a Pull Request
---
Made with ❤️ for the TypeScript/Node.js community