typescriptadvanced
Dependency Injection Container
Build a lightweight DI container with singleton and transient scopes for testable Node.js applications.
typescriptPress ⌘/Ctrl + Shift + C to copy
type Factory<T> = () => T;
class Container {
private singletons = new Map<string, unknown>();
private factories = new Map<string, { factory: Factory<unknown>; singleton: boolean }>();
register<T>(token: string, factory: Factory<T>, options?: { singleton?: boolean }): this {
this.factories.set(token, { factory, singleton: options?.singleton ?? true });
return this;
}
resolve<T>(token: string): T {
const entry = this.factories.get(token);
if (!entry) throw new Error(`No registration for: ${token}`);
if (entry.singleton) {
if (!this.singletons.has(token)) {
this.singletons.set(token, entry.factory());
}
return this.singletons.get(token) as T;
}
return entry.factory() as T;
}
// Create child container for scoped overrides (testing)
createChild(): Container {
const child = new Container();
for (const [token, entry] of this.factories) {
child.factories.set(token, entry);
}
return child;
}
}
// Interfaces
interface Logger { info(msg: string): void; error(msg: string): void; }
interface Database { query(sql: string): Promise<unknown[]>; }
interface UserService { findUser(id: string): Promise<{ id: string; name: string } | null>; }
// Implementations
const container = new Container()
.register<Logger>('Logger', () => ({
info: (msg) => console.log(`[INFO] ${msg}`),
error: (msg) => console.error(`[ERROR] ${msg}`),
}))
.register<Database>('Database', () => ({
query: async (sql) => {
console.log(`Executing: ${sql}`);
return [{ id: '1', name: 'Alice' }];
},
}))
.register<UserService>('UserService', () => {
const db = container.resolve<Database>('Database');
const logger = container.resolve<Logger>('Logger');
return {
findUser: async (id) => {
logger.info(`Finding user: ${id}`);
const rows = await db.query(`SELECT * FROM users WHERE id = '${id}'`);
return rows[0] as { id: string; name: string } | null;
},
};
});
// Usage
async function main() {
const userService = container.resolve<UserService>('UserService');
const user = await userService.findUser('1');
console.log('Found:', user);
// Testing: override with mocks
const testContainer = container.createChild();
testContainer.register<Database>('Database', () => ({
query: async () => [{ id: 'mock', name: 'Mock User' }],
}));
}
main();Use Cases
- Testable application architecture
- Service layer composition
- Mock injection for testing
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
typescriptbeginner
Native Test Runner and Assert
Write tests using Node.js built-in test runner and assert module — no external dependencies needed.
Best for: Unit testing without external packages
#nodejs#testing
typescriptintermediate
Event-Driven Architecture Pattern
Build loosely coupled systems with typed event bus, async event handlers, and domain event patterns.
Best for: Microservice communication
#nodejs#events
typescriptintermediate
API Versioning Strategy
Implement API versioning with URL path, header-based, and content negotiation strategies.
Best for: API backward compatibility
#nodejs#api
kotlinintermediate
Dependency Injection with Koin
Set up Koin DI: modules, scoped instances, factory vs single, and parameterized injection.
Best for: Application dependency management
#kotlin#koin