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 适配器,它使用本机 Request 和 Response API 作为输入和输出。tRPC 特定代码在所有运行时中都是相同的,唯一的区别在于响应的返回方式。
tRPC 包含一个用于本机 Fetch API 的适配器。此适配器允许您将 tRPC 路由器转换为 Request 处理程序,该处理程序返回 Response 对象。
所需的 Web API
tRPC 服务器使用以下 Fetch API
Request、ResponsefetchHeadersURL
如果您的运行时支持这些 API,您可以 使用 tRPC 服务器。
有趣的事实:这也意味着您可以在浏览器中使用 tRPC 服务器!
常见设置
安装依赖项
如果您使用 Deno Deploy,则可以跳过此步骤。
- npm
- yarn
- pnpm
- bun
npm install @trpc/server@next @trpc/client@next zod
yarn add @trpc/server@next @trpc/client@next zod
pnpm add @trpc/server@next @trpc/client@next zod
bun add @trpc/server@next @trpc/client@next zod
Zod 不是必需的依赖项,但它在下面的示例路由器中使用。
创建路由器
首先,您需要一个 路由器 来处理您的查询、变异和订阅。
下面给出了一个示例路由器,将其保存在名为 router.ts 的文件中。
router.ts
router.tstsimport { 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 APIexport type AppRouter = typeof appRouter;
router.tstsimport { 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 APIexport type AppRouter = typeof appRouter;
如果您的路由器文件开始变得太大,请将您的路由器拆分为多个子路由器,每个子路由器都在其自己的文件中实现。然后 将它们 合并到单个根 appRouter 中。
创建上下文
然后,您需要一个 上下文,它将在每个请求中创建。
下面给出了一个示例上下文,将其保存在名为 context.ts 的文件中
context.ts
context.tstsimport { 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.tstsimport { 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].tstsimport { 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].tstsimport { 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.tstsimport { 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.tstsimport { 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 |
|---|---|
getUser | GET https://:8787/trpc/getUserById?input=INPUT 其中 INPUT 是一个 URI 编码的 JSON 字符串。 |
createUser | POST https://:8787/trpc/createUser 使用类型为 User 的 req.body |
Deno Oak
这假设您已安装并设置了 Deno。有关更多信息,请参阅他们的 入门指南。
更新 router.ts 中的导入
router.tstsimport { initTRPC } from 'npm:@trpc/server';import { z } from 'npm:zod';import { Context } from './context.ts';
router.tstsimport { initTRPC } from 'npm:@trpc/server';import { z } from 'npm:zod';import { Context } from './context.ts';
更新 context.ts 中的导入
context.tstsimport { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
context.tstsimport { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
在 app.ts 中使用 fetchRequestHandler 与 Oak
app.tstsimport { 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 });
app.tstsimport { 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
这假设您已安装并设置了 Deno。有关更多信息,请参阅他们的 入门指南。
查看我们的示例 Deno Deploy 应用程序,了解一个可行的示例。
更新 router.ts 中的导入
router.tstsimport { initTRPC } from 'npm:@trpc/server';import { z } from 'npm:zod';import { Context } from './context.ts';
router.tstsimport { initTRPC } from 'npm:@trpc/server';import { z } from 'npm:zod';import { Context } from './context.ts';
更新 context.ts 中的导入
context.tstsimport { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
context.tstsimport { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
创建 Deno Deploy 函数
server.tstsimport { 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.tstsimport { 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 |
|---|---|
getUser | GET https://:8000/trpc/getUserById?input=INPUT 其中 INPUT 是一个 URI 编码的 JSON 字符串。 |
createUser | POST https://:8000/trpc/createUser 使用类型为 User 的 req.body |
Next.js Edge Runtime
查看完整的示例 此处。
Remix
app/routes/trpc/$trpc.tstsimport 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.tstsimport 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].tstsimport { 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].tstsimport { 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 应用程序,了解一个可行的示例。
安装依赖项
- npm
- yarn
- pnpm
- bun
shnpm install -g edge-runtime
shnpm install -g edge-runtime
shyarn global add edge-runtime
shyarn global add edge-runtime
shpnpm add -g edge-runtime
shpnpm add -g edge-runtime
shbun add -g edge-runtime
shbun add -g edge-runtime
创建边缘运行时函数
server.tstsimport { 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.tstsimport { 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 |
|---|---|
getUser | GET https://:3000/trpc/getUserById?input=INPUT 其中 INPUT 是一个 URI 编码的 JSON 字符串。 |
createUser | POST https://:3000/trpc/createUser 使用类型为 User 的 req.body |