GraphQL 개념 정리와 활용법
GraphQL 기본 개념
GraphQL은 Facebook이 2015년에 공개한 API 쿼리 언어이자 런타임.
모바일 앱 성능 향상을 위해 만들어졌다고 함.
핵심 아이디어는 클라이언트가 필요한 데이터를 정확히 요청하고 그에 맞게 정확히 응답받는 것.
# 쿼리 예시
query {
user(id: "123") {
name
email
posts {
title
}
}
}
REST API와 달리 단일 엔드포인트(/graphql)로 모든 요청을 처리하는 방식.
REST vs GraphQL 차이점
특성 ------------------------REST API ----------------------------------------GraphQL ------------------
엔드포인트 | 여러 개 | 단일 |
데이터 요청 | 여러 요청 필요할 수 있음 | 단일 요청으로 해결 |
응답 구조 | 서버가 결정 | 클라이언트가 지정 |
버전 관리 | 명시적 (/v1/, /v2/) | 점진적 진화 |
오버페칭 | 흔히 발생 | 방지 가능 |
REST는 여러 리소스에 대해 각각 요청해야 함:
GET /api/users/123
GET /api/users/123/posts
GraphQL은 하나의 쿼리로 해결:
query {
user(id: "123") {
name
posts { title }
}
}
핵심 개념
1. 스키마와 타입 시스템
GraphQL의 강력한 타입 시스템으로 API의 모든 데이터 구조를 명확하게 정의.
type User {
id: ID!
name: String!
email: String
posts: [Post!]
}
type Post {
id: ID!
title: String!
content: String
author: User!
}
!는 non-nullable 필드, []는 배열을 의미.
2. 작업 유형
- 쿼리(Query): 데이터 읽기 작업
- 뮤테이션(Mutation): 데이터 생성/수정/삭제
- 구독(Subscription): 실시간 업데이트
3. 리졸버(Resolver)
각 필드의 데이터를 어떻게 가져올지 정의하는 함수. 데이터베이스, REST API, 다른 GraphQL API 등 다양한 소스에서 데이터 가져올 수 있음.
const resolvers = {
Query: {
user: (_, { id }) => database.findUser(id)
},
User: {
posts: (parent) => database.findPostsByUser(parent.id)
}
};
간단한 GraphQL 서버 만들기
Apollo Server로 구현한 간단한 예시:
const { ApolloServer, gql } = require('apollo-server');
// 타입 정의
const typeDefs = gql`
type User {
id: ID!
name: String!
posts: [Post!]
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
posts: [Post!]!
}
`;
// 임시 데이터
const users = [
{ id: '1', name: '김개발' }
];
const posts = [
{ id: '1', title: 'GraphQL 시작하기', authorId: '1' }
];
// 리졸버
const resolvers = {
Query: {
user: (_, { id }) => users.find(user => user.id === id),
posts: () => posts
},
User: {
posts: (parent) => posts.filter(post => post.authorId === parent.id)
},
Post: {
author: (parent) => users.find(user => user.id === parent.authorId)
}
};
// 서버 실행
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`서버 실행 중: ${url}`);
});
생각보다 구현은 간단했음. 스키마 정의하고 리졸버 구현하는 방식이 직관적.
GraphQL이 유용한 상황
- 모바일 앱:
- 대역폭 제한 환경에서 필요한 데이터만 요청 가능
- 네트워크 요청 횟수 감소
- 복잡한 UI/대시보드:
- 한 번의 요청으로 여러 데이터 조합 가능
- 프론트엔드 변경 시 백엔드 수정 불필요
- 마이크로서비스:
- API 게이트웨이 역할
- 여러 서비스의 데이터 통합 지점
- 실제 사용 기업: GitHub, Shopify, Airbnb, Twitter 등
장점
- 필요한 데이터만 요청 가능 (오버페칭 방지)
- 여러 API 호출을 하나로 통합
- 강력한 타입 시스템
- 프론트엔드 개발 독립성 향상
단점
- 학습 곡선이 있음
- 캐싱이 REST보다 복잡
- 파일 업로드 별도 구현 필요
- N+1 쿼리 문제 해결 필요
도입 시 고려사항
N+1 쿼리 문제
많은 리졸버가 실행되면서 데이터베이스 쿼리가 중복 발생:
{
posts { # 1번 쿼리
title
author { # 각 post마다 쿼리 발생 (N번)
name
}
}
}
DataLoader로 해결 가능:
const userLoader = new DataLoader(async (userIds) => {
const users = await database.findUsersByIds(userIds);
return userIds.map(id => users.find(user => user.id === id));
});
보안 고려사항
- 쿼리 복잡성/깊이 제한 설정
- 실행 시간 제한
- 접근 제어 구현
GraphQL 생태계 도구
- 서버: Apollo Server, Express GraphQL, GraphQL Yoga
- 클라이언트: Apollo Client, Relay, urql
- 개발 도구: GraphiQL, GraphQL Playground, Apollo Studio
REST API와 완전히 다른 패러다임이지만 적절한 상황에서는 매우 유용한 도구라고 느낌.
특히 다양한 클라이언트에서 각기 다른 데이터 요구사항이 있을 때 유연성이 좋음.
모든 프로젝트에 적합한 것은 아니며, 간단한 CRUD 작업에는 REST가 여전히 좋은 선택일 수 있음.
두 기술을 상황에 맞게 혼합해서 사용하는 게 가장 효율적일 듯.
다음에는 Apollo Client나 Relay와 React에서 GraphQL 사용법에 대해 더 공부해봐야겠음.
참고 자료
- GraphQL 공식 웹사이트
- Apollo GraphQL 문서
- How to GraphQL 튜토리얼