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
、Response
fetch
Headers
URL
如果您的运行时支持这些 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.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((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.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((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.tsts
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.tsts
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].tsts
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].tsts
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.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,});},};
运行 wrangler dev server.ts
,您的端点将通过 HTTP 可用!
端点 | HTTP URI |
---|---|
getUser | GET http://localhost:8787/trpc/getUserById?input=INPUT 其中 INPUT 是一个 URI 编码的 JSON 字符串。 |
createUser | POST http://localhost:8787/trpc/createUser 使用类型为 User 的 req.body |
Deno Oak
这假设您已安装并设置了 Deno。有关更多信息,请参阅他们的 入门指南。
更新 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';
更新 context.ts
中的导入
context.tsts
import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
context.tsts
import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
在 app.ts
中使用 fetchRequestHandler
与 Oak
app.tsts
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.tsts
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.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';
更新 context.ts
中的导入
context.tsts
import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
context.tsts
import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
创建 Deno Deploy 函数
server.tsts
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.tsts
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 |
---|---|
getUser | GET http://localhost:8000/trpc/getUserById?input=INPUT 其中 INPUT 是一个 URI 编码的 JSON 字符串。 |
createUser | POST http://localhost:8000/trpc/createUser 使用类型为 User 的 req.body |
Next.js Edge Runtime
查看完整的示例 此处。
Remix
app/routes/trpc/$trpc.tsts
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.tsts
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].tsts
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].tsts
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 应用程序,了解一个可行的示例。
安装依赖项
- npm
- yarn
- pnpm
- bun
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
sh
bun add -g edge-runtime
sh
bun add -g edge-runtime
创建边缘运行时函数
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,}),);});
运行 edge-runtime --listen server.ts --port 3000
,您的端点将通过 HTTP 可用!
端点 | HTTP URI |
---|---|
getUser | GET http://localhost:3000/trpc/getUserById?input=INPUT 其中 INPUT 是一个 URI 编码的 JSON 字符串。 |
createUser | POST http://localhost:3000/trpc/createUser 使用类型为 User 的 req.body |