--- id: backend-api-gateway-bff title: API Gateway / BFF — 라우팅 / aggregation category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [backend, api-gateway, bff, vibe-coding] tech_stack: { language: "TS / Kong / Envoy", applicable_to: ["Backend"] } applied_in: [] aliases: [API gateway, BFF, backend for frontend, Kong, Envoy, Apollo Federation] --- # API Gateway / BFF > **Gateway = 횡단 관심사 (auth / rate / logging) 묶기**. **BFF = 프론트별 (web / iOS / android) 응답 최적화**. 마이크로서비스 + 다양한 클라. ## 📖 핵심 개념 - API Gateway: 단일 진입점 — Kong / Envoy / Cloudflare / API Gateway (AWS). - BFF: Backend for Frontend — 클라별 다른 backend. - Aggregation: 여러 service 호출 합쳐서 응답. - Transformation: legacy ↔ modern format. ## 💻 코드 패턴 ### Gateway 책임 ``` Client ──▶ Gateway ──▶ Service A └──▶ Service B └──▶ Service C Gateway: - TLS termination - Auth (JWT 검증) - Rate limiting (per IP / per user) - Logging / tracing - CORS - Routing / load balance - Caching (GET) ``` ### Kong (DB-less) ```yaml # kong.yml _format_version: "3.0" services: - name: users url: http://users-svc:3000 routes: - name: users-route paths: [/api/users] plugins: - name: jwt - name: rate-limiting config: { minute: 100, policy: local } ``` ### Cloudflare Workers (가벼운 gateway) ```ts export default { async fetch(req: Request, env: Env): Promise { const url = new URL(req.url); // Auth const token = req.headers.get('authorization')?.replace('Bearer ', ''); const userId = await verifyJwt(token, env.JWT_SECRET); if (!userId && !isPublic(url.pathname)) return new Response('unauthorized', { status: 401 }); // Rate limit const ok = await env.RATE.limit({ key: userId ?? req.headers.get('cf-connecting-ip')! }); if (!ok.success) return new Response('rate limited', { status: 429 }); // Routing if (url.pathname.startsWith('/api/users')) { return fetch(`http://users-svc${url.pathname}`, { headers: { 'x-user-id': userId } }); } if (url.pathname.startsWith('/api/orders')) { return fetch(`http://orders-svc${url.pathname}`, { headers: { 'x-user-id': userId } }); } return new Response('not found', { status: 404 }); }, }; ``` ### BFF (Web-specific) ```ts // bff-web/routes/dashboard.ts app.get('/api/dashboard', async (req, res) => { // 동시에 여러 service 호출 const [user, orders, recommendations] = await Promise.all([ fetch(`${USERS}/users/${req.userId}`).then(r => r.json()), fetch(`${ORDERS}/orders?userId=${req.userId}&limit=10`).then(r => r.json()), fetch(`${RECS}/for/${req.userId}`).then(r => r.json()), ]); // 프론트가 필요한 형태로 합치기 res.json({ user: { id: user.id, name: user.name, avatar: user.avatar }, recentOrders: orders.map(o => ({ id: o.id, status: o.status, total: o.total })), recommendations: recommendations.slice(0, 5).map(r => ({ id: r.id, name: r.name })), }); }); ``` → Web 만 사용. iOS BFF 가 다른 페이로드 반환. ### Pattern: Backend for X ``` bff-web/ → 큰 페이로드, 많은 데이터 bff-mobile/ → 작은 페이로드, 압축 bff-admin/ → 추가 관리 endpoint ``` ### Aggregation + Cache ```ts const cache = new Map(); async function withCache(key: string, ttl: number, fn: () => Promise): Promise { const c = cache.get(key); if (c && c.expires > Date.now()) return c.data as T; const data = await fn(); cache.set(key, { data, expires: Date.now() + ttl }); return data; } const recs = await withCache(`recs:${userId}`, 60_000, () => fetch(...)); ``` ### Header propagation (tracing) ```ts const traceHeaders = ['traceparent', 'tracestate', 'x-request-id', 'x-user-id']; async function forward(req: Request, target: string) { const h = new Headers(); for (const k of traceHeaders) { const v = req.headers.get(k); if (v) h.set(k, v); } return fetch(target, { headers: h }); } ``` ### Apollo Federation (GraphQL gateway) ```ts // 여러 GraphQL service 를 하나의 schema 로 import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway'; import { ApolloServer } from '@apollo/server'; const gateway = new ApolloGateway({ supergraphSdl: new IntrospectAndCompose({ subgraphs: [ { name: 'users', url: 'http://users:4001' }, { name: 'orders', url: 'http://orders:4002' }, ], }), }); const server = new ApolloServer({ gateway }); ``` ### tRPC (TypeSafe RPC) ```ts // router (server) const appRouter = router({ user: userRouter, order: orderRouter, }); export type AppRouter = typeof appRouter; // client import type { AppRouter } from 'server/router'; const trpc = createTRPCClient({ url: '/api/trpc' }); const u = await trpc.user.get.query({ id: '1' }); // typed end-to-end ``` → BFF 와 자연 결합. ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 단일 service | Gateway 불필요 | | 마이크로서비스 + 단일 클라 | Gateway 만 | | 다양한 클라 (web/iOS/AOS) | Gateway + BFF per-client | | Edge / 글로벌 | Cloudflare Workers / Vercel Edge | | GraphQL 통합 | Apollo Federation | | TypeScript 풀스택 | tRPC (BFF 비슷) | ## ❌ 안티패턴 - **Gateway 가 비즈니스 로직**: gateway = thin. 로직은 service. - **BFF 가 모든 클라 공유**: 의미 없음. per-client 또는 그냥 service. - **Aggregation 안 cache**: 매번 다중 호출. 5분 cache 만이라도. - **Header propagation 누락**: tracing 깨짐. - **Auth 가 service 마다**: gateway 한 번 + service 는 신뢰. - **Service 간 Sync 호출 chain**: latency 합산. 병렬 / async. - **Gateway = single point of failure 무 HA**: 다중 노드 + LB. ## 🤖 LLM 활용 힌트 - Gateway = 횡단 (auth/rate/log) + routing + LB. - BFF = 클라별 응답 형태. - Tracing 헤더 항상 propagate. ## 🔗 관련 문서 - [[Backend_Rate_Limiting]] - [[Backend_Health_Check_Patterns]] - [[DevOps_Observability_Stack]]