@zirion/ioc · Examples
Runnable examples
Complete @zirion/ioc v3 examples: a Node.js HTTP server, provider lifetime checks, and retries of asynchronous singleton factories.
Updated · · if-else.dev
Run the examples
These are complete JavaScript modules for Node.js 20 or newer. No framework or TypeScript compiler is needed. In a separate example directory, install the package:
npm init -y
npm install @zirion/ioc@^3.0.0
Save each example with the suggested .mjs filename and run it with node. The main guide explains the registrations in detail.
Node.js HTTP server
Save as server.mjs:
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';
import { Container, InjectScope } from '@zirion/ioc';
class UserStore {
users = [{ id: 1, name: 'Ada' }];
}
class RequestInfo {
constructor(context) {
this.context = context;
}
}
class UserHandler {
id = randomUUID();
constructor(store, config, info) {
this.store = store;
this.config = config;
this.info = info;
}
body() {
return {
requestId: this.info.context.requestId,
handlerId: this.id,
region: this.config.region,
users: this.store.users,
};
}
}
const container = new Container()
.add(UserStore, { scope: InjectScope.SINGLETON })
.add('config', {
scope: InjectScope.SINGLETON,
valueFactory: async () => ({ region: 'eu-central-1' }),
})
.add(RequestInfo, { scope: InjectScope.REQUEST })
.add(UserHandler, { inject: [UserStore, 'config', RequestInfo] });
const server = createServer(async (request, response) => {
const context = { requestId: randomUUID() };
try {
let body;
let status = 200;
if (request.method === 'GET' && request.url === '/health') {
const store = container.getOrFailSync(UserStore);
body = { ok: true, users: store.users.length };
} else if (request.method === 'GET' && request.url === '/users') {
const handler = await container.getOrFail(UserHandler, context);
body = handler.body();
} else {
status = 404;
body = { error: 'Not found' };
}
response.writeHead(status, { 'content-type': 'application/json' });
response.end(JSON.stringify(body));
} catch (error) {
console.error(error);
response.writeHead(500, { 'content-type': 'application/json' });
response.end(JSON.stringify({ error: 'Internal server error' }));
}
});
server.listen(3000, '127.0.0.1', () => {
console.log('http://127.0.0.1:3000/users');
});
process.once('SIGTERM', () => {
server.close();
});
Run the server and make two requests:
node server.mjs
# In another terminal:
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/users
curl http://127.0.0.1:3000/users
The store and config are singleton values. Each HTTP request gets a fresh context and request state. UserHandler is transient; even two resolutions within one request would produce two handlers while sharing that request’s RequestInfo.
/health uses synchronous resolution for the synchronous store graph. /users awaits the graph because config initialization is asynchronous. A real connection factory can replace the demonstration config factory. Close any external clients in the application’s shutdown handler; finalize() does not dispose them.
Verify lifetimes and repeated injection
Save as lifetimes.mjs, then run node lifetimes.mjs:
import assert from 'node:assert/strict';
import { Container, InjectScope } from '@zirion/ioc';
class Store {}
class RequestState {}
class Operation {}
class Pair {
constructor(first, second) {
this.first = first;
this.second = second;
}
}
const container = new Container()
.add(Store, { scope: InjectScope.SINGLETON })
.add(RequestState, { scope: InjectScope.REQUEST })
.add(Operation)
.add(Pair, { inject: [Operation, Operation] });
assert.equal(
container.getOrFailSync(Store),
container.getOrFailSync(Store),
);
const requestA = {};
const requestB = {};
assert.equal(
container.getOrFailSync(RequestState, requestA),
container.getOrFailSync(RequestState, requestA),
);
assert.notEqual(
container.getOrFailSync(RequestState, requestA),
container.getOrFailSync(RequestState, requestB),
);
const pair = container.getOrFailSync(Pair);
assert.notEqual(pair.first, pair.second);
assert.notEqual(pair, container.getOrFailSync(Pair));
console.log('All lifetime checks passed');
The same context reuses request state; a different object does not. Duplicate transient selectors in inject are preserved and create different instances. Change Operation to singleton to see the repeated injection share one instance instead.
Retry a failed async singleton
Save as retry.mjs, then run node retry.mjs:
import assert from 'node:assert/strict';
import { Container, InjectScope } from '@zirion/ioc';
let attempts = 0;
const container = new Container().add('enabled', {
scope: InjectScope.SINGLETON,
valueFactory: async () => {
attempts += 1;
if (attempts === 1) {
throw new Error('Temporary failure');
}
return false;
},
});
await assert.rejects(container.getOrFail('enabled'), /Temporary failure/);
const [first, second] = await Promise.all([
container.getOrFail('enabled'),
container.getOrFail('enabled'),
]);
assert.equal(first, false);
assert.equal(second, false);
assert.equal(await container.getOrFail('enabled'), false);
assert.equal(attempts, 2);
console.log('Retry, concurrent resolution, and falsy caching verified');
The failed factory is not cached. The next two callers share one successful attempt, and false is then cached normally. Apply the same checks to request providers with a shared context and to classes whose onInitialized() can fail.
Next: v2 to v3 migration and migration from other containers.