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
およびResponse
APIを入力と出力として使用するfetchアダプターを提供します。 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
Cloudflare Workersを実行するには、Wrangler CLIが必要です。
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 req.body の型はUser |
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
でOakとともにfetchRequestHandler
を使用
app.tsts
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 });
app.tsts
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
これは、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 req.body の型はUser |
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
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 req.body の型はUser |