Skip to content

Exposing Services

Strata services communicate over a message bus (Redis). That's great for service-to-service communication, but browsers, mobile apps, CLI tools, and services in other languages can't talk to Redis directly.

The RPC Bridge solves this by sitting between your services and the outside world, translating HTTP and WebSocket requests into Strata service calls.

Architecture

┌──────────────┐     ┌───────┐     ┌────────────┐     ┌──────────────────┐
│   Browsers   │     │       │     │            │     │  Strata Service  │
│  Mobile Apps │────▶│  RPC  │────▶│   Redis    │────▶│  Strata Service  │
│  CLI Tools   │◀────│ Bridge│◀────│  (backend) │◀────│  Strata Service  │
│  Other APIs  │     │       │     │            │     │                  │
└──────────────┘     └───────┘     └────────────┘     └──────────────────┘
    HTTP / WS         Strata           Strata            Strata
                      Client          Protocol           Services

External consumers send HTTP POST requests or Socket.IO messages to the bridge. The bridge translates them into Strata requests, sends them through Redis, and returns the responses.

Quick Start

A standalone bridge in under 20 lines:

typescript
import { RPCBridgeServer } from '@strata-js/rpcbridge';

const bridge = new RPCBridgeServer({
    server: {
        enableHTTP: true,
        enableWS: true,
        port: 3000,
        path: '/rpc',
        excludeStack: true,
        excludeUnsafeMessages: true,
    },
    strata: {
        backend: { type: 'redis', redis: { host: 'localhost', port: 6379 } },
        client: { name: 'RPCBridge' },
    },
    globalBlockList: [],
    services: {
        users: { serviceGroup: 'UserService' },
        orders: { serviceGroup: 'OrderService' },
    },
});

bridge.startListening();

This starts an Express server on port 3000 with both HTTP and WebSocket endpoints at /rpc. Any request to the users service name gets forwarded to the UserService service group on Redis.

Integrating with an Existing Server

If you already have an Express app, you don't need a separate process for the bridge. Mount it alongside your existing routes:

typescript
import http from 'http';
import express from 'express';
import { Server as SIOServer } from 'socket.io';
import { RPCBridgeServer } from '@strata-js/rpcbridge';

const app = express();
const server = http.createServer(app);
const io = new SIOServer(server);

// Your own routes
app.get('/health', (_req, res) => { res.send('ok'); });

// Mount the bridge
new RPCBridgeServer(bridgeConfig, undefined, {
    expressApp: app,
    httpServer: server,
    ioServer: io,
});

server.listen(3000);

When providing external server instances, you manage the listener yourself — don't call startListening().

This pattern is useful when your app already has Express middleware for auth, logging, or CORS that you want the bridge to share.

Controlling Access

By default, every operation on a registered service is reachable through the bridge. You should always restrict what's exposed.

Block Lists

Deny specific operations while allowing everything else:

typescript
services: {
    users: {
        serviceGroup: 'UserService',
        blockList: [ 'admin:*', 'internal:*' ],
    },
},

Allow Lists

Only permit specific operations — everything else is denied:

typescript
services: {
    orders: {
        serviceGroup: 'OrderService',
        allowList: [ 'cart:get', 'cart:update', 'checkout:submit' ],
    },
},

Allow lists are the safer default. If you're exposing a service to the public internet, prefer allow lists so new operations aren't automatically reachable.

Global Block List

Applies to every service. Use this for patterns that should never be exposed:

typescript
globalBlockList: [ 'system:*', 'internal:*', 'debug:*' ],

See the RPC Bridge reference for full evaluation order details.

Adding Middleware

The bridge supports middleware for both HTTP and WebSocket endpoints, injected as the second constructor argument.

HTTP Middleware

Standard Express middleware. Runs before the request is forwarded to Strata:

typescript
import type { HTTPMiddleware } from '@strata-js/rpcbridge';

const authMiddleware : HTTPMiddleware = (req, _res, next) =>
{
    // Inject auth token from HTTP headers into the Strata request
    req.body.auth = req.headers.authorization;
    next();
};

const logger : HTTPMiddleware = (req, _res, next) =>
{
    console.log(`${ req.body.serviceName }/${ req.body.context }:${ req.body.operation }`);
    next();
};

WebSocket Middleware

Receives the Socket.IO socket and the parsed RPC request:

typescript
import type { SocketMiddleware } from '@strata-js/rpcbridge';

const wsAuth : SocketMiddleware = (socket, rpcRequest, next) =>
{
    // Pull auth from the socket's handshake
    rpcRequest.auth = socket.handshake.auth?.token;
    next();
};

const wsLogger : SocketMiddleware = (_socket, rpcRequest, next) =>
{
    console.log(`WS: ${ rpcRequest.serviceName }/${ rpcRequest.context }:${ rpcRequest.operation }`);
    next();
};

Applying Middleware

typescript
new RPCBridgeServer(config, {
    http: [ authMiddleware, logger ],
    socket: [ wsAuth, wsLogger ],
});

Calling the Bridge

HTTP

bash
curl -X POST http://localhost:3000/rpc \
    -H 'Content-Type: application/json' \
    -d '{
        "serviceName": "users",
        "context": "profile",
        "operation": "get",
        "payload": { "userId": "abc123" }
    }'

WebSocket

typescript
import { WebSocketClient } from '@strata-js/rpcbridge-client';

const client = new WebSocketClient('http://localhost:3000/rpc');
const user = await client.request('users', 'profile', 'get', { userId: 'abc123' });

For the full browser story — connection lifecycle, auth tokens, error handling — see the Building a Web UI guide.

Next Steps