Server-Side Clients
Server-side clients call procedures locally, within the same process. They are useful in microservices, serverless functions, or any setup where the caller and procedures run in the same environment.
One-Off Calls
Use call when you need to invoke a single procedure without creating a client instance.
import { call, os } from '@orpc/server'
const result = await call(exampleProcedure, 'input', {
context: {} // <- provide initial context if needed
})Router Clients
Use createRouterClient to create a client for your router. This is useful when you want to call multiple procedures.
import { createRouterClient } from '@orpc/server'
const client = createRouterClient(router, {
context: {}, // <- provide initial context if needed, can be async function
interceptors: [
async ({ next, path }) => {
console.time(path.join('.'))
try {
return await next()
}
catch (err) {
console.error(`${path.join('.')}:`, err)
throw err
}
finally {
console.timeEnd(path.join('.'))
}
}
]
})
const result = await client.ping()Client Context
Client context is passed with each call. Use it to switch between contexts, such as different users or tenants, without creating multiple client instances.
interface ClientContext {
cache?: boolean
}
const client = createRouterClient(router, {
context: ({ cache }: ClientContext) => {
if (cache) {
return {} // <- context when cache enabled
}
return {}
}
})
const result = await client.ping(undefined, { context: { cache: true } })Interceptors
Interceptors let you observe or modify an entire call. Common use cases include logging, error handling, and metrics collection.
const client = createRouterClient(router, {
interceptors: [
async ({ next, path, context }) => {
console.time(path.join('.'))
try {
const output = await next()
return output
}
catch (err) {
console.error(`${path.join('.')}:`, err)
throw err
}
finally {
console.timeEnd(path.join('.'))
}
}
]
}).callable extension
Import @orpc/server/extensions/callable from a module that always runs during initialization, such as the file where you define your base builder or create your server. This adds a .callable method to the decorated procedure, allowing you to call it directly like a regular function while still using it as a regular procedure.
const ping = base
.input(z.object({ name: z.string(), }))
.handler(async ({ input }) => `Hello ${input.name}!`)
.callable({
context: async () => ({}), // <- provide initial context if needed, can be async function
interceptors: [], // <- client interceptors
})
const router = {
ping, // <- still use it as a regular procedure
}
const message = await ping({ name: 'World' }) // <- or call it directlyimport '@orpc/server/extensions/callable'
import { os } from '@orpc/server'
export const base = osLifecycle
TODO: add lifecycle diagram

