Cloudflare Worker's Bindings in tRPC 🧙♀️
When using Cloudflare Workers, the main selling point is you can integrate with a lot of other Cloudflare APIs, like D1 (database), R2 (storage) and Durable Objects (websockets), and more. However, there isn’t any documentation on how to do that.
The up-to-date way to implement Cloudflare Workers is by using the module workers syntax, as follows:
export default {
async fetch(
request: Request,
env: Env, // 👈 not shown on the tRPC docs
ctx: ExecutionContext, // 👈 not shown on the tRPC docs
): Promise<Response> {
return fetchRequestHandler({
endpoint: trpcApiPath,
req: request,
router: appRouter,
createContext,
});
},
};
You get the env
object here, allowing you to access cloudflare bindings. Often, examples won’t include the 2nd and 3rd parameters of fetch
, showing only async fetch(request: Request): Promise<Response> {...}
. For example, the tRPC docs.
Actually, we just need to use env
in createContext
. Pretty simple. So let’s try.
Use env
in createContext
🐛
The easiest option is to “inline” the createContext
argument. However, this won’t actually work because we need the createContext
function to instantiate tRPC / appRouter: const t = initTRPC.context<Context>().create();
, in another file. You might also get into a circular dependency hell.
import { FetchCreateContextFnOptions, fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from './ts/trpc';
import { trpcApiPath } from './ts/trpcPath';
import { Env } from './ts/worker-configuration';
import { drizzle } from 'drizzle-orm/d1';
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
return fetchRequestHandler({
endpoint: trpcApiPath,
req: request,
router: appRouter,
createContext: ({ req, resHeaders }: FetchCreateContextFnOptions) =>
// 🎸 You have access to `env` here, so
// use a binding, for example: `env.SERVICE.fetch()`
const user = { name: req.headers.get('username') ?? 'anonymous' };
const db = drizzle(env.DB);
return { req, resHeaders, user, db };
},
});
},
};
Refactoring… it works. 😍
// src/index.ts
import { FetchCreateContextFnOptions, fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from './trpc/appRouter';
import { trpcApiPath } from './trpc/trpcPath';
import { Env } from './worker-configuration';
import { createContext } from './trpc/context';
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
if (request.method === 'OPTIONS') {
const response = new Response(null, { status: 200, headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "*" } });
return response
}
return fetchRequestHandler({
endpoint: trpcApiPath,
req: request,
router: appRouter,
createContext: (options: FetchCreateContextFnOptions) => createContext({ ...options, env, ctx }),
});
},
};
// src/trpc/context.ts
import { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { Env } from './ts/worker-configuration';
import { drizzle } from 'drizzle-orm/d1';
const createContext = async ({
req,
env,
resHeaders,
}: FetchCreateContextFnOptions & { env: Env, ctx: ExecutionContext }) => {
console.log(`Gotttt itt: ${env.MY_ENV_VAR}`);
// Now use a binding, for example: `env.SERVICE.fetch()`
const user = { name: req.headers.get('username') ?? 'anonymous' };
const db = drizzle(env.DB);
return { req, resHeaders, user, db };
};
export type Context = inferAsyncReturnType<typeof createContext>;
Benefits 📈
This approach:
- avoids a third party dependency
- explains how to do it, so you can change it or extend it
- so when the Cloudflare Worker’s API changes in the future, your understanding can help you upgrade to the new API.
- Avoids hijacking the Cloudflare “entry point”, so you can still use the official way of defining a Cloudflare worker. For example, to use static assets,
- There is an alternative (a plugin,
cloudflare-pages-plugin-trpc
) which doesn’t always work, for example when you’re using Astro + tRPC + Cloudflare Workers. See it’s internals.
Conclusion
I’d be interested to know what you’re building, and how you’re using tRPC and Cloudflare!