Creating Realtime API with Jumentix
Responsibility in context
- Owns: Realtime adapters (Socket.IO / gRPC) around the same use-cases
- Used with: sdk-websocket-client, sdk-grpc-client, shared-contracts
- Not responsible for: Plain REST-only flows or browser IndexedDB
What it is
This guide covers enabling push and bidirectional communication on top of the backend template using WebSocket (browser-friendly) or gRPC (server-to-server), with REST kept as an operational fallback.
Why it exists
Many products need live updates — chat, notifications, dashboards — but teams bolt WebSocket code onto REST controllers and lose contract alignment. Jumentix treats AsyncAPI like OpenAPI: channels and payloads are specified first, handlers invoke the same controllers and use cases as REST, and degraded realtime falls back to HTTP.
Prerequisites
| Item | Required | Notes |
|---|---|---|
| Bun 1.3.13+ | Yes | Getting started |
| Working REST profile | Recommended | Complete REST guide first |
| AsyncAPI specs | Yes | Under spec/ alongside OpenAPI |
| Env file | Yes | apps/backend-template/src/config/.env.dev |
Glossary
| Term | Meaning on this page |
|---|---|
| Realtime API | Non-HTTP primary interface — WebSocket or gRPC — for push/pull messaging. |
| AsyncAPI | Spec describing channels, messages, and payload schemas for realtime. |
| WebSocket | Browser-friendly persistent connection; default protocol for SPAs. |
| gRPC | Binary RPC protocol; use for service-to-service, not browser clients. |
| REST fallback | HTTP endpoints still available when realtime channel is down or unsupported. |
| Message handler | Adapter code receiving a message, calling a controller, replying on same correlation id. |
| Correlation id | Token linking a client request to its response on the same socket. |
| JUMENTIX_REALTIME_API | Env flag yes/no enabling realtime startup profile. |
Numbered steps
Step 1 — Enable the realtime runtime profile (< 5 minutes)
- Edit your env file:
JUMENTIX_REALTIME_API=yes
JUMENTIX_REALTIME_API_PROTOCOL=websocket # or grpc
JUMENTIX_HTTP_FRAMEWORK=express # REST fallback + docs- Start the matching dev profile from monorepo root:
# WebSocket + REST fallback (start here for browser apps)
bun run dev:websocket
# gRPC + REST fallback (Node service mesh)
bun run dev:grpcFrom apps/backend-template you can also run bun run dev:websocket-rest or
bun run dev:grpc-rest.
Success check: PM2 shows the websocket or grpc process online; REST /health
still responds.
Step 2 — Define Async contracts (< 15 minutes)
- Add or update AsyncAPI files under
spec/— define channels, messages, and payload schemas. - Align payload shapes with domain models and controller method signatures.
- Reference shared error/response contracts in handler implementations — same types as REST DTOs where possible.
- Cross-check Events and messages for naming conventions.
Success check: AsyncAPI channel names match handler registrations; no orphan messages.
Step 3 — Implement message handlers (< 30 minutes)
WebSocket path (browser apps):
- Handler receives framed message on a channel.
- Handler parses payload → calls controller method.
- Handler sends response on the same client connection using the message correlation id.
gRPC path (Node-to-Node):
- gRPC service method receives protobuf request.
- Invokes the same controller used by REST.
- Returns protobuf response mapped from domain result.
Rule: controllers and use cases stay identical — only adapter code differs from REST.
Success check: unit/integration test invokes handler without starting full cluster.
Step 4 — Keep REST fallback available (< 5 minutes)
Realtime services always run with REST as a secondary interface:
- Operational tools and degraded mode use REST endpoints.
- OpenAPI docs remain the backup contract when sockets fail.
- Do not delete REST routes when adding realtime — clients may downgrade gracefully.
Success check: with realtime process stopped, REST endpoints still serve critical reads.
Step 5 — Validate realtime stability (< 10 minutes)
From monorepo root:
bun run test:integration:realtime
bun run test:smoke:realtimeFor Redis-backed Socket.IO scaling (optional advanced path):
bun run test:integration:realtime:redis-streamsSuccess check: both commands exit 0; smoke test covers connect → message → response.
Step 6 — Client integration (junior path)
Prefer WebSocket for browser apps. gRPC stays Node-to-Node — use static snippets on the gRPC adapter page, not a browser Run button.
Static subscribe pattern:
const ws = api.createMockClient();
const sub = await ws.subscribe({ channel: 'notifications' });
sub.on('message', (msg) => console.log(msg.payload));Examples
Interactive WebSocket client playground
WebSocket client with fake socket
Use the realtime client contract to create and list Task records in the browser.
### WebSocket client with fake socket
```ts
const client = api.createFakeClient();
const status = await client.connect();
const created = await client.request({
operationId: 'tasks.create',
input: {
id: 'task-1',
title: 'Render realtime updates',
categoryId: 'home',
completed: false
}
});
const listed = await client.request({
operationId: 'tasks.list',
input: { categoryId: 'home' }
});
const closed = await client.disconnect();
return {
status,
created,
listed,
closed
};
```const client = api.createFakeClient();
const status = await client.connect();
const created = await client.request({
operationId: 'tasks.create',
input: {
id: 'task-1',
title: 'Render realtime updates',
categoryId: 'home',
completed: false
}
});
const listed = await client.request({
operationId: 'tasks.list',
input: { categoryId: 'home' }
});
const closed = await client.disconnect();
return {
status,
created,
listed,
closed
};Success check: Run connects mock client, receives a message; Reset clears state.
Common errors
| Symptom | Likely cause | Fix | Verify success |
|---|---|---|---|
| Realtime starts but REST 404 | HTTP adapter not loaded in combined profile | Keep JUMENTIX_HTTP_FRAMEWORK=express; use dev:websocket not raw socket only | /health returns 200 |
| Handler never replies | Missing correlation id on response | Echo client message id in adapter reply | Integration test green |
| Browser cannot use gRPC | gRPC is not a browser protocol | Use WebSocket for SPA; gRPC for backend services | WS playground Run green |
test:integration:realtime fails on grpc | Wrong JUMENTIX_REALTIME_API_PROTOCOL | Match env to script (websocket vs grpc) | Target protocol test passes |
| Duplicate message handling | Handler registered twice | One registration per channel in adapter bootstrap | Single response per send |
| Redis streams test skipped | RUN_REDIS_INTEGRATION not set | Start redis compose; set env flag | redis-streams integration green |
Junior checklist (“I can …”)
- Set
JUMENTIX_REALTIME_API=yesand choosewebsocketorgrpc. - Start
bun run dev:websocketand confirm REST fallback still works. - Locate AsyncAPI under
spec/and name one channel and its payload schema. - Trace one message: adapter handler → controller → use case.
- Run
bun run test:integration:realtimeandbun run test:smoke:realtime. - Use the sdk-websocket-client playground (Run green).
- Explain when to choose gRPC instead of WebSocket.
Next step
Build the frontend that consumes these channels in Create a SPA or Offline PWA. For adapter details, see WebSocket adapter and gRPC adapter.