Skip to content

Commit 4defad6

Browse files
authored
fix(trpc-server): auto-detect endpoint from route path (#1599)
* fix(trpc-server): auto-detect endpoint from route path * add changeset * bump zod
1 parent cc3ec5e commit 4defad6

File tree

5 files changed

+64
-7
lines changed

5 files changed

+64
-7
lines changed

.changeset/crazy-cougars-follow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hono/trpc-server': patch
3+
---
4+
5+
fix: auto-detect endpoint from route path

packages/trpc-server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"tsdown": "^0.15.9",
5050
"typescript": "^5.8.2",
5151
"vitest": "^3.2.4",
52-
"zod": "^3.20.2"
52+
"zod": "^4.0.5"
5353
},
5454
"engines": {
5555
"node": ">=16.0.0"

packages/trpc-server/src/index.test.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ describe('tRPC Adapter Middleware', () => {
2929
input: JSON.stringify({ '0': 'Hono' }),
3030
batch: '1',
3131
})
32-
const req = new Request(`http://localhost/trpc/hello?${searchParams.toString()}`)
33-
const res = await app.request(req)
32+
const res = await app.request(`/trpc/hello?${searchParams.toString()}`)
3433
expect(res.status).toBe(200)
3534
expect(await res.json()).toEqual([
3635
{
@@ -40,4 +39,43 @@ describe('tRPC Adapter Middleware', () => {
4039
},
4140
])
4241
})
42+
43+
it('Should auto-detect endpoint from /v1/* route', async () => {
44+
const app = new Hono()
45+
app.use('/v1/*', trpcServer({ router: appRouter }))
46+
47+
const searchParams = new URLSearchParams({
48+
input: JSON.stringify({ '0': 'World' }),
49+
batch: '1',
50+
})
51+
const res = await app.request(`/v1/hello?${searchParams.toString()}`)
52+
expect(res.status).toBe(200)
53+
expect(await res.json()).toEqual([{ result: { data: 'Hello World' } }])
54+
})
55+
56+
it('Should handle short path prefixes like /v/*', async () => {
57+
const app = new Hono()
58+
app.use('/v/*', trpcServer({ router: appRouter }))
59+
60+
const searchParams = new URLSearchParams({
61+
input: JSON.stringify({ '0': 'Test' }),
62+
batch: '1',
63+
})
64+
const res = await app.request(`/v/hello?${searchParams.toString()}`)
65+
expect(res.status).toBe(200)
66+
expect(await res.json()).toEqual([{ result: { data: 'Hello Test' } }])
67+
})
68+
69+
it('Should respect explicit endpoint parameter', async () => {
70+
const app = new Hono()
71+
app.use('/api/trpc/*', trpcServer({ router: appRouter, endpoint: '/api/trpc' }))
72+
73+
const searchParams = new URLSearchParams({
74+
input: JSON.stringify({ '0': 'Explicit' }),
75+
batch: '1',
76+
})
77+
const res = await app.request(`/api/trpc/hello?${searchParams.toString()}`)
78+
expect(res.status).toBe(200)
79+
expect(await res.json()).toEqual([{ result: { data: 'Hello Explicit' } }])
80+
})
4381
})

packages/trpc-server/src/index.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
} from '@trpc/server/adapters/fetch'
66
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
77
import type { Context, MiddlewareHandler } from 'hono'
8+
import { routePath } from 'hono/route'
89

910
type tRPCOptions = Omit<
1011
FetchHandlerRequestOptions<AnyRouter>,
@@ -18,22 +19,35 @@ type tRPCOptions = Omit<
1819
}
1920

2021
export const trpcServer = ({
21-
endpoint = '/trpc',
22+
endpoint,
2223
createContext,
2324
...rest
2425
}: tRPCOptions): MiddlewareHandler => {
2526
const bodyProps = new Set(['arrayBuffer', 'blob', 'formData', 'json', 'text'] as const)
2627
type BodyProp = typeof bodyProps extends Set<infer T> ? T : never
2728
return async (c) => {
2829
const canWithBody = c.req.method === 'GET' || c.req.method === 'HEAD'
30+
31+
// Auto-detect endpoint from route path if not explicitly provided
32+
let resolvedEndpoint = endpoint
33+
if (!endpoint) {
34+
const path = routePath(c)
35+
if (path) {
36+
// Remove wildcard suffix (e.g., "/v1/*" -> "/v1")
37+
resolvedEndpoint = path.replace(/\/\*+$/, '') || '/trpc'
38+
} else {
39+
resolvedEndpoint = '/trpc'
40+
}
41+
}
42+
2943
const res = fetchRequestHandler({
3044
...rest,
3145
createContext: async (opts) => ({
3246
...(createContext ? await createContext(opts, c) : {}),
3347
// propagate env by default
3448
env: c.env,
3549
}),
36-
endpoint,
50+
endpoint: resolvedEndpoint!,
3751
req: canWithBody
3852
? c.req.raw
3953
: new Proxy(c.req.raw, {

yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2389,7 +2389,7 @@ __metadata:
23892389
tsdown: "npm:^0.15.9"
23902390
typescript: "npm:^5.8.2"
23912391
vitest: "npm:^3.2.4"
2392-
zod: "npm:^3.20.2"
2392+
zod: "npm:^4.0.5"
23932393
peerDependencies:
23942394
"@trpc/server": ^10.10.0 || >11.0.0-rc
23952395
hono: ">=4.0.0"
@@ -17320,7 +17320,7 @@ __metadata:
1732017320
languageName: node
1732117321
linkType: hard
1732217322

17323-
"zod@npm:^3.20.2, zod@npm:^3.22.3":
17323+
"zod@npm:^3.22.3":
1732417324
version: 3.24.2
1732517325
resolution: "zod@npm:3.24.2"
1732617326
checksum: 10c0/c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565

0 commit comments

Comments
 (0)