GraphQL vs tRPC vs REST: 2026년 API 레이어 선택 가이드

프론트엔드

GraphQLtRPCREST APIAPI 설계풀스택

이 글은 누구를 위한 것인가

  • 새 프로젝트의 API 레이어를 설계해야 하는 풀스택 개발자
  • GraphQL을 도입했는데 복잡성이 예상보다 높아 고민하는 팀
  • tRPC가 무엇인지, 언제 적합한지 이해하고 싶은 엔지니어

들어가며

2020년대 초반 "GraphQL이 REST를 대체한다"는 예측이 많았다. 2026년 현재 결과는? 공존이다. GraphQL은 특정 사용 사례에서 빛나고, REST는 여전히 대부분의 경우에 실용적이다. 그리고 tRPC라는 새로운 선택지가 TypeScript 풀스택 팀에서 빠르게 성장했다.

"가장 좋은 API 스타일"은 없다. 팀 구조, 클라이언트 다양성, 타입 안전성 요구에 따라 최선이 다르다.

이 글은 bluefoxdev.kr의 API 설계 아키텍처 가이드 를 참고하고, 2026년 실무 선택 관점에서 확장하여 작성했습니다. API 설계 전략은 bluebutton.kr 에서도 다루고 있습니다.


1. 세 방식의 포지셔닝

[API 방식 선택 맵]

                높은 타입 안전성
                      ↑
                    tRPC
                (TS 풀스택 전용)
                      │
외부 클라이언트 ────────┼──────── 내부 클라이언트만
    REST                    │
(범용, 표준)           GraphQL
                  (복잡한 관계, 멀티 클라이언트)
                      ↓
                낮은 타입 안전성

시장 현황 (2026):
- REST: 여전히 주류 (~60% 신규 API)
- GraphQL: 대형 플랫폼/BFF에서 강세 (~25%)
- tRPC: TypeScript 생태계에서 빠르게 성장 (~15%)

2. REST API

2.1 특성과 강점

REST의 강점:
✅ 모든 언어/플랫폼에서 소비 가능
✅ HTTP 캐싱 자연스럽게 활용
✅ 명확한 표준 (HTTP 메서드, 상태 코드)
✅ 운영/디버깅 도구 풍부 (curl, Postman 등)
✅ 외부 파트너에게 노출하는 공개 API에 적합

REST의 약점:
❌ 클라이언트마다 필요한 필드가 달라도 같은 응답
❌ 관계 데이터 조회 시 여러 번 요청 (N+1)
❌ 타입 안전성은 별도 도구(OpenAPI) 필요
// Next.js REST API 예시
// app/api/users/[id]/route.ts
import { NextResponse } from 'next/server';

export async function GET(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  
  const user = await db.user.findUnique({
    where: { id },
    select: { id: true, name: true, email: true },
  });
  
  if (!user) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }
  
  return NextResponse.json(user);
}

export async function PATCH(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const body = await request.json();
  
  const updated = await db.user.update({
    where: { id },
    data: body,
  });
  
  return NextResponse.json(updated);
}

3. GraphQL

3.1 언제 빛나는가

GraphQL이 적합한 경우:
✅ 하나의 API를 여러 클라이언트(웹, iOS, Android)가 소비
✅ 클라이언트마다 필요한 필드가 크게 다름
✅ 복잡하게 연결된 데이터 (그래프 구조)
✅ BFF(Backend for Frontend) 패턴 구현
✅ 실시간 구독(Subscription) 필요
// GraphQL 스키마 (SDL)
const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    orders: [Order!]!
    totalSpent: Float!
  }
  
  type Order {
    id: ID!
    items: [OrderItem!]!
    total: Float!
    createdAt: String!
  }
  
  type Query {
    user(id: ID!): User
    me: User
  }
  
  type Mutation {
    updateProfile(name: String, email: String): User
  }
  
  type Subscription {
    orderStatusChanged(orderId: ID!): Order
  }
`;

// Resolver
const resolvers = {
  Query: {
    user: (_: unknown, { id }: { id: string }) =>
      db.user.findUnique({ where: { id } }),
  },
  User: {
    // N+1 해결: DataLoader 사용
    orders: (user: User, _: unknown, { loaders }: Context) =>
      loaders.ordersByUserId.load(user.id),
    totalSpent: async (user: User) => {
      const result = await db.order.aggregate({
        where: { userId: user.id },
        _sum: { total: true },
      });
      return result._sum.total ?? 0;
    },
  },
};
// 클라이언트: 필요한 필드만 요청
const GET_USER_SUMMARY = gql`
  query GetUserSummary($id: ID!) {
    user(id: $id) {
      name
      totalSpent
      orders {
        id
        total
        createdAt
      }
    }
  }
