# Fetch / Edge Runtimes Adapter

> 

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.md) 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](#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.


```package-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.md) 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 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.md) into a single root `appRouter`.

### Create the context

Then you need a [context](https://trpc.io/docs/server/context.md) 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 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.md#common-options), like `onError` and `maxBatchSize`.

## Runtime-specific setup

### Astro

```ts 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 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 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 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 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`.
