API 설계 패턴 비교 분석 2025

15 min read
compare api rest graphql grpc websocket api-design microservices

REST vs GraphQL vs gRPC vs WebSocket - API 설계 패턴 선택 가이드

🔌 API 설계 패턴 비교 분석 2025

프로젝트 요구사항에 따른 최적의 API 설계 패턴 선택 가이드


📊 개요

비교 대상

  • REST: Representational State Transfer
  • GraphQL: Facebook의 쿼리 언어
  • gRPC: Google의 RPC 프레임워크
  • WebSocket: 양방향 실시간 통신
  • JSON-RPC: 경량 RPC 프로토콜
  • SOAP: 엔터프라이즈 표준 (레거시)

평가 기준

  • 개발 생산성
  • 성능 (처리량, 지연시간)
  • 유연성
  • 타입 안전성
  • 클라이언트 복잡도
  • 생태계 성숙도

📈 상세 비교표

핵심 특성 비교

| 특성 | REST | GraphQL | gRPC | WebSocket | |------|------|----------|------|-----------| | 프로토콜 | HTTP/HTTPS | HTTP | HTTP/2 | WS/WSS | | 데이터 형식 | JSON/XML | JSON | Protocol Buffers | Any | | 통신 방식 | Request-Response | Request-Response | 양방향 스트림 | 양방향 | | 스키마 정의 | OpenAPI | GraphQL Schema | Proto files | 없음 | | 타입 안전성 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ | | 성능 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | | 복잡도 | 낮음 | 중간 | 높음 | 중간 |

장단점 분석

| 항목 | REST | GraphQL | gRPC | WebSocket | |------|------|----------|------|-----------| | 장점 | • 단순함• 캐싱 용이• 표준화 | • 유연한 쿼리• Over/Under-fetching 해결• 단일 엔드포인트 | • 고성능• 양방향 스트리밍• 다국어 지원 | • 실시간• 양방향• 낮은 오버헤드 | | 단점 | • Over-fetching• N+1 문제• 버전 관리 | • 캐싱 복잡• 파일 업로드 제한• 학습 곡선 | • 브라우저 제한• 디버깅 어려움• 프록시 문제 | • 상태 관리• 확장성 제한• HTTP 기능 부재 |


💼 사용 사례별 구현

🛍️ 이커머스 API (REST)

추천: RESTful API

// Express.js REST API import express from 'express'; import { validate } from 'express-validation'; const app = express(); // 상품 목록 조회 (페이지네이션, 필터링) app.get('/api/v1/products', validate(productQuerySchema), async (req, res) => { const { page = 1, limit = 20, category, minPrice, maxPrice, sort } = req.query; const products = await Product.findAll({ where: { ...(category && { category }), ...(minPrice && { price: { $gte: minPrice } }), ...(maxPrice && { price: { $lte: maxPrice } }) }, limit, offset: (page - 1) * limit, order: [[sort || 'createdAt', 'DESC']] }); const total = await Product.count({ where: { /* same filters */ } }); res.json({ data: products, pagination: { page, limit, total, pages: Math.ceil(total / limit) }, links: { self: `/api/v1/products?page=${page}`, next: page < Math.ceil(total / limit) ? `/api/v1/products?page=${page + 1}` : null, prev: page > 1 ? `/api/v1/products?page=${page - 1}` : null } }); }); // 주문 생성 (트랜잭션) app.post('/api/v1/orders', authenticate, validate(orderSchema), async (req, res) => { const transaction = await sequelize.transaction(); try { // 주문 생성 const order = await Order.create({ userId: req.user.id, items: req.body.items, shippingAddress: req.body.shippingAddress, totalAmount: calculateTotal(req.body.items) }, { transaction }); // 재고 차감 for (const item of req.body.items) { await Product.decrement('stock', { by: item.quantity, where: { id: item.productId }, transaction }); } // 결제 처리 const payment = await processPayment({ orderId: order.id, amount: order.totalAmount, method: req.body.paymentMethod }, { transaction }); await transaction.commit(); res.status(201).json({ data: order, links: { self: `/api/v1/orders/${order.id}`, payment: `/api/v1/payments/${payment.id}` } }); } catch (error) { await transaction.rollback(); res.status(400).json({ error: error.message }); } }); // API 버전 관리 app.use('/api/v1', v1Routes); app.use('/api/v2', v2Routes); 

