Client Error Handling
oRPC supports several ways to handle client-side errors. In most cases, try/catch is enough. If you use Typesafe Errors, safe and createSafeClient let you handle them with full type inference.
Using try/catch
For most calls, use regular try/catch.
try {
const data = await client.doSomething({ id: '123' })
}
catch (error) {
// handle error
}
Using safe and isInferableError
When working with Typesafe Errors, use safe to preserve error type inference. It behaves like try/catch, but returns the typesafe result instead of throwing.
import { function isInferableError<T>(error: T): error is Extract<T, AnyORPCError>Checks if an error is an `ORPCError` whose type is inferable at the TypeScript level,
narrowing it so `code` and `data` are fully typed.isInferableError, function safe<TOutput, TError = Error>(promise: PromiseWithError<TOutput, TError>): Promise<SafeResult<TOutput, TError>>Works like try/catch, but help you infer the error type if it is inferable ORPCError.safe } from '@orpc/client'
const const exampleProcedure: DecoratedProcedure<DefaultInitialContext & object, object, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>, Schema<never>, {
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}, never>
exampleProcedure = 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
.Builder<DefaultInitialContext & object, Record<never, never>>.input<z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>): BuilderWithInput<DefaultInitialContext & object, object, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>, Record<never, never>>
input(import zz.function object<{
id: z.ZodString;
}>(shape?: {
id: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.BuilderWithInput<DefaultInitialContext & object, object, ZodObject<{ id: ZodString; }, $strip>, Record<never, never>>['errors']<{
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}>(errors: {
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}): BuilderWithInput<DefaultInitialContext & object, object, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>, {
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}>
errors({
type RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
}
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>
data: import zz.function object<{
retryAfter: z.ZodNumber;
}>(shape?: {
retryAfter: z.ZodNumber;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>
object({ retryAfter: z.ZodNumberretryAfter: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber() })
}
})
.BuilderWithInput<DefaultInitialContext & object, object, ZodObject<{ id: ZodString; }, $strip>, { RATE_LIMIT_EXCEEDED: { ...; }; }>['handler']<never>(handler: ProcedureHandler<DefaultInitialContext & object, {
id: string;
}, never, ORPCErrorConstructorMap<{
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}>>): DecoratedProcedure<DefaultInitialContext & object, object, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>, Schema<never>, {
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}, never>
handler(async ({ input: {
id: string;
}
input, errors: ORPCErrorConstructorMap<{
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}>
errors }) => {
throw errors: ORPCErrorConstructorMap<{
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}>
errors.type RATE_LIMIT_EXCEEDED: ORPCErrorConstructorMapItem
(options: ORPCErrorConstructorMapItemOptions<{
retryAfter: number;
}>) => ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}>
RATE_LIMIT_EXCEEDED({ data: {
retryAfter: number;
}
data: { retryAfter: numberretryAfter: 1000 } })
})
// or { error, data, inferableError }
const [const error: Error | ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}> | null
error, const data: undefineddata, const inferableError: ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}> | null
inferableError] = await safe<never, Error | ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}>>(promise: PromiseWithError<never, Error | ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}>>): Promise<SafeResult<never, Error | ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}>>>
Works like try/catch, but help you infer the error type if it is inferable ORPCError.safe(
call<DefaultInitialContext & object, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>, Schema<never>, {
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}, never>(lazyableProcedure: Lazyable<Procedure<DefaultInitialContext & object, any, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>, Schema<never>, {
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}, never>>, input: {
...;
}, options?: CallOptions<...> | undefined): PromiseWithError<...>
Quickly call a procedure without creating a client.call(const exampleProcedure: DecoratedProcedure<DefaultInitialContext & object, object, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>, Schema<never>, {
RATE_LIMIT_EXCEEDED: {
data: z.ZodObject<{
retryAfter: z.ZodNumber;
}, z.core.$strip>;
};
}, never>
exampleProcedure, { id: stringid: '123' })
)
if (isInferableError<Error | ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}> | null>(error: Error | ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}> | null): error is ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}>
Checks if an error is an `ORPCError` whose type is inferable at the TypeScript level,
narrowing it so `code` and `data` are fully typed.isInferableError(const error: Error | ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}> | null
error)) { // or inferableError
// handle inferable error
// or inferableError.data.retryAfter
var console: Consoleconsole.Console.log(...data: any[]): voidThe **`console.log()`** static method outputs a message to the console.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(const error: ORPCError<"RATE_LIMIT_EXCEEDED", {
retryAfter: number;
}>
error.ORPCError<"RATE_LIMIT_EXCEEDED", { retryAfter: number; }>.data: {
retryAfter: number;
}
data.retryAfter: numberretryAfter)
}
else if (const error: Error | nullerror) {
// handle unknown error
}
else {
// handle success
var console: Consoleconsole.Console.log(...data: any[]): voidThe **`console.log()`** static method outputs a message to the console.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(const data: neverdata)
}
Safe Client
If you use safe often, createSafeClient can reduce repetition by wrapping entire client calls with safe.
import { createSafeClient } from '@orpc/client'
const safeClient = createSafeClient(client)
const [error, data] = await safeClient.doSomething({ id: '123' })