メインコンテンツにスキップ
バージョン: 11.x

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, Response
  • 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

Cloudflare Workersを実行するには、Wrangler CLIが必要です。

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

req.bodyの型はUser

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でOakとともにfetchRequestHandlerを使用

app.ts
ts
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.ts
ts
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.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

req.bodyの型はUser

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

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

req.bodyの型はUser