Skip to content

Fetch API Adapter ​

oRPC supports the Fetch API for both servers and clients.

Server Usage ​

ts
import { RPCHandler } from '@orpc/server/fetch'
import { CORSPlugin } from '@orpc/server/plugins'
import { onError } from '@orpc/server'

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

export async function fetch(request: Request): Promise<Response> {
  const { matched, response } = await handler.handle(request, {
    prefix: '/rpc',
    context: {} // Provide initial context if needed
  })

  if (matched) {
    return response
  }

  return new Response('Not found', { status: 404 })
}
ts
import { OpenAPIHandler } from '@orpc/openapi/fetch'
import { CORSPlugin } from '@orpc/server/plugins'
import { onError } from '@orpc/server'

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

export async function fetch(request: Request): Promise<Response> {
  const { matched, response } = await handler.handle(request, {
    prefix: '/api',
    context: {} // Provide initial context if needed
  })

  if (matched) {
    return response
  }

  return new Response('Not found', { status: 404 })
}

INFO

The actual usage of fetch depends on the runtime environment or library you use:

ts
Bun.serve({
  fetch,
})
ts
export default {
  fetch,
}
ts
Deno.serve(fetch)
ts
import { handle } from 'hono/aws-lambda'

export const handler = handle({ fetch })

WARNING

To better support Blob, File, and ReadableStream<Uint8Array> at the root level in cross-origin scenarios, extend your CORS allowlist to allow clients to send and receive the Content-Disposition and Standard-Server headers. Learn more in the Standard Server documentation. If you use the CORS Plugin, include them in allowHeaders and exposeHeaders:

ts
const cors = new CORSHandlerPlugin({
  allowHeaders: ['Content-Disposition', 'Standard-Server'],
  exposeHeaders: ['Content-Disposition', 'Standard-Server'],
})

Client Usage ​

ts
import { RPCLink } from '@orpc/client/fetch'
import { onError } from '@orpc/client'

const link = new RPCLink({
  origin: 'https://api.example.com', // accepts async function, defaults to current origin
  url: '/rpc', // accepts async function
  headers: { authorization: 'bearer token' }, // accept async function
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
  fetch: (request, init) => { // <- override fetch if needed
    return globalThis.fetch(request, {
      ...init,
      credentials: 'include', // Include cookies on cross-origin requests
    })
  },
})
ts
import { OpenAPILink } from '@orpc/openapi/fetch'
import { onError } from '@orpc/client'

const link = new OpenAPILink(contract, {
  origin: 'https://api.example.com', // accepts async function, defaults to current origin
  url: '/rpc', // accepts async function
  headers: { authorization: 'bearer token' }, // accept async function
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
  fetch: (request, init) => { // <- override fetch if needed
    return globalThis.fetch(request, {
      ...init,
      credentials: 'include', // Include cookies on cross-origin requests
    })
  },
})

INFO

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

Event Stream Options ​

You can configure how event iterators are streamed to the client using the toFetchResponse.eventStream options when creating the handler.

ts
const handler = new OpenAPIHandler(router, {
  toFetchResponse: {
    eventStream: {
      initialComment: {
        /**
         * If true, an initial comment is sent immediately upon stream start to flush headers.
         * This allows the receiving side to establish the connection without waiting for the first event.
         *
         * @default true
         */
        enabled: true,
        /**
         * The content of the initial comment sent upon stream start. Must not include newline characters.
         *
         * @default ''
         */
        comment: '',
      },
      keepAlive: {
        /**
         * If true, a ping comment is sent periodically to keep the connection alive.
         *
         * @default true
         */
        enabled: true,
        /**
         * Interval (in milliseconds) between ping comments sent after the last event.
         *
         * @default 5000
         */
        interval: 5000,
        /**
         * The content of the ping comment. Must not include newline characters.
         *
         * @default ''
         */
        comment: '',
      },
      /**
       * If true, a `close` event is sent even when the iterator completes with `undefined`.
       * When the iterator returns a value, a `close` event is always emitted regardless of this setting.
       *
       * @default true
       */
      emptyCloseEventEnabled: true,
    },
  },
})

INFO

You can also configure how event iterators are streamed from client to server using toFetchBody.eventStream options when creating the link. However, this is rarely used because streaming requests are not widely supported in browsers and may require manually overriding the fetch function with duplex.

Released under the MIT License.