웹소켓
이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →
서버와의 통신 전부 또는 일부에 WebSockets를 사용할 수 있습니다. 클라이언트 설정 방법은 wsLink를 참조하세요.
이 문서는 WebSockets 사용에 관한 구체적인 세부 사항을 설명합니다. 구독의 일반적인 사용법은 구독 가이드를 참조하세요.
WebSocket 서버 생성
bashyarn add ws
bashyarn add ws
server/wsServer.tstsimport { applyWSSHandler } from '@trpc/server/adapters/ws';import ws from 'ws';import { appRouter } from './routers/app';import { createContext } from './trpc';const wss = new ws.Server({port: 3001,});const handler = applyWSSHandler({wss,router: appRouter,createContext,// Enable heartbeat messages to keep connection open (disabled by default)keepAlive: {enabled: true,// server ping message interval in millisecondspingMs: 30000,// connection is terminated if pong message is not received in this many millisecondspongWaitMs: 5000,},});wss.on('connection', (ws) => {console.log(`➕➕ Connection (${wss.clients.size})`);ws.once('close', () => {console.log(`➖➖ Connection (${wss.clients.size})`);});});console.log('✅ WebSocket Server listening on ws://localhost:3001');process.on('SIGTERM', () => {console.log('SIGTERM');handler.broadcastReconnectNotification();wss.close();});
server/wsServer.tstsimport { applyWSSHandler } from '@trpc/server/adapters/ws';import ws from 'ws';import { appRouter } from './routers/app';import { createContext } from './trpc';const wss = new ws.Server({port: 3001,});const handler = applyWSSHandler({wss,router: appRouter,createContext,// Enable heartbeat messages to keep connection open (disabled by default)keepAlive: {enabled: true,// server ping message interval in millisecondspingMs: 30000,// connection is terminated if pong message is not received in this many millisecondspongWaitMs: 5000,},});wss.on('connection', (ws) => {console.log(`➕➕ Connection (${wss.clients.size})`);ws.once('close', () => {console.log(`➖➖ Connection (${wss.clients.size})`);});});console.log('✅ WebSocket Server listening on ws://localhost:3001');process.on('SIGTERM', () => {console.log('SIGTERM');handler.broadcastReconnectNotification();wss.close();});
TRPCClient를 WebSockets로 설정하기
쿼리 및/또는 뮤테이션은 HTTP 전송으로, 구독은 WebSockets로 라우팅하도록 링크를 사용할 수 있습니다.
client.tstsximport { createTRPCClient, createWSClient, wsLink } from '@trpc/client';import type { AppRouter } from '../path/to/server/trpc';// create persistent WebSocket connectionconst wsClient = createWSClient({url: `ws://localhost:3001`,});// configure TRPCClient to use WebSockets transportconst client = createTRPCClient<AppRouter>({links: [wsLink({client: wsClient,}),],});
client.tstsximport { createTRPCClient, createWSClient, wsLink } from '@trpc/client';import type { AppRouter } from '../path/to/server/trpc';// create persistent WebSocket connectionconst wsClient = createWSClient({url: `ws://localhost:3001`,});// configure TRPCClient to use WebSockets transportconst client = createTRPCClient<AppRouter>({links: [wsLink({client: wsClient,}),],});
인증 / 연결 매개변수
웹 애플리케이션을 개발 중이라면, 쿠키가 요청의 일부로 전송되므로 이 섹션을 무시해도 됩니다.
WebSockets 인증을 위해 createWSClient에 connectionParams를 정의할 수 있습니다. 이는 클라이언트가 WebSocket 연결을 설정할 때 첫 번째 메시지로 전송됩니다.
server/context.tstsimport type {CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';export constcreateContext = async (opts :CreateWSSContextFnOptions ) => {consttoken =opts .info .connectionParams ?.token ;// [... authenticate]return {};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;
server/context.tstsimport type {CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';export constcreateContext = async (opts :CreateWSSContextFnOptions ) => {consttoken =opts .info .connectionParams ?.token ;// [... authenticate]return {};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;
client/trpc.tstsimport { createTRPCClient, createWSClient, wsLink } from '@trpc/client';import type { AppRouter } from '~/server/routers/_app';const wsClient = createWSClient({url: `ws://localhost:3000`,connectionParams: async () => {return {token: 'supersecret',};},});export const trpc = createTRPCClient<AppRouter>({links: [wsLink({ client: wsClient, transformer: superjson })],});
client/trpc.tstsimport { createTRPCClient, createWSClient, wsLink } from '@trpc/client';import type { AppRouter } from '~/server/routers/_app';const wsClient = createWSClient({url: `ws://localhost:3000`,connectionParams: async () => {return {token: 'supersecret',};},});export const trpc = createTRPCClient<AppRouter>({links: [wsLink({ client: wsClient, transformer: superjson })],});
tracked()를 사용한 ID 자동 추적 (권장)
tracked() 헬퍼를 사용해 이벤트를 yield하고 id를 포함하면, 클라이언트는 연결이 끊겼을 때 자동으로 재연결하며 재연결 시 lastEventId 입력으로 마지막으로 확인된 ID를 전송합니다.
구독 초기화 시 초기 lastEventId를 전송할 수 있으며, 브라우저가 데이터를 수신하면 자동으로 업데이트됩니다.
lastEventId 기반 데이터 조회 시 모든 이벤트 수집이 중요한 경우, 전체 스택 SSE 예시에서처럼 ReadableStream 또는 유사한 패턴을 중간 계층으로 사용해 lastEventId 기반 원본 배치를 yield하는 동안 새로 발생한 이벤트가 무시되는 것을 방지할 수 있습니다.
tsimport EventEmitter, { on } from 'events';import { tracked } 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(),}).optional(),).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', {// Passing the AbortSignal from the request automatically cancels the event emitter when the subscription is abortedsignal: opts.signal,})) {const post = data as Post;// tracking the post id ensures the client can reconnect at any time and get the latest events this idyield tracked(post.id, post);}}),});
tsimport EventEmitter, { on } from 'events';import { tracked } 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(),}).optional(),).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', {// Passing the AbortSignal from the request automatically cancels the event emitter when the subscription is abortedsignal: opts.signal,})) {const post = data as Post;// tracking the post id ensures the client can reconnect at any time and get the latest events this idyield tracked(post.id, post);}}),});
WebSockets RPC 사양
자세한 내용은 TypeScript 정의를 확인하세요:
query / mutation
요청
ts{id: number | string;jsonrpc?: '2.0'; // optionalmethod: 'query' | 'mutation';params: {path: string;input?: unknown; // <-- pass input of procedure, serialized by transformer};}
ts{id: number | string;jsonrpc?: '2.0'; // optionalmethod: 'query' | 'mutation';params: {path: string;input?: unknown; // <-- pass input of procedure, serialized by transformer};}
응답
... 또는 아래 오류 발생
ts{id: number | string;jsonrpc?: '2.0'; // only defined if included in requestresult: {type: 'data'; // always 'data' for mutation / queriesdata: TOutput; // output from procedure}}
ts{id: number | string;jsonrpc?: '2.0'; // only defined if included in requestresult: {type: 'data'; // always 'data' for mutation / queriesdata: TOutput; // output from procedure}}
subscription / subscription.stop
구독 시작
ts{id: number | string;jsonrpc?: '2.0';method: 'subscription';params: {path: string;input?: unknown; // <-- pass input of procedure, serialized by transformer};}
ts{id: number | string;jsonrpc?: '2.0';method: 'subscription';params: {path: string;input?: unknown; // <-- pass input of procedure, serialized by transformer};}
구독 취소를 위해 subscription.stop 호출
ts{id: number | string; // <-- id of your created subscriptionjsonrpc?: '2.0';method: 'subscription.stop';}
ts{id: number | string; // <-- id of your created subscriptionjsonrpc?: '2.0';method: 'subscription.stop';}
구독 응답 형태
... 또는 아래 오류 발생
ts{id: number | string;jsonrpc?: '2.0';result: (| {type: 'data';data: TData; // subscription emitted data}| {type: 'started'; // subscription started}| {type: 'stopped'; // subscription stopped})}
ts{id: number | string;jsonrpc?: '2.0';result: (| {type: 'data';data: TData; // subscription emitted data}| {type: 'started'; // subscription started}| {type: 'stopped'; // subscription stopped})}
연결 매개변수
연결이 ?connectionParams=1로 초기화된 경우, 첫 번째 메시지는 연결 매개변수여야 합니다.
ts{data: Record<string, string> | null;method: 'connectionParams';}
ts{data: Record<string, string> | null;method: 'connectionParams';}
오류
https://www.jsonrpc.org/specification#error_object 또는 오류 형식 참조.
서버→클라이언트 알림
{ id: null, type: 'reconnect' }
서버 종료 전 클라이언트에게 재연결을 지시합니다. wssHandler.broadcastReconnectNotification()로 호출됩니다.