Error Handling
On this page
A failed procedure call rejects with a TRPCClientError. You can either let it throw and catch it, or use safe() to get it back as a type-safe value.
Catching a thrown error
query() and mutate() behave like any other promise, so a try/catch works as you would expect. TypeScript has no way to describe what a function throws, though, so cause arrives as unknown and has to be narrowed:
client.ts
import { class TRPCClientError<TRouterOrProcedure extends InferrableClientTypes>TRPCClientError } from '@trpc/client';
import type { type AppRouter = Router<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: string;
output: {
id: number;
title: string;
};
meta: object;
errorShape: FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>;
}>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter } from './server';
import { const trpc: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: string;
output: {
id: number;
title: string;
};
meta: object;
errorShape: FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>;
}>;
}>>;
}>>>
trpc } from './trpc';
try {
const const post: {
id: number;
title: string;
}
post = await const trpc: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: string;
output: {
id: number;
title: string;
};
meta: object;
errorShape: FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>;
}>;
}>>;
}>>>
trpc.post: DecoratedProcedureRecord<{
transformer: false;
errorShape: DefaultErrorShape;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: string;
output: {
id: number;
title: string;
};
meta: object;
errorShape: FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>;
}>;
}>>
post.byId: {
query: Resolver<{
input: string;
output: {
id: number;
title: string;
};
errorShape: FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>;
transformer: false;
}>;
}
byId.query: (input: string, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<{
id: number;
title: string;
}, FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>
query('1');
} catch (var cause: unknowncause) {
if (var cause: unknowncause instanceof class TRPCClientError<TRouterOrProcedure extends InferrableClientTypes>TRPCClientError) {
var console: Consoleconsole.Console.log(...data: any[]): voidlog(var cause: TRPCClientError<any>cause.TRPCClientError<any>.data: anydata);
} else {
// something that isn't a tRPC error
}
}See Inferring Types for a reusable isTRPCClientError guard.
Errors as values with safe()
safe() awaits a query() or mutate() call without letting it throw, handing back a [data, error] pair instead. Exactly one side is ever set, so checking either one narrows the other:
client.ts
import { function safe<TData, TErrorShape>(promise: TRPCTaggedPromise<TData, TErrorShape>): Promise<SafeResult<TData, TErrorShape>>safe } from '@trpc/client';
import { const trpc: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: string;
output: {
id: number;
title: string;
};
meta: object;
errorShape: FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>;
}>;
}>>;
}>>>
trpc } from './trpc';
const [const post: {
id: number;
title: string;
} | undefined
post, const error: inferSafeError<FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>> | undefined
error] = await safe<{
id: number;
title: string;
}, FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>(promise: TRPCTaggedPromise<{
id: number;
title: string;
}, FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>): Promise<...>
safe(const trpc: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: string;
output: {
id: number;
title: string;
};
meta: object;
errorShape: FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>;
}>;
}>>;
}>>>
trpc.post: DecoratedProcedureRecord<{
transformer: false;
errorShape: DefaultErrorShape;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: string;
output: {
id: number;
title: string;
};
meta: object;
errorShape: FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>;
}>;
}>>
post.byId: {
query: Resolver<{
input: string;
output: {
id: number;
title: string;
};
errorShape: FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>;
transformer: false;
}>;
}
byId.query: (input: string, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<{
id: number;
title: string;
}, FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>
query('1'));
if (const error: inferSafeError<FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>> | undefined
error) {
var console: Consoleconsole.Console.log(...data: any[]): voidlog(const error: inferSafeError<FoldErrorShape<DefaultErrorShape, {
data: {
kind: "RATE_LIMIT";
retryAfterMs: number;
code: TRPC_ERROR_CODE_KEY;
httpStatus: number;
path?: string;
stack?: string;
};
message: string;
code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>
error.data);} else {
var console: Consoleconsole.Console.log(...data: any[]): voidlog(const post: {
id: number;
title: string;
}
post.title: stringtitle);
}