📱 모바일 앱 백엔드 (GraphQL)

추천: GraphQL

// Apollo Server GraphQL import { ApolloServer, gql } from 'apollo-server'; import { buildFederatedSchema } from '@apollo/federation'; // 스키마 정의 const typeDefs = gql` type User { id: ID! name: String! email: String! profile: Profile! posts(limit: Int = 10, offset: Int = 0): [Post!]! friends: [User!]! } type Profile { bio: String avatar: String location: Location } type Post { id: ID! title: String! content: String! author: User! comments(limit: Int = 10): [Comment!]! likes: Int! createdAt: DateTime! } type Query { me: User user(id: ID!): User feed(cursor: String, limit: Int = 20): FeedConnection! searchUsers(query: String!): [User!]! } type Mutation { createPost(input: CreatePostInput!): Post! likePost(postId: ID!): Post! followUser(userId: ID!): User! updateProfile(input: UpdateProfileInput!): Profile! } type Subscription { postAdded(userId: ID!): Post! messageReceived: Message! } `; // 리졸버 구현 const resolvers = { Query: { me: (_, __, { user }) => getUserById(user.id), feed: async (_, { cursor, limit }, { user }) => { // 커서 기반 페이지네이션 const posts = await getFeedForUser(user.id, cursor, limit); return { edges: posts.map(post => ({ node: post, cursor: encodeCursor(post.id) })), pageInfo: { hasNextPage: posts.length === limit, endCursor: posts.length ? encodeCursor(posts[posts.length - 1].id) : null } }; } }, User: { // N+1 문제 해결 (DataLoader) posts: async (user, { limit, offset }, { loaders }) => { return loaders.userPosts.load({ userId: user.id, limit, offset }); }, friends: async (user, _, { loaders }) => { return loaders.userFriends.load(user.id); } }, Mutation: { createPost: async (_, { input }, { user }) => { const post = await Post.create({ ...input, authorId: user.id }); // 구독자에게 알림 pubsub.publish('POST_ADDED', { postAdded: post }); return post; } }, Subscription: { postAdded: { subscribe: withFilter( () => pubsub.asyncIterator('POST_ADDED'), (payload, variables) => { return payload.postAdded.authorId === variables.userId; } ) } } }; // DataLoader로 N+1 문제 해결 const createLoaders = () => ({ userPosts: new DataLoader(async (keys) => { const posts = await Post.findAll({ where: { userId: keys.map(k => k.userId) }, limit: keys[0].limit, offset: keys[0].offset }); return keys.map(key => posts.filter(post => post.userId === key.userId) ); }) }); 

🎮 마이크로서비스 통신 (gRPC)

추천: gRPC

