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,ResponsefetchHeadersURL
런타임이 이러한 API를 지원한다면 tRPC 서버를 사용할 수 있습니다.
참고: 이는 브라우저에서도 tRPC 서버를 사용할 수 있음을 의미합니다.
공통 설정
의존성 설치
Deno Deploy를 사용하는 경우 이 단계를 건너뛸 수 있습니다.
- npm
- yarn
- pnpm
- bun
- deno
npm install @trpc/server @trpc/client zod
yarn add @trpc/server @trpc/client zod
pnpm add @trpc/server @trpc/client zod
bun add @trpc/server @trpc/client zod
deno add npm:@trpc/server npm:@trpc/client npm:zod
Zod은 필수 의존성이 아니지만, 아래 샘플 라우터에서 사용됩니다.
AI 코딩 에이전트를 사용하는 경우, 더 나은 코드 생성을 위해 tRPC 스킬을 설치하세요:
bashnpx @tanstack/intent@latest install
bashnpx @tanstack/intent@latest install
라우터 생성
먼저 쿼리, 뮤테이션, 구독을 처리하기 위한 라우터가 필요합니다.
아래에 샘플 라우터가 제공됩니다. router.ts이라는 이름의 파일로 저장하세요.
router.ts
router.tstsimport {initTRPC } from '@trpc/server';import {z } from 'zod';import type {Context } from './context';typeUser = {id : string;name : string;bio ?: string;};constusers :Record <string,User > = {};export constt =initTRPC .context <Context >().create ();export constappRouter =t .router ({getUserById :t .procedure .input (z .string ()).query ((opts ) => {returnusers [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 ) => {constid =Date .now ().toString ();constuser :User = {id , ...opts .input };users [user .id ] =user ;returnuser ;}),});// export type definition of APIexport typeAppRouter = typeofappRouter ;
router.tstsimport {initTRPC } from '@trpc/server';import {z } from 'zod';import type {Context } from './context';typeUser = {id : string;name : string;bio ?: string;};constusers :Record <string,User > = {};export constt =initTRPC .context <Context >().create ();export constappRouter =t .router ({getUserById :t .procedure .input (z .string ()).query ((opts ) => {returnusers [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 ) => {constid =Date .now ().toString ();constuser :User = {id , ...opts .input };users [user .id ] =user ;returnuser ;}),});// export type definition of APIexport typeAppRouter = typeofappRouter ;
라우터 파일이 너무 커지면, 각 서브라우터를 별도의 파일에 구현하여 여러 개의 서브라우터로 분리하세요. 그런 다음 병합하여 단일 루트 appRouter로 통합하세요.
컨텍스트 생성
그런 다음 각 요청마다 생성될 컨텍스트가 필요합니다.
아래에 샘플 컨텍스트가 제공됩니다. context.ts이라는 이름의 파일로 저장하세요:
context.ts
context.tstsimport type {FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';export functioncreateContext ({req ,resHeaders ,}:FetchCreateContextFnOptions ) {constuser = {name :req .headers .get ('username') ?? 'anonymous' };return {req ,resHeaders ,user };}export typeContext =Awaited <ReturnType <typeofcreateContext >>;
context.tstsimport type {FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';export functioncreateContext ({req ,resHeaders ,}:FetchCreateContextFnOptions ) {constuser = {name :req .headers .get ('username') ?? 'anonymous' };return {req ,resHeaders ,user };}export typeContext =Awaited <ReturnType <typeofcreateContext >>;
런타임별 설정
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 constALL :APIRoute = (opts ) => {returnfetchRequestHandler ({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 constALL :APIRoute = (opts ) => {returnfetchRequestHandler ({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 {asyncfetch (request :Request ):Promise <Response > {returnfetchRequestHandler ({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 {asyncfetch (request :Request ):Promise <Response > {returnfetchRequestHandler ({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의 import 업데이트
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의 import 업데이트
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의 import 업데이트
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의 import 업데이트
context.tstsimport { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
context.tstsimport { FetchCreateContextFnOptions } from 'npm:@trpc/server/adapters/fetch';
Deno Deploy Function 생성
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 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.tstsimport 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 constloader = async (args :LoaderFunctionArgs ) => {returnhandleRequest (args );};export constaction = async (args :ActionFunctionArgs ) => {returnhandleRequest (args );};functionhandleRequest (args :LoaderFunctionArgs |ActionFunctionArgs ) {returnfetchRequestHandler ({endpoint : '/trpc',req :args .request ,router :appRouter ,createContext ,});}
app/routes/trpc.$trpc.tstsimport 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 constloader = async (args :LoaderFunctionArgs ) => {returnhandleRequest (args );};export constaction = async (args :ActionFunctionArgs ) => {returnhandleRequest (args );};functionhandleRequest (args :LoaderFunctionArgs |ActionFunctionArgs ) {returnfetchRequestHandler ({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 '@solidjs/start/server';import {createContext } from '../../server/context';import {appRouter } from '../../server/router';consthandler = (event :APIEvent ) =>fetchRequestHandler ({endpoint : '/api/trpc',req :event .request ,router :appRouter ,createContext ,});export {handler asGET ,handler asPOST };
src/routes/api/trpc/[trpc].tstsimport {fetchRequestHandler } from '@trpc/server/adapters/fetch';import type {APIEvent } from '@solidjs/start/server';import {createContext } from '../../server/context';import {appRouter } from '../../server/router';consthandler = (event :APIEvent ) =>fetchRequestHandler ({endpoint : '/api/trpc',req :event .request ,router :appRouter ,createContext ,});export {handler asGET ,handler asPOST };
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';// Vercel Edge Runtime uses Service Worker-style addEventListeneraddEventListener ('fetch', (event : any) => {returnevent .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';// Vercel Edge Runtime uses Service Worker-style addEventListeneraddEventListener ('fetch', (event : any) => {returnevent .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입니다 |