UNPKG

@trpc/server

Version:

The tRPC server library

477 lines (356 loc) • 14.3 kB
--- name: adapter-fetch description: Deploy tRPC on WinterCG-compliant edge runtimes with fetchRequestHandler() from @trpc/server/adapters/fetch. Supports Cloudflare Workers, Deno Deploy, Vercel Edge Runtime, Astro, Remix, SolidStart. FetchCreateContextFnOptions provides req (Request) and resHeaders (Headers) for context creation. The endpoint option must match the URL path prefix where the handler is mounted. requires: - server-setup sources: - www/v11/docs/server/adapters/fetch.md metadata: type: core library: trpc library_version: 11.25.0 --- <!-- Generated from www/v11/docs/server/adapters/fetch.md by www/build/skills.ts. Edit the docs instead. --> # Fetch / Edge Runtimes Adapter From https://trpc.io/docs/server/adapters/fetch You can create a tRPC server within any edge runtime that follow the [WinterCG](https://wintercg.org/), specifically the [Minimum Common Web Platform API](https://min-common-api.proposal.wintertc.org/) specification. Some of these runtimes include, but are not limited to: - Cloudflare Workers - Deno Deploy - Vercel Edge Runtime (& Next.js Edge Runtime) This also makes it easy to integrate into frameworks that use the web platform APIs to represent requests and responses, such as: - Astro (SSR mode) - Remix - SolidStart ## Example apps <table> <thead> <tr> <th>Description</th> <th>Links</th> </tr> </thead> <tbody> <tr> <td>Cloudflare Workers example</td> <td> <a href="https://github.com/trpc/trpc/tree/main/examples/cloudflare-workers"> Source </a> </td> </tr> <tr> <td>Deno Deploy example</td> <td> <a href="https://github.com/trpc/trpc/tree/main/examples/deno-deploy"> Source </a> </td> </tr> <tr> <td>Next.js Edge Runtime example</td> <td> <a href="https://github.com/trpc/trpc/tree/main/examples/next-edge-runtime"> Source </a> </td> </tr> <tr> <td>Vercel Edge Runtime example</td> <td> <a href="https://github.com/trpc/trpc/tree/main/examples/vercel-edge-runtime"> Source </a> </td> </tr> </tbody> </table> ## How to use tRPC server with an edge runtime tRPC provides a [fetch adapter](https://trpc.io/docs/server/adapters/fetch) that uses the native [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) APIs as input and output. The tRPC-specific code is the same across all runtimes, the only difference being how the response is returned. tRPC includes an adapter for the native [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) out of the box. This adapter lets you convert your tRPC router into a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) handler that returns [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects. ## Required Web APIs tRPC server uses the following Fetch APIs: - `Request`, `Response` - `fetch` - `Headers` - `URL` If your runtime supports these APIs, you can [use tRPC server](https://trpc.io/docs/server/adapters/fetch#how-to-use-trpc-server-with-an-edge-runtime). > [!TIP] > Fun fact: that also means you can use a tRPC server in your browser! ## Common setup ### Install dependencies > [!TIP] > You can skip this step if you use Deno Deploy. ```bash npm install @trpc/server @trpc/client zod ``` > [Zod](https://github.com/colinhacks/zod) isn't a required dependency, but it's used in the sample router below. ### Create the router First of all you need a [router](https://trpc.io/docs/server/routers) to handle your queries, mutations and subscriptions. A sample router is given below, save it in a file named `router.ts`. <details> <summary>router.ts</summary> ```ts no-check title="router.ts" import { initTRPC } from '@trpc/server'; import { z } from 'zod'; import type { Context } from './context'; type User = { id: string; name: string; bio?: string; }; const users: Record<string, User> = {}; export const t = initTRPC.context<Context>().create(); export const appRouter = t.router({ getUserById: t.procedure.input(z.string()).query((opts) => { return users[opts.input]; // input type is string }), createUser: t.procedure // validate input with Zod .input( z.object({ name: z.string().min(3), bio: z.string().max(142).optional(), }), ) .mutation((opts) => { const id = Date.now().toString(); const user: User = { id, ...opts.input }; users[user.id] = user; return user; }), }); // export type definition of API export type AppRouter = typeof appRouter; ``` </details> If your router file starts getting too big, split your router into several subrouters each implemented in its own file. Then [merge them](https://trpc.io/docs/server/merging-routers) into a single root `appRouter`. ### Create the context Then you need a [context](https://trpc.io/docs/server/context) that will be created for each request. A sample context is given below, save it in a file named `context.ts`: <details> <summary>context.ts</summary> ```ts no-check title="context.ts" import type { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch'; export function createContext({ req, resHeaders, }: FetchCreateContextFnOptions) { const user = { name: req.headers.get('username') ?? 'anonymous' }; return { req, resHeaders, user }; } export type Context = Awaited<ReturnType<typeof createContext>>; ``` </details> Headers you set on `resHeaders`, like `Set-Cookie`, are added to the response. `fetchRequestHandler` takes an `endpoint`, the URL path the handler is mounted on, plus the [options every adapter takes](https://trpc.io/docs/server/adapters#common-options), like `onError` and `maxBatchSize`. ## Runtime-specific setup ### Astro ```ts no-check title="src/pages/trpc/[trpc].ts" import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import type { APIRoute } from 'astro'; import { createContext } from '../../server/context'; import { appRouter } from '../../server/router'; export const ALL: APIRoute = (opts) => { return fetchRequestHandler({ endpoint: '/trpc', req: opts.request, router: appRouter, createContext, }); }; ``` ### Cloudflare Worker > [!NOTE] > You need the [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) to run Cloudflare Workers. #### Create Cloudflare Worker ```ts no-check title="server.ts" import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { createContext } from './context'; import { appRouter } from './router'; export default { async fetch(request: Request): Promise<Response> { return fetchRequestHandler({ endpoint: '/trpc', req: request, router: appRouter, createContext, }); }, }; ``` Run `wrangler dev server.ts` and your endpoints will be available via HTTP! | Endpoint | HTTP URI | | ------------ | -------------------------------------------------------------------------------------------------------------- | | `getUser` | `GET http://localhost:8787/trpc/getUserById?input=INPUT` <br/><br/>where `INPUT` is a URI-encoded JSON string. | | `createUser` | `POST http://localhost:8787/trpc/createUser` <br/><br/>with `req.body` of type `User` | ### Deno Oak > [!NOTE] > This assumes you have Deno installed and setup. Refer to their [getting started guide](https://deno.com/manual/getting_started/installation) for more information. #### Update the imports in `router.ts` ```ts title="router.ts" import { initTRPC } from 'npm:@trpc/server'; import { z } from 'npm:zod'; import { Context } from './context.ts'; ``` #### Update the imports in `context.ts` ```ts title="context.ts" import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch'; ``` #### Use `fetchRequestHandler` with Oak in `app.ts` ```ts title="app.ts" import { Application, Router } from 'https://deno.land/x/oak/mod.ts'; import { fetchRequestHandler } from 'npm:@trpc/server/adapters/fetch'; import { createContext } from './context.ts'; import { appRouter } from './router.ts'; const app = new Application(); const router = new Router(); router.all('/trpc/(.*)', async (ctx) => { const res = await fetchRequestHandler({ endpoint: '/trpc', req: new Request(ctx.request.url, { headers: ctx.request.headers, body: ctx.request.method !== 'GET' && ctx.request.method !== 'HEAD' ? ctx.request.body({ type: 'stream' }).value : void 0, method: ctx.request.method, }), router: appRouter, createContext, }); ctx.response.status = res.status; ctx.response.headers = res.headers; ctx.response.body = res.body; }); app.use(router.routes()); app.use(router.allowedMethods()); await app.listen({ port: 3000 }); ``` ### Deno Deploy > [!NOTE] > This assumes you have Deno installed and setup. Refer to their [getting started guide](https://deno.com/manual/getting_started/installation) for more information. > [!TIP] > See our example [Deno Deploy app](https://github.com/trpc/trpc/tree/main/examples/deno-deploy) for a working example. #### Update the imports in `router.ts` ```ts title="router.ts" import { initTRPC } from 'npm:@trpc/server'; import { z } from 'npm:zod'; import { Context } from './context.ts'; ``` #### Update the imports in `context.ts` ```ts title="context.ts" import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch'; ``` #### Create Deno Deploy Function ```ts title="server.ts" import { fetchRequestHandler } from 'npm:@trpc/server/adapters/fetch'; import { createContext } from './context.ts'; import { appRouter } from './router.ts'; function handler(request) { return fetchRequestHandler({ endpoint: '/trpc', req: request, router: appRouter, createContext, }); } Deno.serve(handler); ``` Run `deno run --allow-net=:8000 --allow-env ./server.ts` and your endpoints will be available via HTTP! | Endpoint | HTTP URI | | ------------ | -------------------------------------------------------------------------------------------------------------- | | `getUser` | `GET http://localhost:8000/trpc/getUserById?input=INPUT` <br/><br/>where `INPUT` is a URI-encoded JSON string. | | `createUser` | `POST http://localhost:8000/trpc/createUser` <br/><br/>with `req.body` of type `User` | ### Next.js Edge Runtime See a full example [here](https://github.com/trpc/trpc/tree/main/examples/next-edge-runtime). ### Remix ```ts no-check title="app/routes/trpc.$trpc.ts" import type { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node'; import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { createContext } from '~/server/context'; import { appRouter } from '~/server/router'; export const loader = async (args: LoaderFunctionArgs) => { return handleRequest(args); }; export const action = async (args: ActionFunctionArgs) => { return handleRequest(args); }; function handleRequest(args: LoaderFunctionArgs | ActionFunctionArgs) { return fetchRequestHandler({ endpoint: '/trpc', req: args.request, router: appRouter, createContext, }); } ``` ### SolidStart ```ts no-check title="src/routes/api/trpc/[trpc].ts" import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import type { APIEvent } from '@solidjs/start/server'; import { createContext } from '../../server/context'; import { appRouter } from '../../server/router'; const handler = (event: APIEvent) => fetchRequestHandler({ endpoint: '/api/trpc', req: event.request, router: appRouter, createContext, }); export { handler as GET, handler as POST }; ``` ### Vercel Edge Runtime > [!NOTE] > See the official [Vercel Edge Runtime documentation](https://edge-runtime.vercel.app/getting-started) for more information. > [!TIP] > See our example [Vercel Edge Runtime app](https://github.com/trpc/trpc/tree/main/examples/vercel-edge-runtime) for a working example. #### Install dependencies <div class="code-tabs" data-tabs="package-manager"> <div data-tab="npm" data-active> ```sh npm install -g edge-runtime ``` </div> <div data-tab="yarn"> ```sh yarn global add edge-runtime ``` </div> <div data-tab="pnpm"> ```sh pnpm add -g edge-runtime ``` </div> <div data-tab="bun"> ```sh bun add -g edge-runtime ``` </div> </div> #### Create Edge Runtime Function ```ts no-check title="server.ts" import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { createContext } from './context'; import { appRouter } from './router'; // Vercel Edge Runtime uses Service Worker-style addEventListener addEventListener('fetch', (event: any) => { return event.respondWith( fetchRequestHandler({ endpoint: '/trpc', req: event.request, router: appRouter, createContext, }), ); }); ``` Run `edge-runtime --listen server.ts --port 3000` and your endpoints will be available via HTTP! | Endpoint | HTTP URI | | ------------ | -------------------------------------------------------------------------------------------------------------- | | `getUser` | `GET http://localhost:3000/trpc/getUserById?input=INPUT` <br/><br/>where `INPUT` is a URI-encoded JSON string. | | `createUser` | `POST http://localhost:3000/trpc/createUser` <br/><br/>with `req.body` of type `User` | ## Common mistakes - **An `endpoint` that doesn't match the route.** tRPC removes `endpoint` from the start of the URL path to find the procedure, so a handler that serves `/api/trpc/*` needs `endpoint: '/api/trpc'`. With the wrong `endpoint`, every call returns `404 NOT_FOUND`. ## Related skills - `server-setup` (https://trpc.io/docs/server/overview): `npx @tanstack/intent@latest load @trpc/server#server-setup`