Skip to content

tRPC v12 isn’t on npm yet. The @trpc12/* packages on this site can’t be installed until it’s released. Use v11 today

tRPC

wsLink

Every call over one WebSocket, with connection params, keep-alive pings and reconnects that resume subscriptions.

On this page

wsLink runs queries, mutations and subscriptions over one WebSocket, which it opens on the first call. The handler serves WebSockets on the same endpoint as HTTP (Adapters).

import { function createTrpcClient<TSource, const TLinks extends ReadonlyArray<TrpcLink<any, RouterOf<TSource>>>>(opts: TypedClientOptions<TSource, TLinks> & DuplicateSwitchCheck<TLinks> & UnmetNeedCheck<TLinks>): TrpcClient<RouterOf<TSource>, LinksContext<TLinks>, undefined, LinksTypes<TLinks>>createTrpcClient, function routerType<TRouter>(): RouterType<TRouter>routerType, 
function wsLink<TOpts extends WebSocketLinkOptions>(opts: TOpts): SocketLink<{
    form: FormOfOptions<TOpts>;
}>
wsLink
} from '@trpc12/trpc/client';
const
const ws: SocketLink<{
    form: JsonForm;
}>
ws
=
wsLink<{
    url: string;
    connectionParams: () => Promise<{
        token: string;
    }>;
}>(opts: {
    url: string;
    connectionParams: () => Promise<{
        token: string;
    }>;
}): SocketLink<{
    form: JsonForm;
}>
wsLink
({
url: stringurl: 'ws://localhost:3000/trpc',
connectionParams: () => Promise<{
    token: string;
}>
connectionParams
: async () => ({ token: stringtoken: await function getToken(): Promise<string>getToken() }),
}); const
const client: TrpcClient<{
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<ZodObject<{
                id: ZodString;
            }, $strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}, {}, undefined, LinksTypes<...>>
client
=
createTrpcClient<RouterType<{
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<ZodObject<{
                id: ZodString;
            }, $strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}>, readonly [...]>(opts: TypedClientOptions<...>): TrpcClient<...>
createTrpcClient
({
TypedClientOptions<RouterType<{ post: { byId: Procedure<{ type: "query"; ctx: Context; meta: {}; input: { id: string; }; output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }> | SchemaValidationError<ZodObject<{ id: ZodString; }, $strip>>; services: never; }>; list: Procedure<...>; create: Procedure<...>; }; }>, readonly [...]>.router: RouterType<{
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<ZodObject<{
                id: ZodString;
            }, $strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}>
router
:
routerType<{
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<ZodObject<{
                id: ZodString;
            }, $strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}>(): RouterType<...>
routerType
<
type AppRouter = {
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<ZodObject<{
                id: ZodString;
            }, $strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}
AppRouter
>(),
TypedClientOptions<RouterType<{ post: { byId: Procedure<{ type: "query"; ctx: Context; meta: {}; input: { id: string; }; output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }> | SchemaValidationError<ZodObject<{ id: ZodString; }, $strip>>; services: never; }>; list: Procedure<...>; create: Procedure<...>; }; }>, readonly [...]>.links: readonly [SocketLink<{
    form: JsonForm;
}>]
links
: [
const ws: SocketLink<{
    form: JsonForm;
}>
ws
],
}); await
const ws: SocketLink<{
    form: JsonForm;
}>
ws
.SocketLink<{ form: JsonForm; }>.close(): Promise<void>close();
  • url: the handler's endpoint.
  • connectionParams: sent once per connection, before any call, or a function that makes them. createContext gets them as info.connectionParams (Context). Browsers can't set WebSocket headers, so send auth tokens here.
  • reconnect: how a dropped connection reconnects, { attempts: 5, delayMs } by default.
  • keepAlive: pings (below).
  • idleMs: close the connection after this long without calls; the next call opens a new one. By default it stays open until close().
  • binary: file bytes in outputs as binary messages instead of base64 text. Servers that don't know binary messages keep sending text.
  • serializer: must match the handler's, and client types follow it (Serializers).
  • WebSocket: a WebSocket implementation, globalThis.WebSocket by default.

close() closes the connection: calls in flight fail with CLIENT_CLOSED_REQUEST, and the next call opens a new connection. The link is also AsyncDisposable, so await using ws = wsLink(…) closes it at the end of the scope.

Reconnects

When the connection drops, subscriptions subscribe again from their last tracked() event id, and queries and mutations in flight fail with NETWORK_ERROR. A subscription made with { reconnect: false } ends with NETWORK_ERROR instead.

Keep-alive

The link pings every 30 s and treats a connection that answers nothing for 10 s as lost, so it reconnects instead of waiting for a close a dead network never delivers. The traffic also keeps proxies from dropping a quiet socket. keepAlive: { intervalMs, timeoutMs } changes the timings, and false sends no pings.

The server closes a socket that sends nothing for 60 s (code 1001) and stops its subscriptions; set createFetchHandler({ websocket: { idleTimeoutMs } }), or false to keep silent sockets open.

The examples/realtime app sends a token in connectionParams, restarts a server under its subscriptions, and has the server drop a client that sends no pings; each one resumes from its last event.