Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

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 { function call<TInitialContext extends Context, TInputSchema extends AnySchema, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap, TReturnedError extends AnyORPCError>(lazyableProcedure: Lazyable<Procedure<TInitialContext, any, TInputSchema, TOutputSchema, TErrorMap, TReturnedError>>, ...[input, options,]: CallRest<TInitialContext, TInputSchema, TOutputSchema, TErrorMap, TReturnedError>): PromiseWithError<InferSchemaOutput<TOutputSchema>, ORPCErrorFromErrorMap<TErrorMap> | TReturnedError | ThrowableError>
Quickly call a procedure without creating a client.
@example```ts const output = await call(getting, 'input') const output = await call(getting, 'input', { context: { db: 'postgres' } }) ```@see{@link https://orpc.dev/docs/client/server-side#one-off-calls Server-Side Clients - One-Off Calls}
call
, const os: Builder<DefaultInitialContext & object, Record<never, never>>
The oRPC procedure builder. Chain methods like `.input`, `.use`, and `.handler` to define procedures, then compose them into routers.
@see{@link https://orpc.dev/docs/procedure Procedure}
os
} from '@orpc/server'
const
const result: {
    id: string;
}
result
= await
call<{}, z.ZodString, Schema<{
    id: string;
}>, Record<never, never>, never>(lazyableProcedure: Lazyable<Procedure<{}, any, z.ZodString, Schema<{
    id: string;
}>, Record<never, never>, never>>, input: string, options?: CallOptions<{}, Schema<{
    id: string;
}>, Record<never, never>, never> | undefined): PromiseWithError<{
    id: string;
}, Error>
Quickly call a procedure without creating a client.
@example```ts const output = await call(getting, 'input') const output = await call(getting, 'input', { context: { db: 'postgres' } }) ```@see{@link https://orpc.dev/docs/client/server-side#one-off-calls Server-Side Clients - One-Off Calls}
call
(
const exampleProcedure: DecoratedProcedure<DefaultInitialContext & object, object, z.ZodString, Schema<{
    id: string;
}>, Record<never, never>, never>
exampleProcedure
, 'input', {
context?: Value<Promisable<{}>, [clientContext: object]> | undefinedcontext: {} // <- 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 { function createRouterClient<T extends AnyRouter, TClientContext extends ClientContext = object>(router: Lazyable<T | undefined>, ...rest: MaybeOptionalOptions<ProcedureClientOptions<InferRouterInitialContext<T>, Schema<unknown>, ErrorMap, any, TClientContext>>): RouterClient<T, TClientContext>
Creates a server-side client that calls the router's procedures directly, within the same process, without any network layer.
@see{@link https://orpc.dev/docs/client/server-side#router-clients Server-Side Clients - Router Clients}
createRouterClient
} from '@orpc/server'
const
const client: {
    ping: ProcedureClient<object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: ProcedureClient<object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}
client
=
createRouterClient<{
    ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}, object>(router: Lazyable<{
    ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
} | undefined>, options?: ProcedureClientOptions<...> | undefined): {
    ...;
}
Creates a server-side client that calls the router's procedures directly, within the same process, without any network layer.
@see{@link https://orpc.dev/docs/client/server-side#router-clients Server-Side Clients - Router Clients}
createRouterClient
(
const router: {
    ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}
router
, {
context?: Value<Promisable<DefaultInitialContext & object>, [clientContext: object]> | undefinedcontext: {}, // <- provide initial context if needed, can be async function interceptors?: ProcedureClientInterceptor<DefaultInitialContext & object, Schema<unknown>, ErrorMap, any>[] | undefinedinterceptors: [ async ({ next: (options?: ProcedureClientInterceptorOptions<DefaultInitialContext & object, ErrorMap> | undefined) => PromiseWithError<unknown, any>next, path: string[]path }) => { var console: Consoleconsole.Console.time(label?: string): void
The **`console.time()`** static method starts a timer you can use to track how long an operation takes. You give each timer a unique name, and may have up to 10,000 timers running on a given page. When you call console.timeEnd() with the same name, the browser will output the time, in milliseconds, that elapsed since the timer was started. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static)
time
(path: string[]path.Array<string>.join(separator?: string): string
Adds all the elements of an array into a string, separated by the specified separator string.
@paramseparator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
join
('.'))
try { return await next: (options?: ProcedureClientInterceptorOptions<DefaultInitialContext & object, ErrorMap> | undefined) => PromiseWithError<unknown, any>next() } catch (function (local var) err: unknownerr) { var console: Consoleconsole.Console.error(...data: any[]): void
The **`console.error()`** static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static)
error
(`${path: string[]path.Array<string>.join(separator?: string): string
Adds all the elements of an array into a string, separated by the specified separator string.
@paramseparator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
join
('.')}:`, function (local var) err: unknownerr)
throw function (local var) err: unknownerr } finally { var console: Consoleconsole.Console.timeEnd(label?: string): void
The **`console.timeEnd()`** static method stops a timer that was previously started by calling console.time(). [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static)
timeEnd
(path: string[]path.Array<string>.join(separator?: string): string
Adds all the elements of an array into a string, separated by the specified separator string.
@paramseparator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
join
('.'))
} } ] }) const const result: stringresult = await
const client: {
    ping: ProcedureClient<object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: ProcedureClient<object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}
client
.
ping: Client
(input?: void | undefined, options?: FriendlyClientOptions<object> | undefined) => PromiseWithError<string, Error>
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 {
  ClientContext.cache?: boolean | undefinedcache?: boolean
}

const 
const client: {
    ping: ProcedureClient<ClientContext, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: ProcedureClient<ClientContext, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}
client
=
createRouterClient<{
    ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}, ClientContext>(router: Lazyable<{
    ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
} | undefined>, options?: ProcedureClientOptions<...> | undefined): {
    ...;
}
Creates a server-side client that calls the router's procedures directly, within the same process, without any network layer.
@see{@link https://orpc.dev/docs/client/server-side#router-clients Server-Side Clients - Router Clients}
createRouterClient
(
const router: {
    ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}
router
, {
context?: Value<Promisable<DefaultInitialContext & object>, [clientContext: ClientContext]> | undefinedcontext: ({ cache: boolean | undefinedcache }: ClientContext) => { if (cache: boolean | undefinedcache) { return {} // <- context when cache enabled } return {} } }) const const result: stringresult = await
const client: {
    ping: ProcedureClient<ClientContext, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: ProcedureClient<ClientContext, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}
client
.
ping: Client
(input?: void | undefined, options?: FriendlyClientOptions<ClientContext> | undefined) => PromiseWithError<string, Error>
ping
(var undefinedundefined, { context?: ClientContext | undefinedcontext: { ClientContext.cache?: boolean | undefinedcache: 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 directly
import '@orpc/server/extensions/callable'

import { os } from '@orpc/server'

export const base = os

Lifecycle

TODO: add lifecycle diagram

Last updated on August 6, 2026

Was this page helpful?