devlog.

REST API vs GraphQL: 실무에서 뭘 선택해야 할까?

·8분 읽기

새 프로젝트를 시작할 때 "API를 REST로 할까, GraphQL로 할까?"라는 고민을 하게 됩니다. 둘 다 HTTP를 기반으로 데이터를 주고받지만 철학이 다릅니다. 각각의 장단점과 실제 선택 기준을 정리했습니다.

REST API란?#

REpresentational State Transfer — 자원(Resource)을 URL로 표현하고, HTTP 메서드로 행위를 나타냅니다.

GET    /users          → 사용자 목록 조회
GET    /users/1        → 사용자 1번 조회
POST   /users          → 사용자 생성
PUT    /users/1        → 사용자 1번 전체 수정
PATCH  /users/1        → 사용자 1번 부분 수정
DELETE /users/1        → 사용자 1번 삭제

GET    /users/1/posts  → 사용자 1번의 게시글 목록

REST 응답 예시#

// GET /users/1
{
  "id": 1,
  "name": "홍길동",
  "email": "hong@example.com",
  "createdAt": "2024-01-01",
  "role": "admin",
  "avatar": "https://..."
  // 필요 없는 필드도 모두 옴
}

GraphQL이란?#

Facebook이 개발한 API 쿼리 언어입니다. 클라이언트가 필요한 데이터만 정확히 요청합니다.

# 하나의 엔드포인트: POST /graphql
query {
  user(id: 1) {
    name
    email
    posts {
      title
      createdAt
    }
  }
}
// 응답: 요청한 필드만 정확히 반환
{
  "data": {
    "user": {
      "name": "홍길동",
      "email": "hong@example.com",
      "posts": [
        { "title": "첫 번째 글", "createdAt": "2024-01-01" }
      ]
    }
  }
}

Over-fetching vs Under-fetching#

REST의 가장 큰 단점이자 GraphQL이 해결하는 문제입니다.

Over-fetching (과다 수신)#

// 사용자 이름만 필요한데 모든 정보가 옴
GET /users/1
→ { id, name, email, phone, address, avatar, createdAt, ... }

// GraphQL은 필요한 것만
query { user(id: 1) { name } }
→ { name: "홍길동" }

Under-fetching (과소 수신)#

// REST: 사용자 + 게시글 + 댓글이 필요하면 3번 요청
GET /users/1
GET /users/1/posts
GET /posts/1/comments

// GraphQL: 한 번의 요청으로 해결
query {
  user(id: 1) {
    name
    posts {
      title
      comments { content }
    }
  }
}

코드로 보는 차이점#

REST API 서버 (Express)#

// 엔드포인트마다 별도의 라우터
app.get('/users/:id', async (req, res) => {
  const user = await User.findById(req.params.id)
  res.json(user)
})

app.get('/users/:id/posts', async (req, res) => {
  const posts = await Post.findAll({ where: { userId: req.params.id } })
  res.json(posts)
})

app.post('/users', async (req, res) => {
  const user = await User.create(req.body)
  res.status(201).json(user)
})

GraphQL 서버 (Apollo Server)#

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
  }

  type Post {
    id: ID!
    title: String!
    author: User!
  }

  type Query {
    user(id: ID!): User
    users: [User!]!
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
  }
`

const resolvers = {
  Query: {
    user: (_, { id }) => User.findById(id),
    users: () => User.findAll(),
  },
  User: {
    posts: (user) => Post.findAll({ where: { userId: user.id } }),
  },
  Mutation: {
    createUser: (_, { name, email }) => User.create({ name, email }),
  },
}

장단점 비교#

기준RESTGraphQL
학습 곡선낮음중간
유연성낮음 (서버 정의)높음 (클라이언트 정의)
Over/Under fetching발생해결
캐싱HTTP 캐시 활용 쉬움복잡 (별도 설정 필요)
파일 업로드쉬움복잡
에러 처리HTTP 상태 코드항상 200, body에 errors
도구/생태계매우 성숙성숙 (빠르게 성장 중)
타입 안전성별도 도구 필요스키마로 기본 제공
N+1 문제적음DataLoader 필요

실무 선택 기준#

REST가 더 나은 경우:

  • 단순한 CRUD 위주의 서비스
  • 팀의 REST 경험이 많을 때
  • 파일 업로드/다운로드가 많을 때
  • 서드파티 API 연동이 많을 때

GraphQL이 더 나은 경우:

  • 여러 클라이언트(웹, 앱)가 다른 데이터 요구를 가질 때
  • 복잡하게 연결된 데이터 구조 (소셜 네트워크, 추천 시스템)
  • 빠르게 변하는 UI가 API 변경 없이 새 데이터가 필요할 때
  • 프론트엔드 개발 속도가 중요할 때

실무 트렌드#

스타트업 / 소규모: REST (빠른 개발, 낮은 러닝커브)
중대형 / 다수 클라이언트: GraphQL (유연성, 타입 안전성)
마이크로서비스: REST (서비스 간 통신)
BFF 패턴: GraphQL (백엔드-프론트엔드 중간 계층)

최근에는 tRPC도 주목받고 있습니다. TypeScript 풀스택 프로젝트에서 REST와 GraphQL의 장점을 결합해 완전한 타입 안전성을 제공합니다.

마무리#

REST와 GraphQL은 서로 대체가 아닌 다른 문제를 해결하는 도구입니다. REST로 잘 동작하는 서비스를 억지로 GraphQL로 바꿀 필요는 없고, 복잡한 데이터 요구사항이 있는데 REST를 고집할 필요도 없습니다. 프로젝트 규모, 팀 역량, 클라이언트 요구사항을 종합적으로 고려해서 선택하세요.

관련 포스트