www/versioned_docs/version-10.x/server/adapters/fetch.mdx
import TabItem from '@theme/TabItem'; import Tabs from '@theme/Tabs';
You can create a tRPC server within any edge runtime that follow the WinterCG, specifically the Minimum Common Web Platform API specification.
Some of these runtimes includes, but not limited to:
This also makes it easy to integrate into frameworks that uses the web platform APIs to represent requests and responses, such as:
tRPC provides a fetch adapter that uses the native Request and 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 out of the box. This adapter lets you convert your tRPC router into a Request handler that returns Response objects.
tRPC server uses the following Fetch APIs:
Request, ResponsefetchHeadersURLIf your runtime supports these APIs, you can use tRPC server.
:::tip Fun fact: that also means you can use a tRPC server in your browser! :::
:::tip You can skip this step if you use Deno Deploy. :::
<Tabs> <TabItem value="npm" label="npm" default>npm install @trpc/server zod
yarn add @trpc/server zod
pnpm add @trpc/server zod
bun add @trpc/server zod
Zod isn't a required dependency, but it's used in the sample router below.
First of all you need a router to handle your queries, mutations and subscriptions.
A sample router is given below, save it in a file named router.ts.
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
import { 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;
If your router file starts getting too big, split your router into several subrouters each implemented in its own file. Then merge them into a single root appRouter.
Then you need a context that will be created for each request.
A sample context is given below, save it in a file named context.ts:
import { 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>>;
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,
});
};
:::note You need the Wrangler CLI to run Cloudflare Workers. :::
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 |
where INPUT is a URI-encoded JSON string. |
| createUser | POST http://localhost:8787/trpc/createUser
with req.body of type User |
:::note This assumes you have Deno installed and setup. Refer to their getting started guide for more information. :::
router.tsimport { initTRPC } from 'npm:@trpc/server';
import { z } from 'npm:zod';
import { Context } from './context.ts';
context.tsimport { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
fetchRequestHandler with Oak in app.tsimport { 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 });
:::note This assumes you have Deno installed and setup. Refer to their getting started guide for more information. :::
:::tip See our example Deno Deploy app for a working example. :::
router.tsimport { initTRPC } from 'npm:@trpc/server';
import { z } from 'npm:zod';
import { Context } from './context.ts';
context.tsimport { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
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 |
where INPUT is a URI-encoded JSON string. |
| createUser | POST http://localhost:8000/trpc/createUser
with req.body of type User |
See a full example here.
import type { ActionArgs, LoaderArgs } 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: LoaderArgs) => {
return handleRequest(args);
};
export const action = async (args: ActionArgs) => {
return handleRequest(args);
};
function handleRequest(args: LoaderArgs | ActionArgs) {
return fetchRequestHandler({
endpoint: '/trpc',
req: args.request,
router: appRouter,
createContext,
});
}
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import type { APIEvent } from 'solid-start';
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 };
:::note See the official Vercel Edge Runtime documentation for more information. :::
:::tip See our example Vercel Edge Runtime app for a working example. :::
npm install -g edge-runtime
yarn global add edge-runtime
pnpm add -g edge-runtime
bun add -g edge-runtime
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { createContext } from './context';
import { appRouter } from './router';
addEventListener('fetch', (event) => {
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 |
where INPUT is a URI-encoded JSON string. |
| createUser | POST http://localhost:3000/trpc/createUser
with req.body of type User |