Specta LogoSpecta
1.0.0-rc.x (alpha)

Vanilla

Call procedures with the tuple client and configure authentication.

The vanilla client calls queries and mutations without a frontend framework. Use it directly or pass its request functions to TanStack Query.

Install and export bindings

npm install @rspc/client@0.3.1

Follow the quickstart to build the server and export bindings. Keep rspc's default legacy feature enabled for this tuple-based client:

let (procedures, types) = router().build().expect("invalid router");
rspc::Typescript::default()
    .export_to("./bindings.ts", &types)
    .expect("failed to export bindings");

Create a client

import { createClient, FetchTransport } from "@rspc/client";
import type { ProceduresLegacy } from "./bindings";

export const client = createClient<ProceduresLegacy>({
  transport: new FetchTransport("/rspc"),
});

const version = await client.query(["version", null]);
const greeting = await client.mutation(["greet", "Ada"]);

The tuple starts with a procedure path and includes its input when required. These calls use the procedures from the quickstart. Keep the bindings and server in sync; the TypeScript generic does not check which server is running.

The rewrite exports () as null, so its no-input procedures need a second tuple item of null. Do not copy the shorter tuple from a 0.3.0 example.

The relative URL assumes a same-origin endpoint. Use a development proxy or the API's full URL with a matching CORS configuration.

Fetch transport

FetchTransport uses the standard fetch API. Queries use GET with JSON in the input parameter. Mutations use POST with a JSON body. This transport does not support subscriptions.

Custom fetch implementation

Pass a fetch function as the second constructor argument. For cross-origin cookie sessions:

const transport = new FetchTransport(
  "https://api.example.com/rspc",
  (input, init) => fetch(input, { ...init, credentials: "include" }),
);

The server must allow the frontend's explicit origin and credentials. A wildcard origin does not work for credentialed requests. Cookie sessions also need a CSRF policy for state-changing calls.

Fetch authentication

Add bearer credentials through the custom fetch function:

const transport = new FetchTransport("/rspc", (input, init) => {
  const headers = new Headers(init?.headers);
  const token = getAccessToken();
  if (token) headers.set("Authorization", `Bearer ${token}`);
  return fetch(input, { ...init, headers });
});

getAccessToken is your application's token lookup function. Read it for each request so refreshed credentials are used. The server must verify the credential and authorize access. Do not log tokens or put a long-lived secret in the URL.

WebSocket transport

Use the WebSocket transport when the server and client support the same subscription protocol:

import { createClient, WebsocketTransport } from "@rspc/client";
import type { ProceduresLegacy } from "./bindings";

const client = createClient<ProceduresLegacy>({
  transport: new WebsocketTransport("ws://localhost:4000/rspc/ws"),
});

Enable the Axum adapter's ws feature. Use wss:// when the site uses HTTPS. After the upgrade, this transport sends WebSocket messages, not HTTP GET or POST requests.

WebSocket authentication

Browser WebSockets cannot set arbitrary authorization headers. This transport accepts only a URL and has no custom handshake-header callback.

A same-origin cookie session can authenticate the upgrade request. Validate the origin on the server and recheck authorization for protected operations. After login or logout, do not assume an existing connection has new credentials; reconnect or use an application-specific reauthentication flow.

For bearer-token authentication without cookies, use HTTP fetch or implement a transport with a defined authentication exchange. Do not put long-lived tokens in query strings.

Single-flight mutations

The tuple client makes a separate request for each mutation. The server-driven invalidation prototype is incomplete, so do not expect a mutation to return updated query data automatically. Invalidate or refetch affected frontend queries after the mutation succeeds.

Edit on GitHub

Last updated on

On this page