A lightweight toolkit for Node.js microservices. Handles the scaffolding — health checks, graceful shutdown, provider connections, HTTP and WebSocket servers and clients, and structured logging — so your service focuses on its tasks.
pnpm install @devvir/service-kitThe minimal case:
import SK from '@devvir/service-kit';
SK.run(async (service) => {
service.logger.info('Running!');
});Out of the box you get a health check on port 3000 and graceful shutdown on SIGTERM/SIGINT. Shutdown listeners can be async — all are awaited before the process exits. No configuration required.
Declare config, bind lifecycle hooks, then run:
import SK from '@devvir/service-kit';
SK.declare({
name: 'api',
config: { port: parseInt(process.env.PORT || '3001') },
});
SK.bind({
onShutdown: async (service) => {
await cleanup();
service.logger.info('cleaned up');
},
});
SK.run(async (service) => {
const port = service.config('port') as number;
startServer(port);
});Connect to a database or queue in your service function:
import SK, { type Service } from '@devvir/service-kit';
SK.declare({
name: 'worker',
providers: {
rabbitmq: { url: process.env.AMQP_URL },
},
});
SK.run(async (service: Service) => {
const conn = await service.providers.connect('rabbitmq');
// start consuming...
});Declare HTTP/WebSocket servers and outbound clients. The kit owns the boilerplate — routing, the error handler, retries, reconnection, and clean transition logs:
import SK, { type ExpressServerHandle, type FetchClientHandle } from '@devvir/service-kit';
SK.declare({
name: 'api',
servers: { type: 'express' },
clients: { name: 'upstream', type: 'fetch', url: process.env.UPSTREAM_URL },
});
SK.run(async (service) => {
const upstream = service.clients.get('upstream') as FetchClientHandle;
const api = service.servers.get() as ExpressServerHandle;
api.addRoute('get', '/status', async (_req, res) => {
res.json(await upstream.get('/health'));
});
await api.start();
});A fetch client retries transient failures with capped backoff and logs an outage once, not once per attempt; a ws client reconnects with single-flight backoff. Both go from declaration to working handle with no hand-rolled loop.
Access any running service — or just its config, state, or providers — from anywhere in the process without prop drilling:
import { registry, SK_CONFIG, SK_STATE, SK_PROVIDERS } from '@devvir/service-kit';
// From a module that has no reference to the service object:
const svc = registry.get('worker');
const config = registry.get('worker', SK_CONFIG); // → service.config()
const state = registry.get('worker', SK_STATE); // → service.state()
const providers = registry.get('worker', SK_PROVIDERS); // → service.providersSK.run() registers the service automatically when the spec includes a name. Registration fails fast if two services with the same name are started in one process — early detection for misconfiguration.
// In tests — swap out the real service with a controlled mock:
import { registry } from '@devvir/service-kit';
beforeEach(() => registry.clear());