`;

// 웹은 전체 정보, 모바일은 요약만
const GET_USER_MOBILE = gql`
  query GetUserMobile($id: ID!) {
    user(id: $id) {
      name
      totalSpent  # 모바일에서는 이것만 필요
    }
  }
`;

4. tRPC

4.1 특성: TypeScript 엔드투엔드 타입 안전성

// 서버: 프로시저 정의
// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.create();

export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(authMiddleware);

export const appRouter = router({
  user: router({
    getById: publicProcedure
      .input(z.object({ id: z.string() }))
      .query(async ({ input }) => {
        return db.user.findUnique({ where: { id: input.id } });
      }),
    
    create: protectedProcedure
      .input(z.object({
        name: z.string().min(2),
        email: z.string().email(),
      }))
      .mutation(async ({ input, ctx }) => {
        return db.user.create({ data: input });
      }),
    
    list: publicProcedure
      .input(z.object({
        page: z.number().min(1).default(1),
        limit: z.number().min(1).max(100).default(20),
      }))
      .query(async ({ input }) => {
        const { page, limit } = input;
        const [users, total] = await Promise.all([
          db.user.findMany({ skip: (page - 1) * limit, take: limit }),
          db.user.count(),
        ]);
        return { users, total, pages: Math.ceil(total / limit) };
      }),
  }),
});

export type AppRouter = typeof appRouter;
// 클라이언트: 서버 타입을 자동으로 사용
// app/users/page.tsx
import { trpc } from '@/lib/trpc';

function UsersPage() {
  // 완전한 타입 추론! input도, output도 타입 안전
  const { data, isLoading } = trpc.user.list.useQuery({ page: 1, limit: 20 });
  
  const createUser = trpc.user.create.useMutation({
    onSuccess: () => trpc.user.list.invalidate(),
  });
  
  if (isLoading) return <Loading />;
  
  return (
    <div>
      {data?.users.map(user => (
        // user의 타입이 서버 반환 타입과 완전히 동일
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

4.2 tRPC 장단점

tRPC의 강점:
✅ 코드 생성 없이 엔드투엔드 타입 안전성
✅ Zod 기반 입력 검증
✅ React Query와 자연스러운 통합
✅ Optimistic Updates, 캐싱이 기본 내장
✅ 개발 속도 극적으로 빠름

tRPC의 약점:
❌ TypeScript 풀스택 전용 (다른 언어 클라이언트 없음)
❌ 외부 API로 노출 불가 (공개 API는 REST)
❌ HTTP 캐싱이 제한적 (RPC 특성상)
❌ 모바일 앱(iOS/Android)에서 직접 사용 어려움

5. 비교 매트릭스

항목RESTGraphQLtRPC
타입 안전성OpenAPI 필요코드gen 필요자동 (TS)
외부 클라이언트✅ 완벽✅ 좋음❌ TS만
캐싱자연스러움복잡제한적
오버페칭있음없음없음
N+1 문제있음DataLoader로 해결없음
학습 곡선낮음높음낮음(TS 안다면)
생태계매우 성숙성숙성장 중
모바일 SDK쉬움가능어려움

6. 선택 프레임워크

의사결정 트리:

외부 API가 필요한가?
  → YES: REST (+ OpenAPI)
  → NO: 다음으로

모바일 앱이 직접 소비하는가?
  → YES: REST 또는 GraphQL
  → NO: 다음으로

TypeScript 풀스택인가?
  → YES: tRPC (강력 추천)
  → NO: REST

복잡한 관계 데이터, 멀티 클라이언트인가?
  → YES: GraphQL (+ DataLoader)
  → NO: REST 또는 tRPC

[2026년 신규 프로젝트 권장]
- Next.js + TypeScript 풀스택: tRPC
- 외부 공개 API + 모바일: REST + OpenAPI
- 복잡한 플랫폼 (Netflix, GitHub 수준): GraphQL

마무리

2026년에도 "REST vs GraphQL vs tRPC" 논쟁이 계속되지만, 답은 간단하다:

  • tRPC: TypeScript 풀스택 신규 프로젝트에서 최고의 DX
  • REST: 공개 API, 멀티 언어 클라이언트의 표준
  • GraphQL: 복잡한 데이터 그래프, 멀티 클라이언트 플랫폼

하나를 선택해야 한다면, 내부 TypeScript 앱은 tRPC, 외부 API는 REST로 시작하라. GraphQL은 팀이 REST/tRPC의 한계를 직접 느낄 때 도입을 검토하는 것이 현실적이다.