Fetch / Edge Runtimes アダプター
WinterCG、特にMinimum Common Web Platform API仕様に従う、あらゆるエッジランタイム内でtRPCサーバーを作成できます。
これらのランタイムには以下が含まれますが、これらに限定されません。
- Cloudflare Workers
- Deno Deploy
- Vercel Edge Runtime (& Next.js Edge Runtime)
これは、以下のようなリクエストとレスポンスを表現するためにWebプラットフォームAPIを使用するフレームワークへの統合も容易にします。
- Astro (SSRモード)
- Remix
- SolidStart
サンプルアプリ
| 説明 | リンク |
|---|---|
| Cloudflare Workersの例 | ソース |
| Deno Deployの例 | ソース |
| Next.js Edge Runtimeの例 | ソース |
| Vercel Edge Runtimeの例 | ソース |
エッジランタイムでtRPCサーバーを使用する方法
tRPCは、ネイティブのRequestおよびResponseAPIを入力と出力として使用するfetchアダプターを提供します。 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
Cloudflare Workersを実行するには、Wrangler CLIが必要です。
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 req.bodyの型はUser |
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でOakとともにfetchRequestHandlerを使用
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 req.bodyの型はUser |
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
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 req.bodyの型はUser |