@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
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@trpc/server",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "11.14.
|
|
5
|
+
"version": "11.14.1",
|
|
6
6
|
"description": "The tRPC server library",
|
|
7
7
|
"author": "KATT",
|
|
8
8
|
"license": "MIT",
|
|
@@ -187,7 +187,10 @@
|
|
|
187
187
|
"shared",
|
|
188
188
|
"unstable-core-do-not-import",
|
|
189
189
|
"!**/*.test.*",
|
|
190
|
-
"!**/__tests__"
|
|
190
|
+
"!**/__tests__",
|
|
191
|
+
"skills",
|
|
192
|
+
"!skills/_artifacts",
|
|
193
|
+
"bin"
|
|
191
194
|
],
|
|
192
195
|
"publishConfig": {
|
|
193
196
|
"access": "public"
|
|
@@ -195,6 +198,7 @@
|
|
|
195
198
|
"devDependencies": {
|
|
196
199
|
"@fastify/websocket": "^11.0.0",
|
|
197
200
|
"@oxc-project/runtime": "0.115.0",
|
|
201
|
+
"@tanstack/intent": "^0.0.20",
|
|
198
202
|
"@tanstack/react-query": "^5.80.3",
|
|
199
203
|
"@types/aws-lambda": "^8.10.149",
|
|
200
204
|
"@types/express": "^5.0.0",
|
|
@@ -228,5 +232,11 @@
|
|
|
228
232
|
"peerDependencies": {
|
|
229
233
|
"typescript": ">=5.7.2"
|
|
230
234
|
},
|
|
231
|
-
"
|
|
235
|
+
"keywords": [
|
|
236
|
+
"tanstack-intent"
|
|
237
|
+
],
|
|
238
|
+
"bin": {
|
|
239
|
+
"intent": "./bin/intent.js"
|
|
240
|
+
},
|
|
241
|
+
"gitHead": "e896259af491fc4b1c9e8fc320817e2222bae869"
|
|
232
242
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adapter-aws-lambda
|
|
3
|
+
description: >
|
|
4
|
+
Deploy tRPC on AWS Lambda with awsLambdaRequestHandler() from
|
|
5
|
+
@trpc/server/adapters/aws-lambda for API Gateway v1 (REST, APIGatewayProxyEvent)
|
|
6
|
+
and v2 (HTTP, APIGatewayProxyEventV2), and Lambda Function URLs. Enable response
|
|
7
|
+
streaming with awsLambdaStreamingRequestHandler() wrapped in
|
|
8
|
+
awslambda.streamifyResponse(). CreateAWSLambdaContextOptions provides event and
|
|
9
|
+
context for context creation.
|
|
10
|
+
type: core
|
|
11
|
+
library: trpc
|
|
12
|
+
library_version: '11.14.0'
|
|
13
|
+
requires:
|
|
14
|
+
- server-setup
|
|
15
|
+
sources:
|
|
16
|
+
- www/docs/server/adapters/aws-lambda.md
|
|
17
|
+
- examples/lambda-url/
|
|
18
|
+
- examples/lambda-api-gateway/
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# tRPC — Adapter: AWS Lambda
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// server.ts
|
|
27
|
+
import { initTRPC } from '@trpc/server';
|
|
28
|
+
import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
29
|
+
import { awsLambdaRequestHandler } from '@trpc/server/adapters/aws-lambda';
|
|
30
|
+
import type { APIGatewayProxyEventV2 } from 'aws-lambda';
|
|
31
|
+
import { z } from 'zod';
|
|
32
|
+
|
|
33
|
+
const t = initTRPC.create();
|
|
34
|
+
|
|
35
|
+
const appRouter = t.router({
|
|
36
|
+
greet: t.procedure
|
|
37
|
+
.input(z.object({ name: z.string() }))
|
|
38
|
+
.query(({ input }) => ({ greeting: `Hello, ${input.name}!` })),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export type AppRouter = typeof appRouter;
|
|
42
|
+
|
|
43
|
+
const createContext = ({
|
|
44
|
+
event,
|
|
45
|
+
context,
|
|
46
|
+
}: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => ({
|
|
47
|
+
event,
|
|
48
|
+
lambdaContext: context,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const handler = awsLambdaRequestHandler({
|
|
52
|
+
router: appRouter,
|
|
53
|
+
createContext,
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Core Patterns
|
|
58
|
+
|
|
59
|
+
### API Gateway v1 (REST) handler
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
63
|
+
import { awsLambdaRequestHandler } from '@trpc/server/adapters/aws-lambda';
|
|
64
|
+
import type { APIGatewayProxyEvent } from 'aws-lambda';
|
|
65
|
+
import { appRouter } from './router';
|
|
66
|
+
|
|
67
|
+
const createContext = ({
|
|
68
|
+
event,
|
|
69
|
+
context,
|
|
70
|
+
}: CreateAWSLambdaContextOptions<APIGatewayProxyEvent>) => ({
|
|
71
|
+
user: event.requestContext.authorizer?.claims,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const handler = awsLambdaRequestHandler({
|
|
75
|
+
router: appRouter,
|
|
76
|
+
createContext,
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use `APIGatewayProxyEvent` for REST API (v1 payload format) and `APIGatewayProxyEventV2` for HTTP API (v2 payload format).
|
|
81
|
+
|
|
82
|
+
### Response streaming with awsLambdaStreamingRequestHandler
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
/// <reference types="aws-lambda" />
|
|
86
|
+
import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
87
|
+
import { awsLambdaStreamingRequestHandler } from '@trpc/server/adapters/aws-lambda';
|
|
88
|
+
import type { APIGatewayProxyEventV2 } from 'aws-lambda';
|
|
89
|
+
import { appRouter } from './router';
|
|
90
|
+
|
|
91
|
+
const createContext = ({
|
|
92
|
+
event,
|
|
93
|
+
context,
|
|
94
|
+
}: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => ({});
|
|
95
|
+
|
|
96
|
+
export const handler = awslambda.streamifyResponse(
|
|
97
|
+
awsLambdaStreamingRequestHandler({
|
|
98
|
+
router: appRouter,
|
|
99
|
+
createContext,
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Response streaming is supported for Lambda Function URLs and API Gateway REST APIs (with `responseTransferMode: STREAM`). The `awslambda` namespace is provided by the Lambda execution environment.
|
|
105
|
+
|
|
106
|
+
### Streaming async generator procedure
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { initTRPC } from '@trpc/server';
|
|
110
|
+
|
|
111
|
+
const t = initTRPC.create();
|
|
112
|
+
|
|
113
|
+
export const appRouter = t.router({
|
|
114
|
+
countdown: t.procedure.query(async function* () {
|
|
115
|
+
for (let i = 10; i >= 0; i--) {
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
117
|
+
yield i;
|
|
118
|
+
}
|
|
119
|
+
}),
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Pair with `httpBatchStreamLink` on the client for streamed responses.
|
|
124
|
+
|
|
125
|
+
## Common Mistakes
|
|
126
|
+
|
|
127
|
+
### HIGH Using httpBatchLink with per-procedure API Gateway resources
|
|
128
|
+
|
|
129
|
+
Wrong:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// API Gateway has a separate resource for each procedure
|
|
133
|
+
// e.g., /getUser, /createUser
|
|
134
|
+
// Client uses:
|
|
135
|
+
import { httpBatchLink } from '@trpc/client';
|
|
136
|
+
|
|
137
|
+
httpBatchLink({ url: 'https://api.example.com' });
|
|
138
|
+
// Batch request to /getUser,createUser → 404
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Correct:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { httpBatchLink, httpLink } from '@trpc/client';
|
|
145
|
+
|
|
146
|
+
// Option A: Single catch-all resource (e.g., /{proxy+})
|
|
147
|
+
httpBatchLink({ url: 'https://api.example.com' });
|
|
148
|
+
|
|
149
|
+
// Option B: Per-procedure resources with httpLink (no batching)
|
|
150
|
+
httpLink({ url: 'https://api.example.com' });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`httpBatchLink` sends multiple procedure names in the URL path (e.g., `getUser,createUser`). If API Gateway routes are per-procedure, the combined path does not match any resource and returns 404. Use a single catch-all resource or switch to `httpLink`.
|
|
154
|
+
|
|
155
|
+
Source: www/docs/server/adapters/aws-lambda.md
|
|
156
|
+
|
|
157
|
+
### HIGH Forgetting streamifyResponse wrapper for streaming
|
|
158
|
+
|
|
159
|
+
Wrong:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
export const handler = awsLambdaStreamingRequestHandler({
|
|
163
|
+
router: appRouter,
|
|
164
|
+
createContext,
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Correct:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
export const handler = awslambda.streamifyResponse(
|
|
172
|
+
awsLambdaStreamingRequestHandler({
|
|
173
|
+
router: appRouter,
|
|
174
|
+
createContext,
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`awsLambdaStreamingRequestHandler` requires wrapping with `awslambda.streamifyResponse()` to enable Lambda response streaming. Without it, Lambda treats the handler as a standard buffered response.
|
|
180
|
+
|
|
181
|
+
Source: www/docs/server/adapters/aws-lambda.md
|
|
182
|
+
|
|
183
|
+
## See Also
|
|
184
|
+
|
|
185
|
+
- **server-setup** -- `initTRPC.create()`, router/procedure definition, context
|
|
186
|
+
- **adapter-fetch** -- alternative for edge/serverless runtimes using Fetch API
|
|
187
|
+
- **links** -- `httpBatchLink` vs `httpLink` for API Gateway routing considerations
|
|
188
|
+
- AWS Lambda docs: https://docs.aws.amazon.com/lambda/latest/dg/welcome.html
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adapter-express
|
|
3
|
+
description: >
|
|
4
|
+
Mount tRPC as Express middleware with createExpressMiddleware() from
|
|
5
|
+
@trpc/server/adapters/express. Access Express req/res in createContext via
|
|
6
|
+
CreateExpressContextOptions. Mount at a path prefix like app.use('/trpc', ...).
|
|
7
|
+
Avoid global express.json() conflicting with tRPC body parsing for FormData.
|
|
8
|
+
type: core
|
|
9
|
+
library: trpc
|
|
10
|
+
library_version: '11.14.0'
|
|
11
|
+
requires:
|
|
12
|
+
- server-setup
|
|
13
|
+
sources:
|
|
14
|
+
- www/docs/server/adapters/express.md
|
|
15
|
+
- examples/express-server/src/server.ts
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# tRPC — Adapter: Express
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// server.ts
|
|
24
|
+
import { initTRPC } from '@trpc/server';
|
|
25
|
+
import * as trpcExpress from '@trpc/server/adapters/express';
|
|
26
|
+
import express from 'express';
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
|
|
29
|
+
const createContext = ({
|
|
30
|
+
req,
|
|
31
|
+
res,
|
|
32
|
+
}: trpcExpress.CreateExpressContextOptions) => {
|
|
33
|
+
return { req, res };
|
|
34
|
+
};
|
|
35
|
+
type Context = Awaited<ReturnType<typeof createContext>>;
|
|
36
|
+
|
|
37
|
+
const t = initTRPC.context<Context>().create();
|
|
38
|
+
|
|
39
|
+
const appRouter = t.router({
|
|
40
|
+
greet: t.procedure
|
|
41
|
+
.input(z.object({ name: z.string() }))
|
|
42
|
+
.query(({ input }) => ({ greeting: `Hello, ${input.name}!` })),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export type AppRouter = typeof appRouter;
|
|
46
|
+
|
|
47
|
+
const app = express();
|
|
48
|
+
|
|
49
|
+
app.use(
|
|
50
|
+
'/trpc',
|
|
51
|
+
trpcExpress.createExpressMiddleware({
|
|
52
|
+
router: appRouter,
|
|
53
|
+
createContext,
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
app.listen(4000, () => {
|
|
58
|
+
console.log('Listening on http://localhost:4000');
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Core Patterns
|
|
63
|
+
|
|
64
|
+
### Accessing Express req/res in context
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import * as trpcExpress from '@trpc/server/adapters/express';
|
|
68
|
+
|
|
69
|
+
const createContext = ({
|
|
70
|
+
req,
|
|
71
|
+
res,
|
|
72
|
+
}: trpcExpress.CreateExpressContextOptions) => {
|
|
73
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
74
|
+
return { token, res };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type Context = Awaited<ReturnType<typeof createContext>>;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`CreateExpressContextOptions` provides typed access to the Express `req` (IncomingMessage) and `res` (ServerResponse).
|
|
81
|
+
|
|
82
|
+
### Adding tRPC alongside existing Express routes
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import * as trpcExpress from '@trpc/server/adapters/express';
|
|
86
|
+
import cors from 'cors';
|
|
87
|
+
import express from 'express';
|
|
88
|
+
import { createContext } from './context';
|
|
89
|
+
import { appRouter } from './router';
|
|
90
|
+
|
|
91
|
+
const app = express();
|
|
92
|
+
|
|
93
|
+
app.use(cors());
|
|
94
|
+
|
|
95
|
+
app.get('/health', (_req, res) => {
|
|
96
|
+
res.json({ status: 'ok' });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
app.use(
|
|
100
|
+
'/trpc',
|
|
101
|
+
trpcExpress.createExpressMiddleware({
|
|
102
|
+
router: appRouter,
|
|
103
|
+
createContext,
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
app.listen(4000);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Common Mistakes
|
|
111
|
+
|
|
112
|
+
### HIGH Global express.json() consuming tRPC request body
|
|
113
|
+
|
|
114
|
+
Wrong:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
const app = express();
|
|
118
|
+
app.use(express.json()); // global body parser
|
|
119
|
+
app.use(
|
|
120
|
+
'/trpc',
|
|
121
|
+
trpcExpress.createExpressMiddleware({
|
|
122
|
+
router: appRouter,
|
|
123
|
+
createContext,
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Correct:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const app = express();
|
|
132
|
+
// Only apply body parser to non-tRPC routes
|
|
133
|
+
app.use('/api', express.json());
|
|
134
|
+
app.use(
|
|
135
|
+
'/trpc',
|
|
136
|
+
trpcExpress.createExpressMiddleware({
|
|
137
|
+
router: appRouter,
|
|
138
|
+
createContext,
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
If `express.json()` is applied globally before the tRPC middleware, it consumes and parses the request body. tRPC then receives an already-parsed body, which breaks FormData and binary content type handling.
|
|
144
|
+
|
|
145
|
+
Source: www/docs/server/non-json-content-types.md
|
|
146
|
+
|
|
147
|
+
## See Also
|
|
148
|
+
|
|
149
|
+
- **server-setup** -- `initTRPC.create()`, router/procedure definition, context
|
|
150
|
+
- **adapter-standalone** -- simpler adapter when Express middleware ecosystem is not needed
|
|
151
|
+
- **auth** -- extracting JWT from `req.headers.authorization` in context
|
|
152
|
+
- Express docs: https://expressjs.com/en/guide/using-middleware.html
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adapter-fastify
|
|
3
|
+
description: >
|
|
4
|
+
Mount tRPC as a Fastify plugin with fastifyTRPCPlugin from
|
|
5
|
+
@trpc/server/adapters/fastify. Configure prefix, trpcOptions (router,
|
|
6
|
+
createContext, onError). Enable WebSocket subscriptions with useWSS and
|
|
7
|
+
@fastify/websocket. Set routerOptions.maxParamLength for batch requests.
|
|
8
|
+
Requires Fastify v5+. FastifyTRPCPluginOptions for type-safe onError.
|
|
9
|
+
CreateFastifyContextOptions provides req, res.
|
|
10
|
+
type: core
|
|
11
|
+
library: trpc
|
|
12
|
+
library_version: '11.14.0'
|
|
13
|
+
requires:
|
|
14
|
+
- server-setup
|
|
15
|
+
sources:
|
|
16
|
+
- www/docs/server/adapters/fastify.md
|
|
17
|
+
- examples/fastify-server/
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# tRPC — Adapter: Fastify
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// server.ts
|
|
26
|
+
import { initTRPC } from '@trpc/server';
|
|
27
|
+
import {
|
|
28
|
+
fastifyTRPCPlugin,
|
|
29
|
+
FastifyTRPCPluginOptions,
|
|
30
|
+
} from '@trpc/server/adapters/fastify';
|
|
31
|
+
import type { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
|
|
32
|
+
import fastify from 'fastify';
|
|
33
|
+
import { z } from 'zod';
|
|
34
|
+
|
|
35
|
+
function createContext({ req, res }: CreateFastifyContextOptions) {
|
|
36
|
+
return { req, res };
|
|
37
|
+
}
|
|
38
|
+
type Context = Awaited<ReturnType<typeof createContext>>;
|
|
39
|
+
|
|
40
|
+
const t = initTRPC.context<Context>().create();
|
|
41
|
+
|
|
42
|
+
const appRouter = t.router({
|
|
43
|
+
greet: t.procedure
|
|
44
|
+
.input(z.object({ name: z.string() }))
|
|
45
|
+
.query(({ input }) => ({ greeting: `Hello, ${input.name}!` })),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export type AppRouter = typeof appRouter;
|
|
49
|
+
|
|
50
|
+
const server = fastify({
|
|
51
|
+
routerOptions: {
|
|
52
|
+
maxParamLength: 5000,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
server.register(fastifyTRPCPlugin, {
|
|
57
|
+
prefix: '/trpc',
|
|
58
|
+
trpcOptions: {
|
|
59
|
+
router: appRouter,
|
|
60
|
+
createContext,
|
|
61
|
+
onError({ path, error }) {
|
|
62
|
+
console.error(`Error in tRPC handler on path '${path}':`, error);
|
|
63
|
+
},
|
|
64
|
+
} satisfies FastifyTRPCPluginOptions<AppRouter>['trpcOptions'],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
(async () => {
|
|
68
|
+
try {
|
|
69
|
+
await server.listen({ port: 3000 });
|
|
70
|
+
console.log('Listening on http://localhost:3000');
|
|
71
|
+
} catch (err) {
|
|
72
|
+
server.log.error(err);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
})();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Core Patterns
|
|
79
|
+
|
|
80
|
+
### WebSocket subscriptions with @fastify/websocket
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import ws from '@fastify/websocket';
|
|
84
|
+
import {
|
|
85
|
+
fastifyTRPCPlugin,
|
|
86
|
+
FastifyTRPCPluginOptions,
|
|
87
|
+
} from '@trpc/server/adapters/fastify';
|
|
88
|
+
import fastify from 'fastify';
|
|
89
|
+
import { createContext } from './context';
|
|
90
|
+
import { appRouter, type AppRouter } from './router';
|
|
91
|
+
|
|
92
|
+
const server = fastify({
|
|
93
|
+
routerOptions: { maxParamLength: 5000 },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Register @fastify/websocket BEFORE the tRPC plugin
|
|
97
|
+
server.register(ws);
|
|
98
|
+
|
|
99
|
+
server.register(fastifyTRPCPlugin, {
|
|
100
|
+
prefix: '/trpc',
|
|
101
|
+
useWSS: true,
|
|
102
|
+
trpcOptions: {
|
|
103
|
+
router: appRouter,
|
|
104
|
+
createContext,
|
|
105
|
+
keepAlive: {
|
|
106
|
+
enabled: true,
|
|
107
|
+
pingMs: 30000,
|
|
108
|
+
pongWaitMs: 5000,
|
|
109
|
+
},
|
|
110
|
+
} satisfies FastifyTRPCPluginOptions<AppRouter>['trpcOptions'],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
server.listen({ port: 3000 });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Install: `npm install @fastify/websocket` (minimum version 3.11.0)
|
|
117
|
+
|
|
118
|
+
### Type-safe onError with satisfies
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
server.register(fastifyTRPCPlugin, {
|
|
122
|
+
prefix: '/trpc',
|
|
123
|
+
trpcOptions: {
|
|
124
|
+
router: appRouter,
|
|
125
|
+
createContext,
|
|
126
|
+
onError({ path, error, type, input }) {
|
|
127
|
+
console.error(`[${type}] ${path}:`, error.message);
|
|
128
|
+
},
|
|
129
|
+
} satisfies FastifyTRPCPluginOptions<AppRouter>['trpcOptions'],
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Due to Fastify plugin type inference limitations, use `satisfies FastifyTRPCPluginOptions<AppRouter>['trpcOptions']` to get correct types on `onError` and other callbacks.
|
|
134
|
+
|
|
135
|
+
## Common Mistakes
|
|
136
|
+
|
|
137
|
+
### HIGH Registering @fastify/websocket after tRPC plugin
|
|
138
|
+
|
|
139
|
+
Wrong:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
server.register(fastifyTRPCPlugin, {
|
|
143
|
+
useWSS: true,
|
|
144
|
+
trpcOptions: { router: appRouter, createContext },
|
|
145
|
+
});
|
|
146
|
+
server.register(ws); // too late!
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Correct:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
server.register(ws); // register FIRST
|
|
153
|
+
server.register(fastifyTRPCPlugin, {
|
|
154
|
+
useWSS: true,
|
|
155
|
+
trpcOptions: { router: appRouter, createContext },
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The WebSocket plugin must be registered before the tRPC plugin. Reverse order causes WebSocket routes to not be recognized.
|
|
160
|
+
|
|
161
|
+
Source: www/docs/server/adapters/fastify.md
|
|
162
|
+
|
|
163
|
+
### HIGH Missing maxParamLength for batch requests
|
|
164
|
+
|
|
165
|
+
Wrong:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const server = fastify();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Correct:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
const server = fastify({
|
|
175
|
+
routerOptions: { maxParamLength: 5000 },
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Fastify defaults to `maxParamLength: 100`. Batch requests from `httpBatchLink` encode multiple procedure names in the URL path parameter, which easily exceeds 100 characters and returns a 404.
|
|
180
|
+
|
|
181
|
+
Source: www/docs/server/adapters/fastify.md
|
|
182
|
+
|
|
183
|
+
### CRITICAL Using Fastify v4 with tRPC v11
|
|
184
|
+
|
|
185
|
+
Wrong:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{ "dependencies": { "fastify": "^4.0.0" } }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Correct:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{ "dependencies": { "fastify": "^5.0.0" } }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
tRPC v11 requires Fastify v5+. Fastify v4 may return empty responses without errors due to incompatible response handling.
|
|
198
|
+
|
|
199
|
+
Source: www/docs/server/adapters/fastify.md
|
|
200
|
+
|
|
201
|
+
## See Also
|
|
202
|
+
|
|
203
|
+
- **server-setup** -- `initTRPC.create()`, router/procedure definition, context
|
|
204
|
+
- **subscriptions** -- async generator subscriptions, `tracked()`, `keepAlive`
|
|
205
|
+
- **adapter-standalone** -- simpler adapter when Fastify features are not needed
|
|
206
|
+
- Fastify docs: https://fastify.dev/docs/latest/
|