跳至主要内容
版本:11.x

Fetch / 边缘运行时适配器

您可以在任何遵循 WinterCG 的边缘运行时中创建 tRPC 服务器,特别是 最小通用 Web 平台 API 规范。

其中一些运行时包括但不限于

  • Cloudflare Workers
  • Deno Deploy
  • Vercel Edge Runtime(和 Next.js Edge Runtime)

这也使得将 tRPC 轻松集成到使用 Web 平台 API 来表示请求和响应的框架中,例如

  • Astro(SSR 模式)
  • Remix
  • SolidStart

示例应用程序

描述链接
Cloudflare Workers 示例来源
Deno Deploy 示例来源
Next.js Edge Runtime 示例来源
Vercel Edge Runtime 示例来源

如何在边缘运行时使用 tRPC 服务器

tRPC 提供了一个 fetch 适配器,它使用本机 RequestResponse API 作为输入和输出。tRPC 特定代码在所有运行时中都是相同的,唯一的区别在于响应的返回方式。

tRPC 包含一个用于本机 Fetch API 的适配器。此适配器允许您将 tRPC 路由器转换为 Request 处理程序,该处理程序返回 Response 对象。

所需的 Web API

tRPC 服务器使用以下 Fetch API

  • RequestResponse
  • fetch
  • Headers
  • URL

如果您的运行时支持这些 API,您可以 使用 tRPC 服务器

提示

有趣的事实:这也意味着您可以在浏览器中使用 tRPC 服务器!

常见设置

安装依赖项

提示

如果您使用 Deno Deploy,则可以跳过此步骤。

npm install @trpc/server@next @trpc/client@next zod

Zod 不是必需的依赖项,但它在下面的示例路由器中使用。

创建路由器

首先,您需要一个 路由器 来处理您的查询、变异和订阅。

下面给出了一个示例路由器,将其保存在名为 router.ts 的文件中。

router.ts
router.ts
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;
router.ts
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;

如果您的路由器文件开始变得太大,请将您的路由器拆分为多个子路由器,每个子路由器都在其自己的文件中实现。然后 将它们 合并到单个根 appRouter 中。

创建上下文

然后,您需要一个 上下文,它将在每个请求中创建。

下面给出了一个示例上下文,将其保存在名为 context.ts 的文件中

context.ts
context.ts
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>>;
context.ts
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>>;

运行时特定设置

Astro

src/pages/trpc/[trpc].ts
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,
});
};
src/pages/trpc/[trpc].ts
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

注意

您需要 Wrangler CLI 来运行 Cloudflare Workers。

创建 Cloudflare Worker

server.ts
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,
});
},
};
server.ts
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,
});
},
};

运行 wrangler dev server.ts,您的端点将通过 HTTP 可用!

端点HTTP URI
getUserGET http://localhost:8787/trpc/getUserById?input=INPUT

其中 INPUT 是一个 URI 编码的 JSON 字符串。
createUserPOST http://localhost:8787/trpc/createUser

使用类型为 Userreq.body

Deno Oak

注意

这假设您已安装并设置了 Deno。有关更多信息,请参阅他们的 入门指南

更新 router.ts 中的导入

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

更新 context.ts 中的导入

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

app.ts 中使用 fetchRequestHandler 与 Oak

app.ts
ts
import { Application, Router } from 'https://land.deno.org.cn/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 });
app.ts
ts
import { Application, Router } from 'https://land.deno.org.cn/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

注意

这假设您已安装并设置了 Deno。有关更多信息,请参阅他们的 入门指南

提示

查看我们的示例 Deno Deploy 应用程序,了解一个可行的示例。

更新 router.ts 中的导入

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

更新 context.ts 中的导入

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

创建 Deno Deploy 函数

server.ts
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);
server.ts
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);

运行 deno run --allow-net=:8000 --allow-env ./server.ts,您的端点将通过 HTTP 可用!

端点HTTP URI
getUserGET http://localhost:8000/trpc/getUserById?input=INPUT

其中 INPUT 是一个 URI 编码的 JSON 字符串。
createUserPOST http://localhost:8000/trpc/createUser

使用类型为 Userreq.body

Next.js Edge Runtime

查看完整的示例 此处

Remix

app/routes/trpc/$trpc.ts
ts
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,
});
}
app/routes/trpc/$trpc.ts
ts
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,
});
}

SolidStart

src/routes/api/trpc/[trpc].ts
ts
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 };
src/routes/api/trpc/[trpc].ts
ts
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 };

Vercel Edge Runtime

注意

有关更多信息,请参阅官方 Vercel Edge Runtime 文档

提示

查看我们的示例 Vercel Edge Runtime 应用程序,了解一个可行的示例。

安装依赖项

sh
npm install -g edge-runtime
sh
npm install -g edge-runtime

创建边缘运行时函数

server.ts
ts
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.ts
ts
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,
}),
);
});

运行 edge-runtime --listen server.ts --port 3000,您的端点将通过 HTTP 可用!

端点HTTP URI
getUserGET http://localhost:3000/trpc/getUserById?input=INPUT

其中 INPUT 是一个 URI 编码的 JSON 字符串。
createUserPOST http://localhost:3000/trpc/createUser

使用类型为 Userreq.body