REST API vs GraphQL: 실무에서 뭘 선택해야 할까?
새 프로젝트를 시작할 때 "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 }),
},
}
장단점 비교#
| 기준 | REST | GraphQL |
|---|---|---|
| 학습 곡선 | 낮음 | 중간 |
| 유연성 | 낮음 (서버 정의) | 높음 (클라이언트 정의) |
| 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를 고집할 필요도 없습니다. 프로젝트 규모, 팀 역량, 클라이언트 요구사항을 종합적으로 고려해서 선택하세요.
관련 포스트
웹 성능 최적화 실전 가이드: Core Web Vitals와 최적화 기법
LCP, FID, CLS 등 Core Web Vitals의 의미와 실제 프론트엔드 성능을 개선하는 실전 기법을 정리했습니다.
Docker 입문 가이드: 개발자가 꼭 알아야 할 컨테이너 기초
Docker가 왜 필요한지부터 이미지, 컨테이너, Docker Compose까지 개발자 관점에서 실용적으로 정리했습니다.
CSS Flexbox vs Grid: 언제 뭘 써야 할까?
Flexbox와 Grid의 차이점을 명확히 이해하고, 각 상황에 어떤 레이아웃 방식을 선택해야 하는지 실전 예제와 함께 정리했습니다.