본문으로 건너뛰기
버전: 11.x

Fetch / Edge Runtimes 어댑터

WinterCG, 특히 Minimum Common Web Platform API 사양을 따르는 모든 엣지 런타임에서 tRPC 서버를 생성할 수 있습니다.

이러한 런타임에는 다음이 포함되지만 이에 국한되지 않습니다:

  • Cloudflare Workers
  • Deno Deploy
  • Vercel Edge Runtime (& Next.js Edge Runtime)

이로 인해 요청과 응답을 웹 플랫폼 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 객체를 반환할 수 있습니다.

필수 웹 API​

tRPC 서버는 다음 Fetch API를 사용합니다:

  • Request, Response
  • fetch
  • Headers
  • URL

런타임이 이러한 API를 지원한다면 tRPC 서버를 사용할 수 있습니다.

팁

참고: 이는 브라우저에서도 tRPC 서버를 사용할 수 있음을 의미합니다.

공통 설정​

의존성 설치​

팁

Deno Deploy를 사용하는 경우 이 단계를 건너뛸 수 있습니다.

npm install @trpc/server @trpc/client zod

Zod은 필수 의존성이 아니지만, 아래 샘플 라우터에서 사용됩니다.

AI 에이전트

AI 코딩 에이전트를 사용하는 경우, 더 나은 코드 생성을 위해 tRPC 스킬을 설치하세요:

bash
npx @tanstack/intent@latest install
bash
npx @tanstack/intent@latest install

라우터 생성​

먼저 쿼리, 뮤테이션, 구독을 처리하기 위한 라우터가 필요합니다.

아래에 샘플 라우터가 제공됩니다. router.ts이라는 이름의 파일로 저장하세요.

router.ts
router.ts
ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
import type { 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 type { 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 type { 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 type { 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의 import 업데이트​

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의 import 업데이트​

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의 import 업데이트​

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의 import 업데이트​

context.ts
ts
import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
context.ts
ts
import { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';

Deno Deploy Function 생성​

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 { ActionFunctionArgs, LoaderFunctionArgs } 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: LoaderFunctionArgs) => {
return handleRequest(args);
};
export const action = async (args: ActionFunctionArgs) => {
return handleRequest(args);
};
function handleRequest(args: LoaderFunctionArgs | ActionFunctionArgs) {
return fetchRequestHandler({
endpoint: '/trpc',
req: args.request,
router: appRouter,
createContext,
});
}
app/routes/trpc.$trpc.ts
ts
import type { ActionFunctionArgs, LoaderFunctionArgs } 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: LoaderFunctionArgs) => {
return handleRequest(args);
};
export const action = async (args: ActionFunctionArgs) => {
return handleRequest(args);
};
function handleRequest(args: LoaderFunctionArgs | ActionFunctionArgs) {
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 '@solidjs/start/server';
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 '@solidjs/start/server';
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';
 
// Vercel Edge Runtime uses Service Worker-style addEventListener
addEventListener('fetch', (event: any) => {
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';
 
// Vercel Edge Runtime uses Service Worker-style addEventListener
addEventListener('fetch', (event: any) => {
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입니다