클라이언트에서 GraphQL을 효과적으로 활용하는 방법
최근 회사에서 참여하고 있는 프로젝트에서 GraphQL을 도입하게 되었다. 만들고자 하는 서비스에서 유연한 API 통신이 필요했고 이러한 상황에서 GraphQL이 도입된 것 같다. 따라서 이번 기회에 프론트엔드 개발자의 시각에서 GraphQL에 대해 자세히 알아보았다.
GraphQL은 2012년에 Facebook에서 개발하고 오픈스로 제공한 비교적 새로운 API 쿼리 언어다. GraphQL은 최신 애플리케이션에서 발견되는 복잡하고 중첩된 데이터 종속성을 처리하도록 설계되었다. 특히 클라이언트가 API에서 필요한 데이터를 정확히 지정할 수 있도록 선언적 데이터 가져오기를 활성화한다 .
GraphQL의 주요 특징
- 단일 요청-단일 응답: GraphQL을 사용하면, 클라이언트는 필요한 데이터만 요청하고, 서버는 정확히 그 데이터를 제공한다. 이는 여러 REST 요청을 보내지 않고도 원하는 모든 정보를 한 번에 가져올 수 있다는 것을 의미한다.
- 타입 시스템: GraphQL은 강력한 타입 시스템을 제공한다. 이를 통해 개발자들은 API 스키마에 따라 어떤 필드와 타입이 사용 가능한지 사전에 알 수 있다.
- 리얼타임 업데이트: GraphQL은 subscription이라는 기능을 제공하여 실시간 데이터를 클라이언트에 전달할 수 있다. 이는 실시간 채팅 앱과 같은 경우에 유용하게 사용될 수 있다.
- 자체 문서화: GraphQL API는 내장된 문서화 도구를 가지고 있다. 이는 개발자가 API를 이해하고 사용하기 쉽게 만든다.
REST와 GraphQL은 둘 다 특정 ID를 가진 리소스에 대한 아이디어를 포함한다는 점에서 근본적으로 유사하다. REST는 GET으로 데이터를 가져오고 GraphQL은 POST를 사용 하지만 둘 다 해당 요청에서 JSON 데이터를 반환할 수 있다. 또한 둘 다 다양한 유형의 리소스를 처리할 수 있으며 웹 API가 지정된 클라이언트에 대해 올바르게 작동하는지 확인한다.
REST와 GraphQL의 주요 차이점
| | REST | GraphQL | | ------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | | 데이터 요청 및 응답 | 서버가 정의한 데이터 형식에 따라 요청하고 응답 받음 | 클라이언트가 필요한 데이터를 정확히 명시하고 그에 따라 응답 받음 | | Over-fetching | 필요한 데이터보다 더 많은 데이터를 받게 될 수 있음 | 오직 클라이언트가 요청한 데이터만을 받음 | | Under-fetching | 한 번의 요청으로 충분하지 않은 데이터를 받을 수 있어 여러 번의 요청이 필요할 수 있음 | 단일 요청으로 여러 데이터를 받을 수 있음 | | API Versioning | 새로운 버전의 API가 필요할 때 새로운 엔드포인트를 만들어야 함 | 단일 엔드포인트에서 버전 관리가 가능하며 필드를 추가하거나 제거할 수 있음 | | Real-Time Updates | WebSocket 등 별도의 방법을 통해 실시간 업데이트를 구현해야 함 | Subscriptions를 통해 실시간 업데이트를 제공함 | | Caching | 각 엔드포인트 별로 HTTP 캐싱을 활용할 수 있음 | 캐싱은 클라이언트 측에서 처리해야 하며, 보통 Apollo Client 같은 라이브러리가 이를 도와줌 |
GraphQL과 REST 모두 특정 상황에서 이상적이라 할 만한 장점들을 가지고 있다.
GraphQL은 많은 REST 요소를 포함하면서도 더 발전된 접근 방식을 취하고 있다. 여전히 REST가 더 적합하게 다룰 수 있는 프로젝트들이 존재한다.
REST가 적합한 상황
- 더 간단한 웹 서비스가 관련되어 있는 경우 : REST는 웹 서비스가 아직 상대적으로 단순했던 시기에 개발되었다. 엔드포인트나 리소스를 과도하게 효율적으로 처리할 필요가 없는 프로젝트에 대해 그대로 사용하는 것이 좋다.
- 다양한 호환성이 필요한 경우 : REST는 웹 서비스를 설계하고 구현하기 위한 널리 사용되는 표준이다. 과거부터 사용되어 왔기 때문에 다양한 플랫폼, 언어, 프레임워크에서 지원되며, 대부분의 서버와 클라이언트는 RESTful HTTP 요청을 처리할 수 있다. 따라서 구축하는 API가 여러 시스템과 통합해야 하는 경우, 다양한 클라이언트(예: 웹, 모바일, IoT 장치 등)에서 사용해야 하는 경우 REST를 사용하는 것이 좋다.
GraphQL이 적합한 상황
- 모바일 사용자를 많이 대상으로 하는 경우 : GraphQL은 네트워크를 통해 전송되어야 하는 데이터를 최소화하여 데이터 로딩을 더 효율적으로 만든다. 그렇게 함으로써 저성능의 장치나 품질이 좋지 않은 네트워크에서 웹 서비스에 최소한의 방해로 접근하는데 도움이 된다.
- 한 번의 요청에서 여러 중첩된 엔드포인트를 호출해야 하는 경우 : GraphQL은 데이터의 과다 또는 부족 조회 문제를 해결하며, 단일 쿼리로 여러 리소스를 검색할 수 있다. 여러 번의 트립 없이 많은 관련 기능을 호출할 수 있어, 전체 과정을 더 간단하게 만든다.
GraphQL 클라이언트 라이브러리
Axios? Apollo Client?
GraphQL을 사용할 때 Apollo Client가 일반적으로 더 널리 사용됩니다. 이는 Apollo Client가 GraphQL을 지원하기 위해 특별히 설계되었기 때문인데, 이에는 여러 가지 이점이 있다.
- 내장 캐싱 기능: Apollo Client는 자동으로 쿼리 결과를 캐싱한다. 이는 같은 쿼리가 여러 번 실행될 때 매번 서버에 요청을 보내지 않아도 되며, 앱의 응답성을 향상시킨다.
- GraphQL 구문 지원: Apollo Client는 GraphQL 쿼리와 뮤테이션을 작성하고 실행하는데 필요한 모든 구문을 지원한다. 이에는 쿼리, 뮤테이션, 서브스크립션, 페이징, 서버 사이드 필터링 등이 포함된다.
- React와의 통합: Apollo Client는 React와 함께 사용하기에 적합하게 설계되었으며, React 컴포넌트 내에서 쿼리와 뮤테이션을 쉽게 관리할 수 있게 한다.
반면에, axios는 HTTP 요청 라이브러리로서 GraphQL 서버와 통신하기 위해 사용될 수 있지만, Apollo Client처럼 GraphQL에 특화된 기능은 제공하지 않는다. 따라서, 직접 쿼리를 작성하고 결과를 파싱하며, 캐싱과 같은 기능을 구현해야 한다.
이외에도 Relay, URQL 등 다른 GraphQL 클라이언트 라이브러리들도 있으며, 각각의 특징과 필요성에 따라 라이브러리를 선택할 수 있다.
- Relay는 Facebook에서 개발하였으며, 특히 대규모 애플리케이션에서 성능 최적화에 초점을 맞추고 있다.
- URQL은 Apollo Client나 Relay보다 가볍지만, 필요한 기능을 모두 제공한다.
React와 Apollo Client를 이용한 간단한 GraphQL 사용 예제
GitHub의 GraphQL API를 사용
GraphQL API를 사용하기 위한 토큰을 발급받아야 한다. GitHub 계정에서 개인 접근 토큰을 생성하고, 이 토큰을 사용하여 API에 요청한다.
*주의: GitHub 개인 접근 토큰은 다른 사람과 공유하지 않도록 주의하세요. 토큰은 프로젝트의 환경 변수 등 안전한 곳에 저장하고 사용하세요.
import React from "react"; import { ApolloProvider } from "@apollo/client"; import { ApolloClient, InMemoryCache, gql } from "@apollo/client"; const client = new ApolloClient({ uri: "https://api.github.com/graphql", headers: { authorization: `Bearer YOUR_GITHUB_PERSONAL_ACCESS_TOKEN`, }, cache: new InMemoryCache(), }); function App() { const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(false); const [data, setData] = React.useState(null); React.useEffect(() => { client .query({ query: gql` query { viewer { login } } `, }) .then((result) => { setData(result.data); setLoading(false); }) .catch(() => { setError(true); setLoading(false); }); }, []); if (loading) return <div>Loading...</div>; if (error) return <div>Error!</div>; return ( <div> <h1>{`Hello, ${data.viewer.login}`}</h1> </div> ); } export default function AppWrapper() { return ( <ApolloProvider client={client}> <App /> </ApolloProvider> ); }
위 코드는 로그인 한 GitHub 사용자의 사용자명을 가져오는 GraphQL 쿼리를 실행한다.
ApolloProvider
컴포넌트는 Apollo Client를 앱의 나머지 부분에 제공ApolloClient
객체는 GraphQL 서버의 URI와 요청 헤더를 설정client.query
메서드는 GraphQL 쿼리를 실행하고 결과를 반환- 반환된 결과는 React의 상태로 저장되고 화면에 표시된다.
GraphQL 성능 최적화하는 방법
쿼리 최적화
- 필요한 데이터만 정확히 요청하도록 쿼리를 작성
- GraphQL의 가장 큰 장점 중 하나는 클라이언트가 정확히 필요한 데이터만 요청할 수 있다는 것을 최대한 활용하면 불필요한 데이터 전송을 피하고 성능을 향상시킬 수 있다.
캐싱 활용
- Apollo Client와 같은 일부 GraphQL 클라이언트는 자동 캐싱 기능을 제공하여, 이전에 요청한 데이터를 재사용할 수 있게 한다.
Pagination
- 대량의 데이터를 가져올 때 Pagination을 사용하여 데이터를 작은 부분들로 나누어 가져오는 것이 성능 최적화에 좋다.
- GraphQL은 페이지네이션을 위한 기능을 제공한다.
배치 요청 및 병렬 요청
- 동시에 여러 요청을 하거나 한 번의 요청으로 여러 작업을 처리하려면, 병렬 요청과 배치 요청을 사용하면 좋다. 이는 네트워크 지연 시간을 줄이고 전체 요청 시간을 단축하는데 도움이 된다.