The useful boundary is larger than the router.
Most Node.js applications begin with a small set of routes and middleware. That is a good default. Problems appear later, when the same application has to register itself, discover another service, survive a slow dependency, propagate trace context and shut down without advertising a half-dead instance.
OpenMesh treats the application lifecycle as the integration boundary. The HTTP server, service registration, database resources, managed peer pools, telemetry adapters and workload certificates can all participate in startup and shutdown. You can still use only the HTTP layer; the other layers are opt-in.
Start with an ordinary API. Add runtime capabilities when the system creates the problem that needs them.
Keep the request path inside Node.js.
OpenMesh owns a normal Node.js HTTP server. Application traffic is not proxied through a second runtime. Routing, middleware, Standard Schema-compatible validation, response contracts and graceful lifecycle hooks execute in-process.
import openmesh from 'openmesh-node';
import { bodyParser } from 'openmesh-node/plugins';
const app = openmesh();
app.use(bodyParser());
app.get('/users/:id', ({ params }) => ({
id: params.id
}));
await app.listen({ port: 3000 });The package ships ESM, CommonJS and generated TypeScript declarations. The native HTTP core has zero runtime dependencies; integrations such as Redis and OpenTelemetry remain optional boundaries.
Your database client remains your database client.
A service runtime does not need to invent a query language. openmesh-node/db wraps the lifecycle of the client you already chose and preserves its type. Prisma, Drizzle, Kysely, TypeORM, MikroORM, Sequelize and lower-level clients can keep their native APIs.
import { database } from 'openmesh-node/db';
const db = database(prisma, {
connect: client => client.$connect(),
disconnect: client => client.$disconnect(),
transaction: (client, work) => client.$transaction(work)
});
app.register(db);
app.get('/users', () => db.client.user.findMany());This keeps connection setup, health, transactions and shutdown explicit without turning the runtime into an ORM.
When the application splits, the programming model grows.
The preferred service-to-service surface in OpenMesh 0.5 is app.mesh(name). A handle is lazy. The first real call creates one managed service relationship and reuses its discovery watch, pressure budget, circuit state, traffic policy and transport resources.
const payments = app.mesh('payments');
const charge = await payments.post('/charges', {
key: user.id,
body: {
userId: user.id,
amount: order.total
}
});The point is not to hide the network. The point is to stop every caller from re-implementing the same discovery, deadline, retry and observability glue with slightly different failure semantics.
Use the layer that matches the system you actually have.
| Stage | OpenMesh surface | What stays optional |
|---|---|---|
| Single API | HTTP runtime + plugins | control plane, mesh, Redis, telemetry |
| API + database | openmesh-node/db | mesh and discovery |
| Several services | app.mesh() + control plane | mTLS, Redis, advanced policy |
| Production mesh | traffic policy, backpressure, identity, observability | components you do not need |
OpenMesh is intentionally not a hidden cluster platform. Certificate issuance, orchestration, DNS, infrastructure provisioning and Kubernetes networking remain external responsibilities. That boundary makes the runtime easier to reason about and keeps adoption incremental.
Read the OpenMesh 0.5 documentation or the full 0.5 architecture for exact APIs and limits.