Fetch API Adapter β
oRPC supports the Fetch API for both servers and clients.
Server Usage β
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 })
}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:
Bun.serve({
fetch,
})export default {
fetch,
}Deno.serve(fetch)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:
const cors = new CORSHandlerPlugin({
allowHeaders: ['Content-Disposition', 'Standard-Server'],
exposeHeaders: ['Content-Disposition', 'Standard-Server'],
})Client Usage β
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
})
},
})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 an AsyncIteratorObject is streamed to the client using the toFetchResponse.eventStream options when creating the handler.
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 an AsyncIteratorObject is streamed from client to server using toFetchRequest.eventStream options when creating the link.

