Usage with edge runtimes using the Fetch API
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:
- Cloudflare Workers
- Deno Deploy
- Vercel Edge Runtime (& Next.js Edge Runtime)
Example apps
Description | Links |
---|---|
Cloudflare Workers example | Source |
Deno Deploy example | Source |
Next.js Edge Runtime example | Source |
Vercel Edge Runtime example | Source |
How to use tRPC server with an edge runtime
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.
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.
Fun fact: that also means you can use a tRPC server in your browser!
Common setup
Install dependencies
You can skip this step if you use Deno Deploy.
- npm
- yarn
- pnpm
sh
npm install @trpc/server zod
sh
npm install @trpc/server zod
sh
yarn add @trpc/server zod
sh
yarn add @trpc/server zod
sh
pnpm add @trpc/server zod
sh
pnpm add @trpc/server zod
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 to handle your queries, mutations and subscriptions.
A sample router is given below, save it in a file named router.ts
.
router.ts
router.tsts
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(({ input }) => {return users[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(({ input }) => {const id = Date.now().toString();const user: User = { id, ...input };users[user.id] = user;return user;}),});// export type definition of APIexport type AppRouter = typeof appRouter;
router.tsts
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(({ input }) => {return users[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(({ input }) => {const id = Date.now().toString();const user: User = { id, ...input };users[user.id] = user;return user;}),});// export type definition of APIexport 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
.
Create the context
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
:
context.ts
context.tsts
import { inferAsyncReturnType } from '@trpc/server';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 = inferAsyncReturnType<typeof createContext>;
context.tsts
import { inferAsyncReturnType } from '@trpc/server';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 = inferAsyncReturnType<typeof createContext>;
Runtimes-specific setup
Cloudflare Worker
Install dependencies
- npm
- yarn
- pnpm
sh
npm install -g wrangler
sh
npm install -g wrangler
sh
yarn global add wrangler
sh
yarn global add wrangler
sh
pnpm add -g wrangler
sh
pnpm add -g wrangler
Create Cloudflare Worker
server.tsts
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,});},};
server.tsts
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 |
Deno Deploy
Install dependencies
- macOS / Linux
- Windows
sh
curl -fsSL https://deno.land/x/install/install.sh | sh
sh
curl -fsSL https://deno.land/x/install/install.sh | sh
sh
irm https://deno.land/install.ps1 | iex
sh
irm https://deno.land/install.ps1 | iex
Update the imports in router.ts
router.tsts
import { initTRPC } from 'npm:@trpc/server';import { z } from 'npm:zod';import { Context } from './context.ts';
router.tsts
import { initTRPC } from 'npm:@trpc/server';import { z } from 'npm:zod';import { Context } from './context.ts';
Update the imports in context.ts
context.tsts
import { inferAsyncReturnType } from 'npm:@trpc/server';import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
context.tsts
import { inferAsyncReturnType } from 'npm:@trpc/server';import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
Create Deno Deploy Function
server.tsts
import { serve } from 'https://deno.land/std@0.140.0/http/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,});}serve(handler);
server.tsts
import { serve } from 'https://deno.land/std@0.140.0/http/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,});}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 |
Next.js Edge Runtime
See a full example here.
Vercel Edge Runtime
Install dependencies
- npm
- yarn
- pnpm
sh
npm install -g edge-runtime
sh
npm install -g edge-runtime
sh
yarn global add edge-runtime
sh
yarn global add edge-runtime
sh
pnpm add -g edge-runtime
sh
pnpm add -g edge-runtime
Create Edge Runtime Function
server.tsts
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,}),);});
server.tsts
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 |