How Tauri JSON-RPC Bridges Rust Core and React Frontend in OpenHuman
OpenHuman uses JSON-RPC 2.0 over a local HTTP endpoint to enable type-safe communication between the Rust core binary and the React/Vite frontend, with Tauri IPC handling authentication and endpoint discovery.
OpenHuman is an open-source AI interface that separates business logic from UI concerns using a Rust core and React frontend. The architecture implements Tauri JSON-RPC to create a language-agnostic bridge, allowing the core to run as a standalone binary while the React layer communicates through a well-defined RPC surface. This design ensures the core remains testable independently of the desktop shell.
Transport Layer: The HTTP JSON-RPC Server
The Rust core exposes its functionality via a lightweight HTTP server defined in src/core/jsonrpc.rs. When the core starts, it binds to a random localhost port and listens for standard JSON-RPC 2.0 requests.
The server expects envelopes following the {jsonrpc:"2.0", id, method, params} format and routes each request to the appropriate domain controller through crate::core::jsonrpc::invoke_method. This transport layer is deliberately kept thin; domain logic resides in separate modules under src/openhuman/*/schemas, ensuring the JSON-RPC implementation remains purely concerned with request routing and serialization.
Endpoint Discovery: Tauri IPC Commands
Since the core runs as a separate process with a dynamic port, the Tauri layer must broker the connection details. In app/src-tauri/src/core_process.rs, the desktop application launches the core binary and captures the RPC URL (e.g., http://127.0.0.1:57123/rpc) along with a bearer token stored in OPENHUMAN_CORE_TOKEN.
These values are exposed to the JavaScript side through dedicated Tauri commands defined in app/src-tauri/src/core_rpc.rs:
#[tauri::command]
fn core_rpc_url() -> String { … }
#[tauri::command]
fn core_rpc_token() -> Option<String> { … }
The frontend invokes these commands to retrieve the endpoint address and authentication token before making any RPC calls.
Frontend Implementation: The TypeScript RPC Client
The React frontend consumes the core API through a centralized client in app/src/services/coreRpcClient.ts. The callCoreRpc function orchestrates the full request lifecycle:
- Retrieves the endpoint URL and bearer token via Tauri IPC
- Constructs a JSON-RPC 2.0 payload
- Issues an authenticated
fetchrequest - Normalizes responses, converting JSON-RPC errors into JavaScript exceptions
export async function callCoreRpc<T>(method: string, params?: unknown): Promise<T> {
const url = await invoke('core_rpc_url');
const token = await invoke('core_rpc_token');
const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params });
const resp = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body,
});
const { result, error } = await resp.json();
if (error) throw new CoreRpcError(error);
return result as T;
}
This abstraction ensures all network concerns are handled in one location, providing consistent error handling and authentication for every core interaction.
Developer Experience: Typed Command Wrappers
To maintain type safety and simplify domain logic access, OpenHuman provides thin wrapper modules under app/src/utils/tauriCommands/. Files like config.ts and auth.ts import callCoreRpc and expose strongly-typed functions that forward method names and parameters to the core.
For example, configuration operations are wrapped in app/src/utils/tauriCommands/config.ts, allowing UI components to call domain-specific methods without constructing raw JSON-RPC payloads. This pattern keeps the React code clean while ensuring the TypeScript types match the Rust schema definitions.
Boot Sequence: Initializing the Connection
The connection between React and Rust follows a strict initialization sequence:
- Core Launch:
app/src-tauri/src/core_process.rsspawns the core binary, often with flags like--jsonrpc-only - Endpoint Capture: The core prints its RPC URL to stdout, which Tauri captures via
CoreProcessHandle - UI Hydration: The React app loads and calls
core_rpc_url()andcore_rpc_token()to cache connection details - Runtime Communication: UI components invoke
tauriCommandwrappers, which translate to authenticated JSON-RPC requests against the core
This sequence ensures the frontend never attempts to connect before the core is ready and authenticated.
Why JSON-RPC?
OpenHuman chose JSON-RPC 2.0 over Tauri's native IPC for several architectural reasons:
- Language Agnosticism: The same RPC definitions serve the Rust core, CLI tools, and integration tests without modification
- Structured Error Handling: JSON-RPC's standardized
errorobject maps cleanly to Rust'sRpcOutcomeand JavaScript exceptions, providing consistent error propagation across the boundary - Extensibility: Adding new capabilities requires only a schema file in the Rust core and a corresponding TypeScript wrapper; the transport layer remains unchanged
Summary
- Transport:
src/core/jsonrpc.rsruns an HTTP server speaking JSON-RPC 2.0 on a random localhost port - Discovery: Tauri commands in
app/src-tauri/src/core_rpc.rsexpose the endpoint URL and bearer token to the frontend - Client:
app/src/services/coreRpcClient.tshandles authenticatedfetchrequests and error normalization - Wrappers:
app/src/utils/tauriCommands/*.tsprovide type-safe access to core domain logic - Security: Bearer token authentication (
OPENHUMAN_CORE_TOKEN) prevents unauthorized local access to the core API
Frequently Asked Questions
What protocol does OpenHuman use for Rust-to-React communication?
OpenHuman uses JSON-RPC 2.0 over HTTP. The Rust core implements a local HTTP server that accepts standard JSON-RPC envelopes, while the React frontend sends authenticated POST requests via fetch. This approach provides a language-agnostic interface that works identically whether called from the desktop UI, CLI tools, or automated tests.
How does the frontend authenticate with the Rust core?
Authentication uses a bearer token generated by the core during startup and stored in the OPENHUMAN_CORE_TOKEN environment variable. The Tauri layer exposes this token via the core_rpc_token() command, and the TypeScript client includes it in the Authorization: Bearer ... header of every JSON-RPC request. This ensures only the authorized Tauri process can invoke core methods.
Why use JSON-RPC instead of Tauri's native command system?
While Tauri provides direct Rust-to-JavaScript commands, OpenHuman uses JSON-RPC to maintain a strict separation between the UI shell and business logic. The JSON-RPC approach allows the core to run as a standalone binary (enabling CLI usage and headless testing) while keeping the React frontend decoupled from Tauri-specific APIs. It also provides standardized error handling and makes the API discoverable by any HTTP client.
Where is the RPC endpoint configured during startup?
The endpoint is dynamically assigned. When app/src-tauri/src/core_process.rs launches the core, the core binds to a random available port and prints the URL. Tauri captures this output and stores the URL, making it available via the core_rpc_url() command. The React app retrieves this value at runtime rather than using a hardcoded address, preventing port conflicts on different machines.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →