@abinashpatri/orchestrator
v1.0.1
Published
Reusable TypeScript/JavaScript service discovery and API gateway toolkit
Maintainers
Readme
@abinashpatri/orchestrator
Reusable TypeScript/JavaScript toolkit for service registration, service discovery, and API gateway routing.
Works in both ESM and CommonJS projects with full TypeScript type support.
Core APIs (createServiceClient, registerService, deregisterService, getServiceUrl) are framework-agnostic.
Express gateway APIs are available from the subpath @abinashpatri/orchestrator/express.
Install
npm install @abinashpatri/orchestratorFor Express gateway adapter, install peer dependencies in your microservice:
npm install express http-proxy-middlewarePrerequisites
- Consul agent is running and reachable
- your services expose a health endpoint (default:
/health) - Node.js 18+ recommended
Step-by-step usage (modern way)
1) Create a Consul client
import { createServiceClient } from "@abinashpatri/orchestrator";
const consul = createServiceClient({
host: process.env.CONSUL_HOST ?? "127.0.0.1",
port: Number(process.env.CONSUL_PORT ?? 8500),
token: process.env.CONSUL_HTTP_TOKEN, // optional
secure: process.env.CONSUL_SECURE === "true", // optional
});2) Register your service
import { registerService } from "@abinashpatri/orchestrator";
const serviceId = await registerService(consul, {
serviceName: "user-service",
serviceId: `user-service-${process.pid}`, // optional (auto-generated if not provided)
address: "127.0.0.1",
port: 3001,
tags: ["v1", "public"], // optional
healthPath: "/health", // optional, default: /health
healthInterval: "10s", // optional, default: 10s
healthTimeout: "5s", // optional, default: 5s
deregisterCriticalServiceAfter: "30s", // optional, default: 30s
});3) Discover another service URL
import { getServiceUrl } from "@abinashpatri/orchestrator";
const paymentServiceUrl = await getServiceUrl(consul, "payment-service");
// Example output: http://127.0.0.1:4002By default, discovery returns a random healthy instance (simple load balancing).
4) Build an API gateway app
import { createGatewayApp } from "@abinashpatri/orchestrator/express";
const userHeaderMiddleware = (req, res, next) => {
req.headers["x-service-source"] = "api-gateway";
next();
};
const paymentAuthMiddleware = (req, res, next) => {
if (!req.headers.authorization) {
res.status(401).json({ error: "Missing Authorization header" });
return;
}
next();
};
const app = createGatewayApp(consul, {
healthPath: "/health",
routes: [
{
serviceName: "user-service",
routePrefix: "/api/users",
middlewares: [userHeaderMiddleware],
},
{
serviceName: "payment-service",
routePrefix: "/api/payments",
middlewares: [paymentAuthMiddleware],
},
{ serviceName: "notification-service" }, // defaults to /api/notification-service
],
});
const server = app.listen(4000, () => {
console.log("Gateway running on :4000");
});5) Graceful shutdown (important for production)
import { deregisterService } from "@abinashpatri/orchestrator";
function shutdown(signal: string) {
return async () => {
console.log(`Received ${signal}, shutting down...`);
server.close(async () => {
try {
await deregisterService(consul, serviceId);
} finally {
process.exit(0);
}
});
};
}
process.on("SIGINT", shutdown("SIGINT"));
process.on("SIGTERM", shutdown("SIGTERM"));Full TypeScript example
import {
createServiceClient,
registerService,
deregisterService,
getServiceUrl,
} from "@abinashpatri/orchestrator";
import { createGatewayApp } from "@abinashpatri/orchestrator/express";
const consul = createServiceClient({
host: process.env.CONSUL_HOST ?? "127.0.0.1",
port: Number(process.env.CONSUL_PORT ?? 8500),
token: process.env.CONSUL_HTTP_TOKEN,
});
const serviceId = await registerService(consul, {
serviceName: "api-gateway-service",
address: "127.0.0.1",
port: 4000,
healthPath: "/health",
});
const app = createGatewayApp(consul, {
routes: [
{ serviceName: "user-service", routePrefix: "/api/users" },
{ serviceName: "notification-service", routePrefix: "/api/notifications" },
],
});
app.get("/where-is-user-service", async (_req, res) => {
try {
const url = await getServiceUrl(consul, "user-service");
res.json({ service: "user-service", url });
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
res.status(503).json({ error: message });
}
});
const server = app.listen(4000, () => {
console.log("API gateway listening on port 4000");
});
async function cleanup() {
await deregisterService(consul, serviceId);
}
process.on("SIGINT", async () => {
server.close(async () => {
await cleanup();
process.exit(0);
});
});
process.on("SIGTERM", async () => {
server.close(async () => {
await cleanup();
process.exit(0);
});
});JavaScript (CommonJS) usage
const {
createServiceClient,
registerService,
getServiceUrl,
deregisterService,
} = require("@abinashpatri/orchestrator");
const { createGatewayApp } = require("@abinashpatri/orchestrator/express");
async function main() {
const consul = createServiceClient({
host: "127.0.0.1",
port: 8500,
token: process.env.CONSUL_HTTP_TOKEN,
});
const serviceId = await registerService(consul, {
serviceName: "api-gateway-service",
address: "127.0.0.1",
port: 4000,
});
const app = createGatewayApp(consul, {
routes: [{ serviceName: "user-service", routePrefix: "/api/users" }],
});
app.get("/discover-user-service", async (_req, res) => {
const url = await getServiceUrl(consul, "user-service");
res.json({ url });
});
const server = app.listen(4000);
process.on("SIGINT", async () => {
server.close(async () => {
await deregisterService(consul, serviceId);
process.exit(0);
});
});
}
main().catch((err) => {
console.error(err);
process.exit(1);
});API reference
createServiceClient(options?)
Creates and returns a Consul client.
options:
host?: string(default:127.0.0.1)port?: number(default:8500)token?: stringsecure?: boolean(default:false)
registerService(consulClient, options)
Registers a service and returns the final serviceId.
options:
serviceName: string(required)serviceId?: stringaddress: string(required)port: number(required)tags?: string[]healthPath?: string(default:/health)healthInterval?: string(default:10s)healthTimeout?: string(default:5s)deregisterCriticalServiceAfter?: string(default:30s)
deregisterService(consulClient, serviceId)
Removes a registered service from Consul.
getServiceUrl(consulClient, serviceName, options?)
Returns one discovered service URL, such as http://127.0.0.1:3001.
options:
passing?: boolean(default:true)
createServiceProxy(consulClient, options) (from @abinashpatri/orchestrator/express)
Creates an Express middleware that resolves service target dynamically via Consul.
options:
serviceName: string(required)routePrefix?: string(default:/api/${serviceName})
createGatewayApp(consulClient, options) (from @abinashpatri/orchestrator/express)
Creates an Express app and mounts service proxies for all routes.
options:
routes: Array<{ serviceName: string; routePrefix?: string; middlewares?: RequestHandler[] }>(required)healthPath?: string(default:/health)
Notes
- If Consul returns
host.docker.internal, the library auto-resolves it to127.0.0.1for host-local calls. - Keep service names consistent across registration and discovery.
- For production, always use graceful shutdown to avoid stale Consul registrations.
License
MIT
