본문으로 건너뛰기
버전: 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는 네이티브 RequestResponse 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입니다