Skip to content
tRPC

Fetch / Edge Runtimes Adapter

On this page

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 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

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.

Common setup

Install dependencies

pnpm add @trpc/server @trpc/client zod
npm install @trpc/server @trpc/client zod
yarn add @trpc/server @trpc/client zod
bun add @trpc/server @trpc/client 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.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
import { import zz } from 'zod';
import type { 
type Context = {
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}
Context
} from './context';
type
type User = {
    id: string;
    name: string;
    bio?: string;
}
User
= {
id: stringid: string; name: stringname: string; bio?: string | undefinedbio?: string; }; const const users: Record<string, User>users: type Record<K extends keyof any, T> = { [P in K]: T; }Record<string,
type User = {
    id: string;
    name: string;
    bio?: string;
}
User
> = {};
export const
const t: TRPCRootObject<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, RuntimeConfigOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object>, {
    ctx: {
        req: Request;
        resHeaders: Headers;
        user: {
            name: string;
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.
TRPCBuilder<object, object>.context<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}>(): TRPCBuilder<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object>
context
<
type Context = {
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}
Context
>().
TRPCBuilder<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object>.create<RuntimeConfigOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object>>(opts?: RuntimeConfigOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object> | undefined): TRPCRootObject<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, RuntimeConfigOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object>, {
    ctx: {
        req: Request;
        resHeaders: Headers;
        user: {
            name: string;
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
export const
const appRouter: BuiltRouter<{
    ctx: {
        req: Request;
        resHeaders: Headers;
        user: {
            name: string;
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
const t: TRPCRootObject<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, RuntimeConfigOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object>, {
    ctx: {
        req: Request;
        resHeaders: Headers;
        user: {
            name: string;
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object, RuntimeConfigOptions<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object>, { ...; }>.router: RouterBuilder
<{
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<...>
router
({
getUserById: QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
getUserById
:
const t: TRPCRootObject<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, RuntimeConfigOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object>, {
    ctx: {
        req: Request;
        resHeaders: Headers;
        user: {
            name: string;
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object, RuntimeConfigOptions<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object>, { ...; }>.procedure: ProcedureBuilder<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
.
ProcedureBuilder<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodString>(schema: z.ZodString): ProcedureBuilder<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()).
ProcedureBuilder<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<User>(resolver: ProcedureResolver<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, string, UnsetMarker, User>): QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
((
opts: ProcedureResolverOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, string>
opts
) => {
return const users: Record<string, User>users[
opts: ProcedureResolverOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, string>
opts
.ProcedureResolverOptions<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object, object, string>.input: stringinput]; // input type is string
}),
createUser: MutationProcedure<{
    input: {
        name: string;
        bio?: string | undefined;
    };
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
createUser
:
const t: TRPCRootObject<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, RuntimeConfigOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object>, {
    ctx: {
        req: Request;
        resHeaders: Headers;
        user: {
            name: string;
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object, RuntimeConfigOptions<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object>, { ...; }>.procedure: ProcedureBuilder<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
// validate input with Zod .
ProcedureBuilder<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
    name: z.ZodString;
    bio: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>(schema: z.ZodObject<{
    name: z.ZodString;
    bio: z.ZodOptional<z.ZodString>;
}, z.core.$strip>): ProcedureBuilder<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, {
    name: string;
    bio?: string | undefined;
}, {
    name: string;
    bio?: string | undefined;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(
import zz.
function object<{
    name: z.ZodString;
    bio: z.ZodOptional<z.ZodString>;
}>(shape?: {
    name: z.ZodString;
    bio: z.ZodOptional<z.ZodString>;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    name: z.ZodString;
    bio: z.ZodOptional<z.ZodString>;
}, z.core.$strip>
object
({
name: z.ZodStringname: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()._ZodString<$ZodStringInternals<string>>.min(minLength: number, params?: string | z.core.$ZodCheckMinLengthParams): z.ZodStringmin(3), bio: z.ZodOptional<z.ZodString>bio: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()._ZodString<$ZodStringInternals<string>>.max(maxLength: number, params?: string | z.core.$ZodCheckMaxLengthParams): z.ZodStringmax(142).ZodType<any, any, $ZodStringInternals<string>>.optional(): z.ZodOptional<z.ZodString>optional(), }), ) .
ProcedureBuilder<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object, object, { name: string; bio?: string | undefined; }, { name: string; bio?: string | undefined; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.mutation<User>(resolver: ProcedureResolver<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, {
    name: string;
    bio?: string | undefined;
}, UnsetMarker, User>): MutationProcedure<{
    input: {
        name: string;
        bio?: string | undefined;
    };
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
mutation
((
opts: ProcedureResolverOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, {
    name: string;
    bio?: string | undefined;
}>
opts
) => {
const const id: stringid = var Date: DateConstructorDate.DateConstructor.now(): numbernow().Number.toString(radix?: number): stringtoString(); const const user: Useruser:
type User = {
    id: string;
    name: string;
    bio?: string;
}
User
= { id: stringid, ...
opts: ProcedureResolverOptions<{
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}, object, object, {
    name: string;
    bio?: string | undefined;
}>
opts
.
ProcedureResolverOptions<{ req: Request; resHeaders: Headers; user: { name: string; }; }, object, object, { name: string; bio?: string | undefined; }>.input: {
    name: string;
    bio?: string | undefined;
}
input
};
const users: Record<string, User>users[const user: Useruser.id: stringid] = const user: Useruser; return const user: Useruser; }), }); // export type definition of API export type
type AppRouter = Router<{
    ctx: {
        req: Request;
        resHeaders: Headers;
        user: {
            name: string;
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<...>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: {
        req: Request;
        resHeaders: Headers;
        user: {
            name: string;
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
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.ts
import type { 
type FetchCreateContextFnOptions = {
    req: Request;
    resHeaders: Headers;
    info: TRPCRequestInfo;
}
FetchCreateContextFnOptions
} from '@trpc/server/adapters/fetch';
export function
function createContext({ req, resHeaders, }: FetchCreateContextFnOptions): {
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}
createContext
({
req: Requestreq, resHeaders: HeadersresHeaders, }:
type FetchCreateContextFnOptions = {
    req: Request;
    resHeaders: Headers;
    info: TRPCRequestInfo;
}
FetchCreateContextFnOptions
) {
const
const user: {
    name: string;
}
user
= { name: stringname: req: Requestreq.Request.headers: Headersheaders.Headers.get(name: string): string | nullget('username') ?? 'anonymous' };
return { req: Requestreq, resHeaders: HeadersresHeaders,
user: {
    name: string;
}
user
};
} export type
type Context = {
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}
Context
=
type Awaited<T> = T extends null | undefined ? T : T extends object & {
    then(onfulfilled: infer F, ...args: infer _): any;
} ? F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never : T
Awaited
<type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : anyReturnType<typeof
function createContext({ req, resHeaders, }: FetchCreateContextFnOptions): {
    req: Request;
    resHeaders: Headers;
    user: {
        name: string;
    };
}
createContext
>>;

Runtime-specific setup

Astro

src/pages/trpc/[trpc].ts
import { function fetchRequestHandler<TRouter extends AnyRouter>(opts: FetchHandlerRequestOptions<TRouter>): Promise<Response>fetchRequestHandler } from '@trpc/server/adapters/fetch';
import type { 
type APIRoute = (context: {
    request: Request;
}) => Response | Promise<Response>
APIRoute
} from 'astro';
import {
function createContext(opts: FetchCreateContextFnOptions): {
    user: {
        name: string;
    };
}
createContext
} from '../../server/context';
import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
} from '../../server/router';
export const const ALL: APIRouteALL:
type APIRoute = (context: {
    request: Request;
}) => Response | Promise<Response>
APIRoute
= (
opts: {
    request: Request;
}
opts
) => {
return
fetchRequestHandler<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: FetchHandlerRequestOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>): Promise<Response>
fetchRequestHandler
({
endpoint: stringendpoint: '/trpc', req: Requestreq:
opts: {
    request: Request;
}
opts
.request: Requestrequest,
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, Request>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
,
createContext?: FetchCreateContextFn<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | undefined
createContext
,
}); };

Cloudflare Worker

Create Cloudflare Worker

server.ts
import { function fetchRequestHandler<TRouter extends AnyRouter>(opts: FetchHandlerRequestOptions<TRouter>): Promise<Response>fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { 
function createContext(opts: FetchCreateContextFnOptions): {
    user: {
        name: string;
    };
}
createContext
} from './context';
import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
} from './router';
export default { async function fetch(request: Request): Promise<Response>fetch(request: Requestrequest: Request): interface Promise<T>Promise<Response> { return
fetchRequestHandler<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: FetchHandlerRequestOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>): Promise<Response>
fetchRequestHandler
({
endpoint: stringendpoint: '/trpc', req: Requestreq: request: Requestrequest,
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, Request>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
,
createContext?: FetchCreateContextFn<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | undefined
createContext
,
}); }, };

Run wrangler dev server.ts and your endpoints will be available via HTTP!

EndpointHTTP URI
getUserGET http://localhost:8787/trpc/getUserById?input=INPUT

where INPUT is a URI-encoded JSON string.
createUserPOST http://localhost:8787/trpc/createUser

with req.body of type User

Deno Oak

Update the imports in router.ts

router.ts
import { initTRPC } from 'npm:@trpc/server';
import { z } from 'npm:zod';
import { Context } from './context.ts';

Update the imports in context.ts

context.ts
import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';

Use fetchRequestHandler with Oak in app.ts

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

Update the imports in router.ts

router.ts
import { initTRPC } from 'npm:@trpc/server';
import { z } from 'npm:zod';
import { Context } from './context.ts';

Update the imports in context.ts

context.ts
import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';

Create Deno Deploy Function

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!

EndpointHTTP URI
getUserGET http://localhost:8000/trpc/getUserById?input=INPUT

where INPUT is a URI-encoded JSON string.
createUserPOST http://localhost:8000/trpc/createUser

with req.body of type User

Next.js Edge Runtime

See a full example here.

Remix

app/routes/trpc.$trpc.ts
import type { 
type ActionFunctionArgs = {
    request: Request;
}
ActionFunctionArgs
,
type LoaderFunctionArgs = {
    request: Request;
}
LoaderFunctionArgs
} from '@remix-run/node';
import { function fetchRequestHandler<TRouter extends AnyRouter>(opts: FetchHandlerRequestOptions<TRouter>): Promise<Response>fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { function createContext(opts: FetchCreateContextFnOptions): anycreateContext } from '~/server/context'; import { const appRouter: AnyRouterappRouter } from '~/server/router'; export const const loader: (args: LoaderFunctionArgs) => Promise<Response>loader = async (args: LoaderFunctionArgsargs:
type LoaderFunctionArgs = {
    request: Request;
}
LoaderFunctionArgs
) => {
return function handleRequest(args: LoaderFunctionArgs | ActionFunctionArgs): Promise<Response>handleRequest(args: LoaderFunctionArgsargs); }; export const const action: (args: ActionFunctionArgs) => Promise<Response>action = async (args: ActionFunctionArgsargs:
type ActionFunctionArgs = {
    request: Request;
}
ActionFunctionArgs
) => {
return function handleRequest(args: LoaderFunctionArgs | ActionFunctionArgs): Promise<Response>handleRequest(args: ActionFunctionArgsargs); }; function function handleRequest(args: LoaderFunctionArgs | ActionFunctionArgs): Promise<Response>handleRequest(args: ActionFunctionArgs | LoaderFunctionArgsargs:
type LoaderFunctionArgs = {
    request: Request;
}
LoaderFunctionArgs
|
type ActionFunctionArgs = {
    request: Request;
}
ActionFunctionArgs
) {
return fetchRequestHandler<AnyRouter>(opts: FetchHandlerRequestOptions<AnyRouter>): Promise<Response>fetchRequestHandler({ endpoint: stringendpoint: '/trpc', req: Requestreq: args: ActionFunctionArgs | LoaderFunctionArgsargs.request: Requestrequest, BaseHandlerOptions<AnyRouter, Request>.router: AnyRouterrouter: const appRouter: AnyRouterappRouter, createContext?: FetchCreateContextFn<AnyRouter> | undefinedcreateContext, }); }

SolidStart

src/routes/api/trpc/[trpc].ts
import { function fetchRequestHandler<TRouter extends AnyRouter>(opts: FetchHandlerRequestOptions<TRouter>): Promise<Response>fetchRequestHandler } from '@trpc/server/adapters/fetch';
import type { 
type APIEvent = {
    request: Request;
}
APIEvent
} from '@solidjs/start/server';
import {
function createContext(opts: FetchCreateContextFnOptions): {
    user: {
        name: string;
    };
}
createContext
} from '../../server/context';
import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
} from '../../server/router';
const const handler: (event: APIEvent) => Promise<Response>handler = (event: APIEventevent:
type APIEvent = {
    request: Request;
}
APIEvent
) =>
fetchRequestHandler<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: FetchHandlerRequestOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>): Promise<Response>
fetchRequestHandler
({
endpoint: stringendpoint: '/api/trpc', req: Requestreq: event: APIEventevent.request: Requestrequest,
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, Request>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
,
createContext?: FetchCreateContextFn<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | undefined
createContext
,
}); export { const handler: (event: APIEvent) => Promise<Response>handler as
const GET: (event: APIEvent) => Promise<Response>
export GET
GET
, const handler: (event: APIEvent) => Promise<Response>handler as
const POST: (event: APIEvent) => Promise<Response>
export POST
POST
};

Vercel Edge Runtime

Install dependencies

npm install -g edge-runtime
yarn global add edge-runtime
pnpm add -g edge-runtime
bun add -g edge-runtime

Create Edge Runtime Function

server.ts
import { function fetchRequestHandler<TRouter extends AnyRouter>(opts: FetchHandlerRequestOptions<TRouter>): Promise<Response>fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { 
function createContext(opts: FetchCreateContextFnOptions): {
    user: {
        name: string;
    };
}
createContext
} from './context';
import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
} from './router';
// Vercel Edge Runtime uses Service Worker-style addEventListener function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void (+1 overload)addEventListener('fetch', (event: anyevent: any) => { return event: anyevent.respondWith(
fetchRequestHandler<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: FetchHandlerRequestOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>): Promise<Response>
fetchRequestHandler
({
endpoint: stringendpoint: '/trpc', req: Requestreq: event: anyevent.request,
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, Request>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
,
createContext?: FetchCreateContextFn<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | undefined
createContext
,
}), ); });

Run edge-runtime --listen server.ts --port 3000 and your endpoints will be available via HTTP!

EndpointHTTP URI
getUserGET http://localhost:3000/trpc/getUserById?input=INPUT

where INPUT is a URI-encoded JSON string.
createUserPOST http://localhost:3000/trpc/createUser

with req.body of type User