A dependency-injection (DI) container is a registry that knows how to build your objects: you tell it how each service is made and what it depends on, and it wires the whole graph together on demand. It is the core idea behind frameworks like Angular, NestJS, InversifyJS, and Awilix. You will build a small, explicit one — no decorators, no reflection — exposed as a factory diContainer().
const container = diContainer();
container.register(name, factory, options?);
// factory: (...deps) => instance receives its resolved dependencies
// options.dependencies: string[] names to resolve and pass, in order (default [])
// options.singleton: boolean cache one instance? (default true)
container.registerValue(name, value); // register a constant / already-built value
container.resolve(name); // build (or return the cached) instance
container.has(name); // → boolean: is `name` registered?
const c = diContainer();
c.registerValue('apiUrl', 'https://api.example.com');
c.register('http', (url) => ({ get: (path) => url + path }), {
dependencies: ['apiUrl'],
});
c.resolve('http').get('/users'); // 'https://api.example.com/users'
c.resolve('http') === c.resolve('http'); // true — the same cached singleton
// A transient rebuilds on every resolve; a cycle is rejected, not recursed forever.
c.register('ticket', () => ({ n: Math.random() }), { singleton: false });
c.resolve('ticket') === c.resolve('ticket'); // false — a fresh object each time
c.register('a', (b) => ({ b }), { dependencies: ['b'] });
c.register('b', (a) => ({ a }), { dependencies: ['a'] });
c.resolve('a'); // throws: Circular dependency detected: a -> b -> a
options.dependencies, and the container passes those instances as arguments, in the order listed. This keeps every factory a pure function of its inputs.resolve caches a singleton and returns the same instance every time, including when two services depend on it (a diamond shares one instance). Pass singleton: false for a transient that is rebuilt on each resolve.register time. A factory runs only when its service is resolved, and only once for a singleton.