@trpc/server 11.14.0 → 11.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/bin/intent.js +20 -0
- package/dist/adapters/next-app-dir.cjs +76 -76
- package/dist/adapters/next-app-dir.mjs +76 -76
- package/dist/adapters/next-app-dir.mjs.map +1 -1
- package/package.json +13 -3
- package/skills/adapter-aws-lambda/SKILL.md +188 -0
- package/skills/adapter-express/SKILL.md +152 -0
- package/skills/adapter-fastify/SKILL.md +206 -0
- package/skills/adapter-fetch/SKILL.md +177 -0
- package/skills/adapter-standalone/SKILL.md +184 -0
- package/skills/auth/SKILL.md +342 -0
- package/skills/caching/SKILL.md +205 -0
- package/skills/error-handling/SKILL.md +253 -0
- package/skills/middlewares/SKILL.md +242 -0
- package/skills/non-json-content-types/SKILL.md +265 -0
- package/skills/server-setup/SKILL.md +378 -0
- package/skills/server-side-calls/SKILL.md +249 -0
- package/skills/service-oriented-architecture/SKILL.md +247 -0
- package/skills/subscriptions/SKILL.md +406 -0
- package/skills/trpc-router/SKILL.md +151 -0
- package/skills/validators/SKILL.md +228 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: non-json-content-types
|
|
3
|
+
description: >
|
|
4
|
+
Handle FormData, file uploads, Blob, Uint8Array, and ReadableStream inputs in
|
|
5
|
+
tRPC mutations. Use octetInputParser from @trpc/server/http for binary data.
|
|
6
|
+
Route non-JSON requests with splitLink and isNonJsonSerializable() from
|
|
7
|
+
@trpc/client. FormData and binary inputs only work with mutations (POST).
|
|
8
|
+
type: core
|
|
9
|
+
library: trpc
|
|
10
|
+
library_version: '11.14.0'
|
|
11
|
+
requires:
|
|
12
|
+
- server-setup
|
|
13
|
+
- links
|
|
14
|
+
sources:
|
|
15
|
+
- 'trpc/trpc:www/docs/server/non-json-content-types.md'
|
|
16
|
+
- 'trpc/trpc:examples/next-formdata/'
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# tRPC -- Non-JSON Content Types
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
Server:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// server/trpc.ts
|
|
27
|
+
import { initTRPC } from '@trpc/server';
|
|
28
|
+
|
|
29
|
+
const t = initTRPC.create();
|
|
30
|
+
|
|
31
|
+
export const router = t.router;
|
|
32
|
+
export const publicProcedure = t.procedure;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// server/appRouter.ts
|
|
37
|
+
import { octetInputParser } from '@trpc/server/http';
|
|
38
|
+
import { z } from 'zod';
|
|
39
|
+
import { publicProcedure, router } from './trpc';
|
|
40
|
+
|
|
41
|
+
export const appRouter = router({
|
|
42
|
+
uploadForm: publicProcedure
|
|
43
|
+
.input(z.instanceof(FormData))
|
|
44
|
+
.mutation(({ input }) => {
|
|
45
|
+
const name = input.get('name');
|
|
46
|
+
return { greeting: `Hello ${name}` };
|
|
47
|
+
}),
|
|
48
|
+
uploadFile: publicProcedure.input(octetInputParser).mutation(({ input }) => {
|
|
49
|
+
// input is a ReadableStream
|
|
50
|
+
return { valid: true };
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export type AppRouter = typeof appRouter;
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Client:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// client/index.ts
|
|
61
|
+
import {
|
|
62
|
+
createTRPCClient,
|
|
63
|
+
httpBatchLink,
|
|
64
|
+
httpLink,
|
|
65
|
+
isNonJsonSerializable,
|
|
66
|
+
splitLink,
|
|
67
|
+
} from '@trpc/client';
|
|
68
|
+
import type { AppRouter } from '../server/appRouter';
|
|
69
|
+
|
|
70
|
+
const url = 'http://localhost:3000';
|
|
71
|
+
|
|
72
|
+
const trpc = createTRPCClient<AppRouter>({
|
|
73
|
+
links: [
|
|
74
|
+
splitLink({
|
|
75
|
+
condition: (op) => isNonJsonSerializable(op.input),
|
|
76
|
+
true: httpLink({ url }),
|
|
77
|
+
false: httpBatchLink({ url }),
|
|
78
|
+
}),
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Core Patterns
|
|
84
|
+
|
|
85
|
+
### FormData mutation
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// server/appRouter.ts
|
|
89
|
+
import { z } from 'zod';
|
|
90
|
+
import { publicProcedure, router } from './trpc';
|
|
91
|
+
|
|
92
|
+
export const appRouter = router({
|
|
93
|
+
createPost: publicProcedure
|
|
94
|
+
.input(z.instanceof(FormData))
|
|
95
|
+
.mutation(({ input }) => {
|
|
96
|
+
const title = input.get('title') as string;
|
|
97
|
+
const body = input.get('body') as string;
|
|
98
|
+
return { id: '1', title, body };
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// client usage
|
|
105
|
+
const form = new FormData();
|
|
106
|
+
form.append('title', 'Hello');
|
|
107
|
+
form.append('body', 'World');
|
|
108
|
+
|
|
109
|
+
const result = await trpc.createPost.mutate(form);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Binary file upload with octetInputParser
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// server/appRouter.ts
|
|
116
|
+
import { octetInputParser } from '@trpc/server/http';
|
|
117
|
+
import { publicProcedure, router } from './trpc';
|
|
118
|
+
|
|
119
|
+
export const appRouter = router({
|
|
120
|
+
upload: publicProcedure
|
|
121
|
+
.input(octetInputParser)
|
|
122
|
+
.mutation(async ({ input }) => {
|
|
123
|
+
const reader = input.getReader();
|
|
124
|
+
let totalBytes = 0;
|
|
125
|
+
while (true) {
|
|
126
|
+
const { done, value } = await reader.read();
|
|
127
|
+
if (done) break;
|
|
128
|
+
totalBytes += value.byteLength;
|
|
129
|
+
}
|
|
130
|
+
return { totalBytes };
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// client usage
|
|
137
|
+
const file = new File(['hello world'], 'test.txt', { type: 'text/plain' });
|
|
138
|
+
const result = await trpc.upload.mutate(file);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`octetInputParser` converts Blob, Uint8Array, and File inputs to a ReadableStream on the server.
|
|
142
|
+
|
|
143
|
+
### Client splitLink with superjson transformer
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import {
|
|
147
|
+
createTRPCClient,
|
|
148
|
+
httpBatchLink,
|
|
149
|
+
httpLink,
|
|
150
|
+
isNonJsonSerializable,
|
|
151
|
+
splitLink,
|
|
152
|
+
} from '@trpc/client';
|
|
153
|
+
import superjson from 'superjson';
|
|
154
|
+
import type { AppRouter } from '../server/appRouter';
|
|
155
|
+
|
|
156
|
+
const url = 'http://localhost:3000';
|
|
157
|
+
|
|
158
|
+
const trpc = createTRPCClient<AppRouter>({
|
|
159
|
+
links: [
|
|
160
|
+
splitLink({
|
|
161
|
+
condition: (op) => isNonJsonSerializable(op.input),
|
|
162
|
+
true: httpLink({
|
|
163
|
+
url,
|
|
164
|
+
transformer: {
|
|
165
|
+
serialize: (data) => data,
|
|
166
|
+
deserialize: (data) => superjson.deserialize(data),
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
false: httpBatchLink({
|
|
170
|
+
url,
|
|
171
|
+
transformer: superjson,
|
|
172
|
+
}),
|
|
173
|
+
}),
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
When using a transformer, the non-JSON httpLink needs a custom transformer that skips serialization for the request (FormData/binary cannot be transformed) but deserializes the response.
|
|
179
|
+
|
|
180
|
+
## Common Mistakes
|
|
181
|
+
|
|
182
|
+
### [HIGH] Using httpBatchLink for FormData requests
|
|
183
|
+
|
|
184
|
+
Wrong:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { createTRPCClient, httpBatchLink } from '@trpc/client';
|
|
188
|
+
import type { AppRouter } from '../server/appRouter';
|
|
189
|
+
|
|
190
|
+
const trpc = createTRPCClient<AppRouter>({
|
|
191
|
+
links: [httpBatchLink({ url: 'http://localhost:3000' })],
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Correct:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import {
|
|
199
|
+
createTRPCClient,
|
|
200
|
+
httpBatchLink,
|
|
201
|
+
httpLink,
|
|
202
|
+
isNonJsonSerializable,
|
|
203
|
+
splitLink,
|
|
204
|
+
} from '@trpc/client';
|
|
205
|
+
import type { AppRouter } from '../server/appRouter';
|
|
206
|
+
|
|
207
|
+
const url = 'http://localhost:3000';
|
|
208
|
+
|
|
209
|
+
const trpc = createTRPCClient<AppRouter>({
|
|
210
|
+
links: [
|
|
211
|
+
splitLink({
|
|
212
|
+
condition: (op) => isNonJsonSerializable(op.input),
|
|
213
|
+
true: httpLink({ url }),
|
|
214
|
+
false: httpBatchLink({ url }),
|
|
215
|
+
}),
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
FormData and binary inputs are not batchable; use `splitLink` with `isNonJsonSerializable()` to route them through `httpLink`.
|
|
221
|
+
|
|
222
|
+
Source: www/docs/server/non-json-content-types.md
|
|
223
|
+
|
|
224
|
+
### [HIGH] Global body parser intercepting FormData before tRPC
|
|
225
|
+
|
|
226
|
+
Wrong:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import * as trpcExpress from '@trpc/server/adapters/express';
|
|
230
|
+
import express from 'express';
|
|
231
|
+
import { appRouter } from './appRouter';
|
|
232
|
+
|
|
233
|
+
const app = express();
|
|
234
|
+
app.use(express.json());
|
|
235
|
+
app.use('/trpc', trpcExpress.createExpressMiddleware({ router: appRouter }));
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Correct:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import * as trpcExpress from '@trpc/server/adapters/express';
|
|
242
|
+
import express from 'express';
|
|
243
|
+
import { appRouter } from './appRouter';
|
|
244
|
+
|
|
245
|
+
const app = express();
|
|
246
|
+
app.use('/api', express.json());
|
|
247
|
+
app.use('/trpc', trpcExpress.createExpressMiddleware({ router: appRouter }));
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
A global `express.json()` middleware consumes the request body before tRPC can read it; scope body parsing to non-tRPC routes only.
|
|
251
|
+
|
|
252
|
+
Source: www/docs/server/non-json-content-types.md
|
|
253
|
+
|
|
254
|
+
### [HIGH] FormData only works with mutations
|
|
255
|
+
|
|
256
|
+
FormData and binary inputs are only supported for mutations (POST requests). Using them with `.query()` throws an error because queries use HTTP GET which cannot carry a request body.
|
|
257
|
+
|
|
258
|
+
Source: www/docs/server/non-json-content-types.md
|
|
259
|
+
|
|
260
|
+
## See Also
|
|
261
|
+
|
|
262
|
+
- `server-setup` -- initTRPC, routers, procedures
|
|
263
|
+
- `links` -- splitLink configuration for routing non-JSON requests
|
|
264
|
+
- `validators` -- z.instanceof(FormData) for FormData validation
|
|
265
|
+
- `adapter-express` -- Express-specific body parser considerations
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: server-setup
|
|
3
|
+
description: >
|
|
4
|
+
Initialize tRPC with initTRPC.create(), define routers with t.router(),
|
|
5
|
+
create procedures with .query()/.mutation()/.subscription(), configure context
|
|
6
|
+
with createContext(), export AppRouter type, merge routers with t.mergeRouters(),
|
|
7
|
+
lazy-load routers with lazy().
|
|
8
|
+
type: core
|
|
9
|
+
library: trpc
|
|
10
|
+
library_version: '11.14.0'
|
|
11
|
+
requires: []
|
|
12
|
+
sources:
|
|
13
|
+
- 'trpc/trpc:www/docs/server/overview.md'
|
|
14
|
+
- 'trpc/trpc:www/docs/server/routers.md'
|
|
15
|
+
- 'trpc/trpc:www/docs/server/procedures.md'
|
|
16
|
+
- 'trpc/trpc:www/docs/server/context.md'
|
|
17
|
+
- 'trpc/trpc:www/docs/server/merging-routers.md'
|
|
18
|
+
- 'trpc/trpc:www/docs/main/quickstart.mdx'
|
|
19
|
+
- 'trpc/trpc:packages/server/src/unstable-core-do-not-import/initTRPC.ts'
|
|
20
|
+
- 'trpc/trpc:packages/server/src/unstable-core-do-not-import/router.ts'
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# tRPC -- Server Setup
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// server/trpc.ts
|
|
29
|
+
import { initTRPC } from '@trpc/server';
|
|
30
|
+
|
|
31
|
+
const t = initTRPC.create();
|
|
32
|
+
|
|
33
|
+
export const router = t.router;
|
|
34
|
+
export const publicProcedure = t.procedure;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// server/appRouter.ts
|
|
39
|
+
import { z } from 'zod';
|
|
40
|
+
import { publicProcedure, router } from './trpc';
|
|
41
|
+
|
|
42
|
+
type User = { id: string; name: string };
|
|
43
|
+
|
|
44
|
+
export const appRouter = router({
|
|
45
|
+
userList: publicProcedure.query(async (): Promise<User[]> => {
|
|
46
|
+
return [{ id: '1', name: 'Katt' }];
|
|
47
|
+
}),
|
|
48
|
+
userById: publicProcedure
|
|
49
|
+
.input(z.string())
|
|
50
|
+
.query(async ({ input }): Promise<User> => {
|
|
51
|
+
return { id: input, name: 'Katt' };
|
|
52
|
+
}),
|
|
53
|
+
userCreate: publicProcedure
|
|
54
|
+
.input(z.object({ name: z.string() }))
|
|
55
|
+
.mutation(async ({ input }): Promise<User> => {
|
|
56
|
+
return { id: '1', ...input };
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export type AppRouter = typeof appRouter;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// server/index.ts
|
|
65
|
+
import { createHTTPServer } from '@trpc/server/adapters/standalone';
|
|
66
|
+
import { appRouter } from './appRouter';
|
|
67
|
+
|
|
68
|
+
const server = createHTTPServer({ router: appRouter });
|
|
69
|
+
server.listen(3000);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Core Patterns
|
|
73
|
+
|
|
74
|
+
### Context with typed session
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// server/context.ts
|
|
78
|
+
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
|
|
79
|
+
|
|
80
|
+
export async function createContext(opts: CreateHTTPContextOptions) {
|
|
81
|
+
const token = opts.req.headers['authorization'];
|
|
82
|
+
return { token };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type Context = Awaited<ReturnType<typeof createContext>>;
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// server/trpc.ts
|
|
90
|
+
import { initTRPC } from '@trpc/server';
|
|
91
|
+
import type { Context } from './context';
|
|
92
|
+
|
|
93
|
+
const t = initTRPC.context<Context>().create();
|
|
94
|
+
|
|
95
|
+
export const router = t.router;
|
|
96
|
+
export const publicProcedure = t.procedure;
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// server/index.ts
|
|
101
|
+
import { createHTTPServer } from '@trpc/server/adapters/standalone';
|
|
102
|
+
import { appRouter } from './appRouter';
|
|
103
|
+
import { createContext } from './context';
|
|
104
|
+
|
|
105
|
+
const server = createHTTPServer({
|
|
106
|
+
router: appRouter,
|
|
107
|
+
createContext,
|
|
108
|
+
});
|
|
109
|
+
server.listen(3000);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Inner/outer context split for testability
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// server/context.ts
|
|
116
|
+
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
|
|
117
|
+
import { db } from './db';
|
|
118
|
+
|
|
119
|
+
interface CreateInnerContextOptions {
|
|
120
|
+
session: { user: { email: string } } | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function createContextInner(opts?: CreateInnerContextOptions) {
|
|
124
|
+
return {
|
|
125
|
+
db,
|
|
126
|
+
session: opts?.session ?? null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function createContext(opts: CreateHTTPContextOptions) {
|
|
131
|
+
const session = getSessionFromCookie(opts.req);
|
|
132
|
+
const contextInner = await createContextInner({ session });
|
|
133
|
+
return {
|
|
134
|
+
...contextInner,
|
|
135
|
+
req: opts.req,
|
|
136
|
+
res: opts.res,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type Context = Awaited<ReturnType<typeof createContextInner>>;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Infer `Context` from `createContextInner` so server-side callers and tests never need HTTP request objects.
|
|
144
|
+
|
|
145
|
+
### Merging child routers
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
// server/routers/user.ts
|
|
149
|
+
import { publicProcedure, router } from '../trpc';
|
|
150
|
+
|
|
151
|
+
export const userRouter = router({
|
|
152
|
+
list: publicProcedure.query(() => []),
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// server/routers/post.ts
|
|
158
|
+
import { z } from 'zod';
|
|
159
|
+
import { publicProcedure, router } from '../trpc';
|
|
160
|
+
|
|
161
|
+
export const postRouter = router({
|
|
162
|
+
create: publicProcedure
|
|
163
|
+
.input(z.object({ title: z.string() }))
|
|
164
|
+
.mutation(({ input }) => ({ id: '1', ...input })),
|
|
165
|
+
list: publicProcedure.query(() => []),
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// server/routers/_app.ts
|
|
171
|
+
import { router } from '../trpc';
|
|
172
|
+
import { postRouter } from './post';
|
|
173
|
+
import { userRouter } from './user';
|
|
174
|
+
|
|
175
|
+
export const appRouter = router({
|
|
176
|
+
user: userRouter,
|
|
177
|
+
post: postRouter,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
export type AppRouter = typeof appRouter;
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Lazy-loaded routers for serverless cold starts
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// server/routers/_app.ts
|
|
187
|
+
import { lazy } from '@trpc/server';
|
|
188
|
+
import { router } from '../trpc';
|
|
189
|
+
|
|
190
|
+
export const appRouter = router({
|
|
191
|
+
// Short-hand when the module has exactly one router exported
|
|
192
|
+
greeting: lazy(() => import('./greeting.js')),
|
|
193
|
+
// Use .then() to pick a named export when the module exports multiple routers
|
|
194
|
+
user: lazy(() => import('./user.js').then((m) => m.userRouter)),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
export type AppRouter = typeof appRouter;
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Common Mistakes
|
|
201
|
+
|
|
202
|
+
### [CRITICAL] Calling initTRPC.create() more than once
|
|
203
|
+
|
|
204
|
+
Wrong:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
// file: userRouter.ts
|
|
208
|
+
import { initTRPC } from '@trpc/server';
|
|
209
|
+
const t = initTRPC.create();
|
|
210
|
+
export const userRouter = t.router({});
|
|
211
|
+
|
|
212
|
+
// file: postRouter.ts
|
|
213
|
+
import { initTRPC } from '@trpc/server';
|
|
214
|
+
const t2 = initTRPC.create();
|
|
215
|
+
export const postRouter = t2.router({});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Correct:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
// file: trpc.ts (single file, created once)
|
|
222
|
+
import { initTRPC } from '@trpc/server';
|
|
223
|
+
import type { Context } from './context';
|
|
224
|
+
|
|
225
|
+
const t = initTRPC.context<Context>().create();
|
|
226
|
+
|
|
227
|
+
export const router = t.router;
|
|
228
|
+
export const publicProcedure = t.procedure;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Multiple tRPC instances cause type mismatches and runtime errors when routers from different instances are merged.
|
|
232
|
+
|
|
233
|
+
Source: www/docs/server/routers.md
|
|
234
|
+
|
|
235
|
+
### [HIGH] Using reserved words as procedure names
|
|
236
|
+
|
|
237
|
+
Wrong:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { publicProcedure, router } from './trpc';
|
|
241
|
+
|
|
242
|
+
const appRouter = router({
|
|
243
|
+
then: publicProcedure.query(() => 'hello'),
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Correct:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { publicProcedure, router } from './trpc';
|
|
251
|
+
|
|
252
|
+
const appRouter = router({
|
|
253
|
+
next: publicProcedure.query(() => 'hello'),
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Router creation throws if procedure names are "then", "call", or "apply" because these conflict with JavaScript Proxy internals.
|
|
258
|
+
|
|
259
|
+
Source: packages/server/src/unstable-core-do-not-import/router.ts
|
|
260
|
+
|
|
261
|
+
### [CRITICAL] Importing AppRouter as a value import
|
|
262
|
+
|
|
263
|
+
Wrong:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
// client.ts
|
|
267
|
+
import { AppRouter } from '../server/router';
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Correct:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
// client.ts
|
|
274
|
+
import type { AppRouter } from '../server/router';
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
A non-type import pulls the entire server bundle into the client; use `import type` so it is stripped at build time.
|
|
278
|
+
|
|
279
|
+
Source: www/docs/server/routers.md
|
|
280
|
+
|
|
281
|
+
### [MEDIUM] Creating context without inner/outer split
|
|
282
|
+
|
|
283
|
+
Wrong:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
import type { CreateExpressContextOptions } from '@trpc/server/adapters/express';
|
|
287
|
+
|
|
288
|
+
export function createContext({ req }: CreateExpressContextOptions) {
|
|
289
|
+
return { db: prisma, user: getUserFromReq(req) };
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Correct:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import type { CreateExpressContextOptions } from '@trpc/server/adapters/express';
|
|
297
|
+
|
|
298
|
+
export function createContextInner(opts: { user?: User }) {
|
|
299
|
+
return { db: prisma, user: opts.user ?? null };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function createContext({ req }: CreateExpressContextOptions) {
|
|
303
|
+
return createContextInner({ user: getUserFromReq(req) });
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Without an inner context factory, server-side callers and tests must construct HTTP request objects to get context.
|
|
308
|
+
|
|
309
|
+
Source: www/docs/server/context.md
|
|
310
|
+
|
|
311
|
+
### [HIGH] Merging routers with different transformers
|
|
312
|
+
|
|
313
|
+
Wrong:
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
import { initTRPC } from '@trpc/server';
|
|
317
|
+
import superjson from 'superjson';
|
|
318
|
+
|
|
319
|
+
const t1 = initTRPC.create({ transformer: superjson });
|
|
320
|
+
const t2 = initTRPC.create();
|
|
321
|
+
|
|
322
|
+
const router1 = t1.router({ a: t1.procedure.query(() => 'a') });
|
|
323
|
+
const router2 = t2.router({ b: t2.procedure.query(() => 'b') });
|
|
324
|
+
|
|
325
|
+
t1.mergeRouters(router1, router2);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Correct:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import { initTRPC } from '@trpc/server';
|
|
332
|
+
import superjson from 'superjson';
|
|
333
|
+
|
|
334
|
+
const t = initTRPC.create({ transformer: superjson });
|
|
335
|
+
|
|
336
|
+
const router1 = t.router({ a: t.procedure.query(() => 'a') });
|
|
337
|
+
const router2 = t.router({ b: t.procedure.query(() => 'b') });
|
|
338
|
+
|
|
339
|
+
t.mergeRouters(router1, router2);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`t.mergeRouters()` throws at runtime if the routers were created with different transformer or errorFormatter configurations.
|
|
343
|
+
|
|
344
|
+
Source: packages/server/src/unstable-core-do-not-import/router.ts
|
|
345
|
+
|
|
346
|
+
### [CRITICAL] Importing appRouter value into client code
|
|
347
|
+
|
|
348
|
+
Wrong:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
// client.ts
|
|
352
|
+
import { appRouter } from '../server/router';
|
|
353
|
+
|
|
354
|
+
type AppRouter = typeof appRouter;
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Correct:
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
// client.ts
|
|
361
|
+
import type { AppRouter } from '../server/router';
|
|
362
|
+
|
|
363
|
+
// server/router.ts
|
|
364
|
+
export type AppRouter = typeof appRouter;
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Importing the appRouter value bundles the entire server into the client, even if you only use `typeof`.
|
|
368
|
+
|
|
369
|
+
Source: www/docs/server/routers.md
|
|
370
|
+
|
|
371
|
+
## See Also
|
|
372
|
+
|
|
373
|
+
- `middlewares` -- add auth, logging, context extension to procedures
|
|
374
|
+
- `validators` -- add input/output validation with Zod
|
|
375
|
+
- `error-handling` -- throw and format typed errors
|
|
376
|
+
- `server-side-calls` -- call procedures from server code
|
|
377
|
+
- `adapter-standalone` -- mount on Node.js HTTP server
|
|
378
|
+
- `adapter-fetch` -- mount on edge runtimes
|