Skip to content

WebSocket Adapters

oRPC supports WebSockets for low-latency, full-duplex communication between clients and servers.

Server Adapters

AdapterTarget
websocketMDN WebSocket API, ws
crosswscrossws
ts
import { WebSocketServer } from 'ws'
import { RPCHandler } from '@orpc/server/websocket'
import { onError } from '@orpc/server'

const handler = new RPCHandler(router, {
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
})

const wss = new WebSocketServer({ port: 8080 })

wss.on('connection', (ws) => {
  handler.upgrade(ws, {
    /**
     * Provide initial context if needed. The context can be an async function
     * that receives the per-call request as its first argument, and is **not**
     * related to the initial WebSocket upgrade request.
     */
    context: request => ({}),
  })
})
ts
import { createServer } from 'node:http'
import { experimental_RPCHandler as RPCHandler } from '@orpc/server/crossws'
import { onError } from '@orpc/server'

// any crossws adapter is supported
import crossws from 'crossws/adapters/node'

const handler = new RPCHandler(router, {
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
})

const ws = crossws({
  hooks: {
    message: (peer, message) => {
      handler.message(peer, message, {
        /**
         * Provide initial context if needed. The context can be an async function
         * that receives the per-call request as its first argument, and is **not**
         * related to the initial WebSocket upgrade request.
         */
        context: request => ({}),
      })
    },
    close: (peer) => {
      handler.close(peer)
    },
  },
})

const server = createServer((req, res) => {
  res.end(`Hello World`)
}).listen(3000)

server.on('upgrade', (req, socket, head) => {
  if (req.headers.upgrade === 'websocket') {
    ws.handleUpgrade(req, socket, head)
  }
})
ts
import { RPCHandler } from '@orpc/server/websocket'
import { onError } from '@orpc/server'

const handler = new RPCHandler(router, {
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
})

Deno.serve((req) => {
  if (req.headers.get('upgrade') !== 'websocket') {
    return new Response(null, { status: 501 })
  }

  const { socket, response } = Deno.upgradeWebSocket(req)

  handler.upgrade(socket, {
    /**
     * Provide initial context if needed. The context can be an async function
     * that receives the per-call request as its first argument, and is **not**
     * related to the initial WebSocket upgrade request.
     */
    context: request => ({}),
  })

  return response
})
ts
import { RPCHandler } from '@orpc/server/websocket'
import { onError } from '@orpc/server'

const handler = new RPCHandler(router, {
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
})

export class ChatRoom extends DurableObject {
  async fetch(): Promise<Response> {
    const { '0': client, '1': server } = new WebSocketPair()

    this.ctx.acceptWebSocket(server)

    return new Response(null, {
      status: 101,
      webSocket: client,
    })
  }

  async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
    await handler.message(ws, message, {
      /**
       * Provide initial context if needed. The context can be an async function
       * that receives the per-call request as its first argument, and is **not**
       * related to the initial WebSocket upgrade request.
       */
      context: request => ({}),
    })
  }

  async webSocketClose(ws: WebSocket): Promise<void> {
    handler.close(ws)
  }
}

Client Adapters

AdapterTarget
websocketMDN WebSocket API
ts
import { RPCLink } from '@orpc/client/websocket'

const link = new RPCLink({
  connect: info => new WebSocket('ws://localhost:3000'),
  /**
   * Whether to connect immediately on initialization, instead of waiting
   * for the first call. Reduces latency for the first request.
   *
   * @default false
   */
  connectOnInit: true,

  /**
   * Optional headers to attach to each per-call request.
   * These can be accessed in the server context or via the Request Headers Plugin.
   */
  headers: () => ({})
})

INFO

The examples above only show how to configure the link. For examples of creating a typesafe client, see RPC Link.

Auto Reconnect

The client adapter has built-in support for reconnecting when the connection is lost. You can configure reconnect behavior with the reconnect option when creating the link.

ts
const link = new RPCLink({
  reconnect: {
    /**
     * Whether to automatically reconnect when the connection is lost.
     *
     * @default false
     */
    enabled: true,

    /**
     * Delay before a (re)connect attempt, in milliseconds.
     *
     * @default info => info.attempt === 1 ? 0 : 2_000
     */
    delay: info => info.attempt === 1 ? 0 : 2_000,

    /**
     * Maximum number of consecutive failed attempts before giving up.
     * When exceeded, `getConnectedPeer` throws instead of retrying.
     * Should greater than 1
     *
     * @default Infinity
     */
    maxAttempt: Infinity,

    onClose: {
      /**
       * Whether to proactively reconnect right after the socket closes,
       * rather than waiting for the next call to trigger reconnection.
       * Reduces latency for the next request.
       *
       * @default false
       */
      enabled: false,

      /**
       * Delay before reconnecting after the socket closes, in milliseconds.
       *
       * @default 0
       */
      delay: 0
    }
  }
})

Released under the MIT License.