# SpacetimeDB Remote Procedure Calls (RPC): Architecture and Implementation Guide

> Explore SpacetimeDB remote procedure calls RPC architecture and implementation. Discover how its unified TypeScript connector benchmarks against SQL databases using identical client code.

- Repository: [Clockwork Labs/SpacetimeDB](https://github.com/clockworklabs/SpacetimeDB)
- Tags: architecture
- Published: 2026-03-09

---

**SpacetimeDB implements a pluggable RPC layer that enables benchmarking against traditional SQL databases through a unified TypeScript connector interface, allowing identical client code to target PostgreSQL, SQLite, CockroachDB, and cloud backends.**

SpacetimeDB remote procedure calls (RPC) provide a modular architecture for executing database operations across heterogeneous backends. This implementation, found in the `clockworklabs/SpacetimeDB` repository, allows developers to benchmark SpacetimeDB’s performance against classic relational stores using a single client abstraction. The system bridges **TypeScript connectors** with HTTP-based server implementations that translate JSON RPC calls into native SQL transactions.

## Client-Side RPC Architecture

### The RpcConnector Interface

All RPC clients implement the `RpcConnector` interface defined in [`templates/keynote-2/src/core/connectors.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/templates/keynote-2/src/core/connectors.ts). This interface extends `BaseConnector` and exposes the primary method signature `call(name, args)`, which returns a `Promise<unknown>` and standardizes how client code invokes remote functions regardless of the underlying database.

```typescript
// Conceptual usage of the RpcConnector interface
const conn = await postgres_rpc();
await conn.call('transfer', { from_id: 1, to_id: 2, amount: 100 });

```

### Type-Safe Request and Response Types

The file [`templates/keynote-2/src/connectors/rpc/rpc_common.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/templates/keynote-2/src/connectors/rpc/rpc_common.ts) establishes shared type definitions that enforce contract consistency across all transports. These types guarantee that every RPC exchange follows a predictable JSON structure.

```typescript
export type RpcRequest = { name?: string; args?: Record<string, unknown> };
export type RpcResponse = 
  | { ok: true; result?: unknown } 
  | { ok: false; error: string };

```

## Server-Side RPC Implementation

### HTTP Endpoint Structure

Each backend provides an HTTP server implementation under `templates/keynote-2/src/rpc-servers/`. These servers expose a single POST endpoint at `/rpc` that accepts an `RpcRequest` payload, dispatches to the appropriate local handler, and returns a typed `RpcResponse`.

### PostgreSQL RPC Server Example

The file [`templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts) demonstrates the standard handler pattern. It parses incoming requests and routes them to specific database functions such as `rpcTransfer`, `rpcGetAccount`, `rpcVerify`, and `rpcSeed`.

```typescript
if (req.method === 'POST' && url.pathname === '/rpc') {
  const { name, args } = (await jsonBody(req)) as RpcRequest;
  let result: RpcResponse;

  switch (name) {
    case 'transfer':
      await rpcTransfer(args ?? {});
      result = { ok: true };
      break;
    case 'get_account':
      result = { ok: true, result: await rpcGetAccount(args ?? {}) };
      break;
    default:
      result = { ok: false, error: `unknown RPC ${name}` };
  }

  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(result));
}

```

### Database-Specific Optimizations

Server implementations leverage database-specific features for performance and correctness. The PostgreSQL server utilizes prepared statements stored in a `PREPARED` constants object to reduce parsing overhead. For transactional integrity during transfers, it employs **row-level locking** via `SELECT … FOR UPDATE` to prevent race conditions between concurrent RPC calls.

## RPC Initialization and Configuration

The orchestration script [`templates/keynote-2/src/init/init_rpc_servers.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/templates/keynote-2/src/init/init_rpc_servers.ts) manages server lifecycle based on the `ENABLE_RPC_SERVERS` environment variable. This design makes the RPC layer entirely optional; developers can activate specific backends (Postgres, SQLite, CockroachDB, Supabase, PlanetScale) by setting the corresponding environment flags (`PG_URL`, `SQLITE_URL`, etc.) without modifying application code. Servers launch via `pnpm tsx`, enabling rapid benchmarking setup.

## Benchmarking with RPC Scenarios

### Single Transfer with Retry Logic

