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.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.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.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.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.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): voidThe **`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): stringAdds all the elements of an array into a string, separated by the specified separator string.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[]): voidThe **`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): stringAdds all the elements of an array into a string, separated by the specified separator string.join('.')}:`, function (local var) err: unknownerr)
throw function (local var) err: unknownerr
}
finally {
var console: Consoleconsole.Console.timeEnd(label?: string): voidThe **`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): stringAdds all the elements of an array into a string, separated by the specified separator string.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.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 directlyimport '@orpc/server/extensions/callable'
import { os } from '@orpc/server'
export const base = osLifecycle
TODO: add lifecycle diagram