HTTP Subscription Link
httpSubscriptionLink
is a terminating link that's uses Server-sent Events (SSE) for subscriptions.
SSE is a good option for real-time as it's a bit easier to deal with than WebSockets and handles things like reconnecting and continuing where it left off automatically.
Setup
info
If your client's environment doesn't support EventSource, you need an EventSource polyfill.
To use httpSubscriptionLink
, you need to use a splitLink to make it explicit that we want to use SSE for subscriptions.
client/index.tsts
import type { TRPCLink } from '@trpc/client';import {httpBatchLink,loggerLink,splitLink,unstable_httpSubscriptionLink,} from '@trpc/client';const trpcClient = createTRPCClient<AppRouter>({/*** @link https://trpc.io/docs/v11/client/links*/links: [// adds pretty logs to your console in development and logs errors in productionloggerLink(),splitLink({// uses the httpSubscriptionLink for subscriptionscondition: (op) => op.type === 'subscription',true: unstable_httpSubscriptionLink({url: `/api/trpc`,}),false: httpBatchLink({url: `/api/trpc`,}),}),],});
client/index.tsts
import type { TRPCLink } from '@trpc/client';import {httpBatchLink,loggerLink,splitLink,unstable_httpSubscriptionLink,} from '@trpc/client';const trpcClient = createTRPCClient<AppRouter>({/*** @link https://trpc.io/docs/v11/client/links*/links: [// adds pretty logs to your console in development and logs errors in productionloggerLink(),splitLink({// uses the httpSubscriptionLink for subscriptionscondition: (op) => op.type === 'subscription',true: unstable_httpSubscriptionLink({url: `/api/trpc`,}),false: httpBatchLink({url: `/api/trpc`,}),}),],});
Usage
tip
For a full example, see our full-stack SSE example.
ts
import EventEmitter, { on } from 'events';import type { Post } from '@prisma/client';import { sse } from '@trpc/server';import { z } from 'zod';import { publicProcedure, router } from '../trpc';const ee = new EventEmitter();export const subRouter = router({onPostAdd: publicProcedure.input(z.object({// lastEventId is the last event id that the client has received// On the first call, it will be whatever was passed in the initial setup// If the client reconnects, it will be the last event id that the client receivedlastEventId: z.string().nullish(),}),).subscription(async function* (opts) {if (opts.input.lastEventId) {// [...] get the posts since the last event id and yield them}// listen for new eventsfor await (const [data] of on(ee, 'add')) {const post = data as Post;yield sse({// yielding the post id ensures the client can reconnect at any time and get the latest events this idid: post.id,data: post,});}}),});
ts
import EventEmitter, { on } from 'events';import type { Post } from '@prisma/client';import { sse } from '@trpc/server';import { z } from 'zod';import { publicProcedure, router } from '../trpc';const ee = new EventEmitter();export const subRouter = router({onPostAdd: publicProcedure.input(z.object({// lastEventId is the last event id that the client has received// On the first call, it will be whatever was passed in the initial setup// If the client reconnects, it will be the last event id that the client receivedlastEventId: z.string().nullish(),}),).subscription(async function* (opts) {if (opts.input.lastEventId) {// [...] get the posts since the last event id and yield them}// listen for new eventsfor await (const [data] of on(ee, 'add')) {const post = data as Post;yield sse({// yielding the post id ensures the client can reconnect at any time and get the latest events this idid: post.id,data: post,});}}),});
httpSubscriptionLink
Options
ts
type HTTPSubscriptionLinkOptions<TRoot extends AnyClientTypes> = {/*** The URL to connect to (can be a function that returns a URL)*/url: string | (() => MaybePromise<string>);/*** EventSource options*/eventSourceOptions?: EventSourceInit;/*** Data transformer* @link https://trpc.io/docs/v11/data-transformers**/transformer?: DataTransformerOptions;};
ts
type HTTPSubscriptionLinkOptions<TRoot extends AnyClientTypes> = {/*** The URL to connect to (can be a function that returns a URL)*/url: string | (() => MaybePromise<string>);/*** EventSource options*/eventSourceOptions?: EventSourceInit;/*** Data transformer* @link https://trpc.io/docs/v11/data-transformers**/transformer?: DataTransformerOptions;};