The reusable scenario `rpc_single_call` in [`templates/keynote-2/src/scenario_recipes/rpc_single_call.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/templates/keynote-2/src/scenario_recipes/rpc_single_call.ts) demonstrates production-ready RPC usage with resilient error handling. It automatically retries transient HTTP errors (429, 502, 503, 504) using exponential backoff before failing.

```typescript
export async function rpc_single_call(
  conn: RpcConnector,
  from: number,
  to: number,
  amount: number,
): Promise<void> {
  if (from === to || amount <= 0) return;

  const fn = conn.name === 'convex' && process.env.CONVEX_USE_SHARDED_COUNTER === '1'
    ? 'transfer:transfer_sharded'
    : 'transfer';

  for (let attempts = 0; attempts < 3; attempts++) {
    try {
      await conn.call(fn, { amount, from_id: from, to_id: to });
      return;
    } catch (e: any) {
      const msg = String(e?.message ?? '');
      if (!/429|502|503|504/.test(msg) || attempts === 2) throw e;
      await new Promise(r => setTimeout(r, 50 * (attempts + 1)));
    }
  }
}

```

### Environment-Based Routing

The benchmark scenario automatically adapts its behavior based on runtime configuration. When `CONVEX_USE_SHARDED_COUNTER` is set to `"1"`, the connector switches to a sharded transfer method (`transfer:transfer_sharded`) instead of the standard `transfer` function, enabling performance comparisons between different data partitioning strategies.

## Summary

- **Pluggable Connector Architecture**: The `RpcConnector` interface abstracts PostgreSQL, SQLite, CockroachDB, Supabase, and PlanetScale behind a unified client contract defined in [`templates/keynote-2/src/core/connectors.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/templates/keynote-2/src/core/connectors.ts).
- **Type Safety**: [`rpc_common.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/rpc_common.ts) enforces strict typing for `RpcRequest` and `RpcResponse` payloads across all supported transports.
- **HTTP Translation**: Server implementations in `templates/keynote-2/src/rpc-servers/` handle POST requests to `/rpc`, dispatching to specific functions like `rpcTransfer` with database-specific SQL execution.
- **Production Resilience**: The `rpc_single_call` scenario implements automatic retry logic with exponential backoff for transient HTTP errors (429/502/503/504).
- **Modular Deployment**: [`init_rpc_servers.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/init_rpc_servers.ts) enables selective server activation via environment variables, supporting flexible benchmarking configurations without code changes.

## Frequently Asked Questions

### How does SpacetimeDB handle RPC errors and retries?

The `rpc_single_call` function in [`templates/keynote-2/src/scenario_recipes/rpc_single_call.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/templates/keynote-2/src/scenario_recipes/rpc_single_call.ts) implements a retry mechanism that catches HTTP errors matching patterns 429, 502, 503, or 504. It attempts the call up to three times with increasing delays (50ms, 100ms, 150ms) before throwing the final error to the caller.

### What databases are supported by the SpacetimeDB RPC layer?

The RPC implementation supports PostgreSQL, SQLite, CockroachDB, Supabase, and PlanetScale. Each backend maintains dedicated client connectors in `templates/keynote-2/src/connectors/rpc/` (e.g., [`postgres_rpc.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/postgres_rpc.ts), [`sqlite_rpc.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/sqlite_rpc.ts)) and corresponding HTTP server implementations in `templates/keynote-2/src/rpc-servers/`.

### How are RPC requests typed in SpacetimeDB?

The `RpcRequest` type in [`templates/keynote-2/src/connectors/rpc/rpc_common.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/templates/keynote-2/src/connectors/rpc/rpc_common.ts) defines the contract as an object containing optional `name` (string) and `args` (Record<string, unknown>) properties. The `RpcResponse` type uses a discriminated union with an `ok` boolean to distinguish successful results (`{ ok: true; result?: unknown }`) from failures (`{ ok: false; error: string }`).

### Can I run multiple RPC servers simultaneously for benchmarking?

Yes. The [`templates/keynote-2/src/init/init_rpc_servers.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/templates/keynote-2/src/init/init_rpc_servers.ts) script reads the `ENABLE_RPC_SERVERS` environment variable to selectively launch multiple server processes concurrently. This allows head-to-head performance comparisons where the same client code executes against PostgreSQL, SQLite, and CockroachDB instances simultaneously.