// service.proto syntax = "proto3"; package game; service GameService { // 단방향 RPC rpc GetPlayer(GetPlayerRequest) returns (Player); // 서버 스트리밍 RPC rpc ListPlayers(ListPlayersRequest) returns (stream Player); // 클라이언트 스트리밍 RPC rpc RecordGameplay(stream GameplayEvent) returns (GameplaySummary); // 양방향 스트리밍 RPC rpc GameStream(stream GameAction) returns (stream GameUpdate); } message Player { string id = 1; string name = 2; int32 level = 3; int32 experience = 4; repeated Item inventory = 5; PlayerStats stats = 6; } message GameAction { string player_id = 1; oneof action { MoveAction move = 2; AttackAction attack = 3; UseItemAction use_item = 4; } int64 timestamp = 5; } 
// Go 서버 구현 type gameServer struct { pb.UnimplementedGameServiceServer playerStore PlayerStore } // 양방향 스트리밍 구현 func (s *gameServer) GameStream(stream pb.GameService_GameStreamServer) error { playerId := extractPlayerId(stream.Context()) // 플레이어별 게임 상태 gameState := s.getOrCreateGameState(playerId) // 고루틴으로 클라이언트 액션 처리 go func() { for { action, err := stream.Recv() if err == io.EOF { return } if err != nil { log.Printf("Error receiving action: %v", err) return } // 게임 로직 처리 update := s.processAction(gameState, action) // 다른 플레이어에게 브로드캐스트 s.broadcastUpdate(update) } }() // 게임 업데이트 전송 for update := range gameState.Updates { if err := stream.Send(update); err != nil { return err } } return nil } // 클라이언트 (Python) import grpc import game_pb2 import game_pb2_grpc async def play_game(): async with grpc.aio.insecure_channel('localhost:50051') as channel: stub = game_pb2_grpc.GameServiceStub(channel) # 양방향 스트리밍 stream = stub.GameStream() # 액션 전송 태스크 async def send_actions(): while True: action = get_player_action() await stream.write(action) # 업데이트 수신 태스크 async def receive_updates(): async for update in stream: render_game_update(update) await asyncio.gather(send_actions(), receive_updates()) 

💬 실시간 채팅 (WebSocket)

추천: WebSocket + REST 하이브리드

// Socket.io + REST API import { Server } from 'socket.io'; import express from 'express'; const app = express(); const io = new Server(server); // REST API - 채팅방 관리 app.post('/api/rooms', authenticate, async (req, res) => { const room = await Room.create({ name: req.body.name, creatorId: req.user.id, members: [req.user.id] }); res.json({ data: room }); }); // REST API - 메시지 히스토리 app.get('/api/rooms/:roomId/messages', authenticate, async (req, res) => { const messages = await Message.findAll({ where: { roomId: req.params.roomId }, include: [{ model: User, as: 'sender' }], order: [['createdAt', 'DESC']], limit: 50 }); res.json({ data: messages }); }); // WebSocket - 실시간 메시징 io.use(socketAuth); io.on('connection', (socket) => { console.log('User connected:', socket.userId); // 룸 참가 socket.on('join-room', async (roomId) => { // 권한 확인 const hasAccess = await checkRoomAccess(socket.userId, roomId); if (!hasAccess) { return socket.emit('error', 'Access denied'); } socket.join(roomId); // 참가 알림 socket.to(roomId).emit('user-joined', { userId: socket.userId, timestamp: new Date() }); }); // 메시지 전송 socket.on('send-message', async (data) => { const { roomId, content, attachments } = data; // 메시지 저장 (REST API와 공유) const message = await Message.create({ roomId, senderId: socket.userId, content, attachments }); // 실시간 브로드캐스트 io.to(roomId).emit('new-message', { ...message.toJSON(), sender: await message.getSender() }); }); // 타이핑 인디케이터 socket.on('typing', ({ roomId, isTyping }) => { socket.to(roomId).emit('user-typing', { userId: socket.userId, isTyping }); }); }); 

🏢 실제 기업 사례

REST 사용

  • Twitter: REST API v2
  • GitHub: REST API v3
  • Stripe: 결제 API
  • 카카오: 오픈 API
  • 네이버: 오픈 API

GraphQL 사용

  • Facebook: 전체 API
  • GitHub: GraphQL API v4
  • Shopify: Storefront API
  • 토스: 일부 내부 API

gRPC 사용

  • Google: 내부 서비스
  • Netflix: 마이크로서비스
  • Uber: 서비스 통신
  • 라인: 메시징 서버

WebSocket 사용

  • Slack: 실시간 메시징
  • Discord: 채팅/음성
  • Figma: 실시간 협업
  • 당근마켓: 채팅

🔄 하이브리드 접근법

REST + GraphQL

