Skip to main content

Cloudflare Worker's Bindings in tRPC

ยท 3 min read

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!