feat(trpc): access hono context from trpc procedures (#458)

* trpc: access hono context from trpc procedures

* chore: Add changeset
pull/480/head
Benjamín Eidelman 2024-04-26 23:46:09 -03:00 committed by GitHub
parent 9e60a47df4
commit 2526e1e685
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 129 additions and 5 deletions

View File

@ -0,0 +1,5 @@
---
'@hono/trpc-server': minor
---
access hono context from trpc procedures

View File

@ -67,6 +67,52 @@ const client = createTRPCProxyClient<AppRouter>({
console.log(await client.hello.query('Hono')) console.log(await client.hello.query('Hono'))
``` ```
## Context
You can also access `c.env` from hono context from the trpc `ctx`. eg. here's an example using cloudflare D1 binding available as `env.DB`
```ts
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
type Env = {
DB: D1Database;
}
type HonoContext = {
env: Env,
};
const t = initTRPC.context<HonoContext>().create()
const publicProcedure = t.procedure
const router = t.router
export const appRouter = router({
usersCount: publicProcedure.query(({ input, ctx }) => {
const result = await ctx.env.DB.prepare("SELECT count(*) from user;").all();
return result.results[0].count;
}),
})
export type AppRouter = typeof appRouter
```
For further control, you can optionally specify a `createContext` that in this case will receive the hono context as 2nd argument:
```ts
app.use(
'/trpc/*',
trpcServer({
router: appRouter,
createContext: (_opts, c) => ({
// c is the hono context
var1: c.env.MY_VAR1,
var2: c.req.header('X-VAR2'),
})
})
)
```
## Author ## Author
Yusuke Wada <https://github.com/yusukebe> Yusuke Wada <https://github.com/yusukebe>

View File

@ -1,15 +1,21 @@
import type { AnyRouter } from '@trpc/server' import type { AnyRouter } from '@trpc/server'
import type { FetchHandlerRequestOptions } from '@trpc/server/adapters/fetch' import type { FetchCreateContextFnOptions, FetchHandlerRequestOptions } from '@trpc/server/adapters/fetch'
import { fetchRequestHandler } from '@trpc/server/adapters/fetch' import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import type { MiddlewareHandler } from 'hono' import type { Context, MiddlewareHandler } from 'hono'
type tRPCOptions = Omit<FetchHandlerRequestOptions<AnyRouter>, 'req' | 'endpoint'> & type tRPCOptions = Omit<FetchHandlerRequestOptions<AnyRouter>, 'req' | 'endpoint' | 'createContext'> &
Partial<Pick<FetchHandlerRequestOptions<AnyRouter>, 'endpoint'>> Partial<Pick<FetchHandlerRequestOptions<AnyRouter>, 'endpoint'>> &
{ createContext? (opts: FetchCreateContextFnOptions, c: Context): Record<string, unknown> }
export const trpcServer = ({ endpoint = '/trpc', ...rest }: tRPCOptions): MiddlewareHandler => { export const trpcServer = ({ endpoint = '/trpc', createContext, ...rest }: tRPCOptions): MiddlewareHandler => {
return async (c) => { return async (c) => {
const res = fetchRequestHandler({ const res = fetchRequestHandler({
...rest, ...rest,
createContext: (opts) => ({
...createContext ? createContext(opts, c) : {},
// propagate env by default
env: c.env,
}),
endpoint, endpoint,
req: c.req.raw, req: c.req.raw,
}) })

View File

@ -0,0 +1,67 @@
import { initTRPC } from '@trpc/server'
import { Hono } from 'hono'
import { trpcServer } from '../src'
describe('tRPC Adapter Middleware passing Context', () => {
type Env = {
NAME: string;
}
type HonoContext = {
env: Env,
batch: string;
};
const t = initTRPC.context<HonoContext>().create()
const publicProcedure = t.procedure.use(
t.middleware((opts) => {
return opts.next({
ctx: {
// add .env into context, simulating a middleware as cloudflare pages
env: {
DB: {
getName: () => 'World'
},
}
},
})
}),
)
const router = t.router
const appRouter = router({
hello: publicProcedure.query(({ ctx }) => {
return `Hello ${ctx.env.DB.getName()}, batch is ${ctx.batch}`
}),
})
const app = new Hono()
app.use(
'/trpc/*',
trpcServer({
router: appRouter,
// optional createContext, additional `c` arg with the hono context
createContext: (_opts, c) => ({
batch: c.req.query('batch'),
})
})
)
it.only('Should return 200 response', async () => {
const searchParams = new URLSearchParams({
input: JSON.stringify({ '0': 'Hono' }),
batch: '1',
})
const req = new Request(`http://localhost/trpc/hello?${searchParams.toString()}`)
const res = await app.request(req)
expect(res.status).toBe(200)
expect(await res.json()).toEqual([
{
result: {
data: 'Hello World, batch is 1',
},
},
])
})
})