// 공존 전략 app.use('/api/rest', restRoutes); app.use('/graphql', apolloServer.getMiddleware()); // REST에서 GraphQL 재사용 app.get('/api/rest/users/:id', async (req, res) => { const result = await apolloServer.executeOperation({ query: ` query GetUser($id: ID!) { user(id: $id) { id name email } } `, variables: { id: req.params.id } }); res.json(result.data.user); }); 

gRPC + REST Gateway

# gRPC Gateway 설정 type: google.api.Service config_version: 3 http: rules: - selector: game.GameService.GetPlayer get: /v1/players/{player_id} - selector: game.GameService.ListPlayers get: /v1/players 

💰 비용 및 성능 비교

성능 벤치마크

| 메트릭 | REST | GraphQL | gRPC | WebSocket | |--------|------|----------|------|-----------| | 요청/초 | 10K | 8K | 50K | 30K | | 지연시간 | 10ms | 15ms | 2ms | 1ms | | 대역폭 사용 | 100% | 60% | 30% | 50% | | CPU 사용률 | 낮음 | 중간 | 낮음 | 중간 |

개발 비용

  • REST: 가장 낮음 (표준화)
  • GraphQL: 중간 (초기 설정)
  • gRPC: 높음 (도구 체인)
  • WebSocket: 중간 (상태 관리)

🎯 선택 가이드

REST 선택 시

✅ 공개 API
✅ 간단한 CRUD
✅ 캐싱 중요
✅ 파일 업/다운로드
❌ 복잡한 데이터 요구
❌ 실시간 기능

GraphQL 선택 시

✅ 모바일 앱 백엔드
✅ 복잡한 데이터 모델
✅ 다양한 클라이언트
✅ Rapid development
❌ 파일 처리
❌ 단순 CRUD

gRPC 선택 시

✅ 마이크로서비스
✅ 고성능 필요
✅ 타입 안전성
✅ 스트리밍 데이터
❌ 브라우저 직접 호출
❌ 공개 API

WebSocket 선택 시

✅ 실시간 기능
✅ 양방향 통신
✅ 라이브 업데이트
✅ 협업 도구
❌ 단순 요청-응답
❌ 캐싱 필요


📚 추가 리소스

명세 및 도구

한국 기술 블로그

API 테스트 도구

  • REST: Postman, Insomnia
  • GraphQL: GraphQL Playground, Altair
  • gRPC: BloomRPC, grpcurl
  • WebSocket: wscat, Socket.io Client

💡 핵심 조언: API 설계는 "최신 기술"이 아닌 "적합한 기술"을 선택하는 것입니다. 대부분의 경우 REST로 시작하고, 특별한 요구사항(실시간, 유연한 쿼리, 고성능)이 있을 때 다른 패턴을 고려하세요. 여러 패턴을 조합하는 하이브리드 접근법도 좋은 선택입니다.

Found this helpful? Share it with others!
Tweet

🔗 Related Content

You might also be interested in these articles

🏢 company

🏢 Canva 기술 스택 분석

캔바가 1억 명 이상의 사용자에게 쉽고 강력한 디자인 도구를 제공하는 기술 스택 심층 분석 - Java, TypeScript, AWS로 구축한 대규모 디자인 플랫폼

35 min read
canva, java+10
Read more
🏢 company

🏢 Uber 기술 스택 분석

우버가 매일 2,500만 건의 라이드를 실시간으로 매칭하는 기술 스택 심층 분석 - Go, Java, Node.js로 구축한 글로벌 실시간 마켓플레이스

17 min read
uber, microservices+12
Read more
🏗️ stack

🏢 Enterprise Microservices Stack

대규모 트래픽과 복잡한 비즈니스 로직을 위한 마이크로서비스 아키텍처 - Go, gRPC, Kubernetes로 구축하는 확장 가능한 시스템

12 min read
go, grpc+13
Read more

Found this helpful?

Help us improve this content by contributing on GitHub or sharing your feedback with the community.