Skip to content

Commit 10a5e0b

Browse files
juliusmarmingec-ehrlichnexxeln
authored
feat: add errorformatter (#1273)
* add errorformatter * add docs * changeset * Update www/src/pages/en/usage/trpc.md Co-authored-by: Christopher Ehrlich <[email protected]> --------- Co-authored-by: Christopher Ehrlich <[email protected]> Co-authored-by: Shoubhit Dash <[email protected]>
1 parent ae5cd40 commit 10a5e0b

File tree

8 files changed

+95
-13
lines changed

8 files changed

+95
-13
lines changed

.changeset/eighty-masks-invite.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-t3-app": minor
3+
---
4+
5+
feat: add errorformatter for zod errors

cli/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"superjson": "^1.12.2",
7272
"tsup": "^6.6.3",
7373
"type-fest": "^3.6.0",
74-
"typescript": "^4.9.5"
74+
"typescript": "^4.9.5",
75+
"zod": "^3.21.4"
7576
}
7677
}

cli/template/extras/src/server/api/trpc/base.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,25 @@ export const createTRPCContext = (_opts: CreateNextContextOptions) => {
4646
/**
4747
* 2. INITIALIZATION
4848
*
49-
* This is where the tRPC API is initialized, connecting the context and transformer.
49+
* This is where the tRPC API is initialized, connecting the context and transformer. We also parse
50+
* ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
51+
* errors on the backend.
5052
*/
5153
import { initTRPC } from "@trpc/server";
5254
import superjson from "superjson";
55+
import { ZodError } from "zod";
5356

5457
const t = initTRPC.context<typeof createTRPCContext>().create({
5558
transformer: superjson,
56-
errorFormatter({ shape }) {
57-
return shape;
59+
errorFormatter({ shape, error }) {
60+
return {
61+
...shape,
62+
data: {
63+
...shape.data,
64+
zodError:
65+
error.cause instanceof ZodError ? error.cause.flatten() : null,
66+
},
67+
};
5868
},
5969
});
6070

cli/template/extras/src/server/api/trpc/with-auth-prisma.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,25 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => {
6161
/**
6262
* 2. INITIALIZATION
6363
*
64-
* This is where the tRPC API is initialized, connecting the context and transformer.
64+
* This is where the tRPC API is initialized, connecting the context and transformer. We also parse
65+
* ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
66+
* errors on the backend.
6567
*/
6668
import { initTRPC, TRPCError } from "@trpc/server";
6769
import superjson from "superjson";
70+
import { ZodError } from "zod";
6871

6972
const t = initTRPC.context<typeof createTRPCContext>().create({
7073
transformer: superjson,
71-
errorFormatter({ shape }) {
72-
return shape;
74+
errorFormatter({ shape, error }) {
75+
return {
76+
...shape,
77+
data: {
78+
...shape.data,
79+
zodError:
80+
error.cause instanceof ZodError ? error.cause.flatten() : null,
81+
},
82+
};
7383
},
7484
});
7585

cli/template/extras/src/server/api/trpc/with-auth.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,25 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => {
5959
/**
6060
* 2. INITIALIZATION
6161
*
62-
* This is where the tRPC API is initialized, connecting the context and transformer.
62+
* This is where the tRPC API is initialized, connecting the context and transformer. We also parse
63+
* ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
64+
* errors on the backend.
6365
*/
6466
import { initTRPC, TRPCError } from "@trpc/server";
6567
import superjson from "superjson";
68+
import { ZodError } from "zod";
6669

6770
const t = initTRPC.context<typeof createTRPCContext>().create({
6871
transformer: superjson,
69-
errorFormatter({ shape }) {
70-
return shape;
72+
errorFormatter({ shape, error }) {
73+
return {
74+
...shape,
75+
data: {
76+
...shape.data,
77+
zodError:
78+
error.cause instanceof ZodError ? error.cause.flatten() : null,
79+
},
80+
};
7181
},
7282
});
7383

cli/template/extras/src/server/api/trpc/with-prisma.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,25 @@ export const createTRPCContext = (_opts: CreateNextContextOptions) => {
4949
/**
5050
* 2. INITIALIZATION
5151
*
52-
* This is where the tRPC API is initialized, connecting the context and transformer.
52+
* This is where the tRPC API is initialized, connecting the context and transformer. We also parse
53+
* ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
54+
* errors on the backend.
5355
*/
5456
import { initTRPC } from "@trpc/server";
5557
import superjson from "superjson";
58+
import { ZodError } from "zod";
5659

5760
const t = initTRPC.context<typeof createTRPCContext>().create({
5861
transformer: superjson,
59-
errorFormatter({ shape }) {
60-
return shape;
62+
errorFormatter({ shape, error }) {
63+
return {
64+
...shape,
65+
data: {
66+
...shape.data,
67+
zodError:
68+
error.cause instanceof ZodError ? error.cause.flatten() : null,
69+
},
70+
};
6171
},
6272
});
6373

pnpm-lock.yaml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

www/src/pages/en/usage/trpc.md

+30
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,36 @@ const UserPage = () => {
9393

9494
You'll immediately notice how good the autocompletion and typesafety is. As soon as you write `api.`, your routers will show up in autocomplete, and when you select a router, its procedures will show up as well. You'll also get a TypeScript error if your input doesn't match the validator that you defined on the backend.
9595

96+
## Inferring errors
97+
98+
By default, `create-t3-app` sets up an [error formatter](https://trpc.io/docs/error-formatting) that lets you infer your Zod Errors if you get validation errors on the backend.
99+
100+
Example usage:
101+
102+
```tsx
103+
function MyComponent() {
104+
const { mutate, error } = api.post.create.useMutation();
105+
106+
return (
107+
<form onSubmit={(e) => {
108+
e.preventDefault();
109+
const formData = new FormData(e.currentTarget);
110+
mutate({ title: formData.get('title') });
111+
}}>
112+
<input name="title" />
113+
{error?.data?.zodError?.fieldErrors.title && (
114+
{/** `mutate` returned with an error on the `title` */}
115+
<span className="mb-8 text-red-500">
116+
{error.data.zodError.fieldErrors.title}
117+
</span>
118+
)}
119+
120+
...
121+
</form>
122+
);
123+
}
124+
```
125+
96126
## Files
97127

98128
tRPC requires quite a lot of boilerplate that `create-t3-app` sets up for you. Let's go over the files that are generated:

0 commit comments

Comments
 (0)