💡 Apollo Client 가 무엇인지 알아본후, Next.js 에 Apollo Client 를 세팅하고 graphQL 을 사용할 수 있는 방법을 알아봅시다
Apollo Client 는 GraphQl 을 사용해 로컬 및 원격 데이터를 모두 관리할 수 있는 Javascript 용 상태관리 라이브러리입니다. UI 를 자동으로 업데이트하면서 애플리케이션 데이터를 가져오고, 캐시를 수정합니다. (공식문서 설명)
Apollo 는 GraphQL 을 사용한다면 거의 필수적으로 사용하는 상태관리 플랫폼이기도 합니다. 다른 선택지로 Facebook 에서 만든 Relay가 있지만, 학습 비용이 높고 React 계열만 지원한다는 단점이 있습니다.
그에 반해 Apollo 는 유연하고 러닝 거브가 높지 않을 뿐더러 Frontend 프레임워크 삼대장인 React, Angular, Vue 를 동시에 지원한다는 점에서 인기가 높습니다.
Apollo Client 는 GraphQL 쿼리 결과를 정규화된 로컬 인메모리 캐시에 저장합니다. 이를 통해 동일한 요청에 대해서는 새로운 네트워크 요청을 보내지 않고도, 이미 캐시된 데이터에 대한 쿼리에 거의 즉시 응답할수 있습니다.
// before
{
"__typename": "Person",
"id": "cGVvcGxlOjE=",
"name": "Luke Skywalker",
"homeworld": {
"__typename": "Planet",
"id": "cGxhbmV0czox",
"name": "Tatooine"
}
}
// after
{
"__typename": "Person",
"id": "cGVvcGxlOjE=",
"name": "Luke Skywalker",
"homeworld": {
"__ref": "Planet:cGxhbmV0czox"
}
}
위와 같이 homeworld 필드에 적절한 정규화된 Planet 개체가 참조에 포함
됩니다.데이터가 메모리에 없을 때
데이터가 메모리에 있을 때
위에서 알아본것과 같이 Apollo Client 는 GraphQL API 을 호출할 수 있게 하는 도구이자, 캐시를 이용한 상태관리 라이브러리이기도 합니다. 최근에는 useQuery, useMutation 과 같은 hook API 를 제공해 좀더 선언적으로 비동기처리를 할 수 있게 됐습니다. Next.js 에 apollo client 를 설치하고 사용하는 방법에 대해 알아봅시다.
yarn add @apollo/client graphql
// typescript 설치
yarn add -D @types/graphql
// ./apollo-client.ts
import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
uri: "https://countries.trevorblades.com",
cache: new InMemoryCache(),
});
export default client;
프로젝트의 루트에 apollo-client.ts 라는 파일 생성하고, 연결할 엔드포인트를 적어 ApolloClient 객체를 초기화해줍니다. 여기서 사용한 엔드포인트는 GraphQL 연습을 위한 공개 API 입니다.
위에서 생성한 ApolloClient 객체를 Next.js(React.js) 에 연결합니다. 앱 내에서 특정 컴포넌트만 GraphQL API 호출이 필요한 경우가 아닌 이상, 모든 컴포넌트에서 ApolloClient 를 사용할 수 있도록 설정하는 것이 일반적이라고 합니다. Next.js 의 루트 파일은 _app.tsx
이기 때문에, 이 파일에서 <ApolloProvider>
로 앱의 최상위 컴포넌트를 감싸고, client 객체를 주입해줍니다.
import { ApolloProvider } from "@apollo/client";
import client from "../apollo-client";
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<title>Apollo Test</title>
<meta charSet="utf-8"></meta>
</Head>
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
</>
);
}
export default MyApp;
apolli client 라이브러리는 react hook 방식으로 GraphQL 을 호출할 수 있도록 useQuery, useMutation Apollo Hooks 을 제공합니다. 그리고 라이브러리에서 제공하는 gql
이라는 template literal tag 를 사용해 일반 자바스크립트 문자열을 GraphQL 구문으로 바꿔줍니다.
데이터를 읽어올 때는 useQuery
을 사용합니다. useQuery 함수는 응답 데이터 (data)
뿐만 아니라, 로딩 여부(loading)
과 오류 데이터 (error)
까지 함께 리턴하기 때문에 아래와 같이 비동기처리와 예외처리를 쉽게 할 수 있습니다.
useQuery 의 첫번째 인자로 gql 로 작성한 쿼리를 넘겨줍니다.
import React from "react";
import { useQuery, gql } from "@apollo/client";
interface Country {
code: string;
emoji: string;
name: string;
}
interface CountryData {
countries: Country[];
}
const GET_COUNTRIES = gql`
query Countries {
countries {
code
name
emoji
}
}
`;
// GRAPHQL READ
export function ApolloRead() {
const { data, loading, error } = useQuery<CountryData>(GET_COUNTRIES);
if (loading) {
return <h2>로딩중</h2>;
}
if (error) {
return <h1>에러 발생</h1>;
}
const countries = data?.countries.slice(0, 4);
return (
<div>
{countries?.map((country: Country, idx: number) => (
<div key={`country-${idx}`}>
{country.code} / {country.emoji} / {country.name}
</div>
))}
</div>
);
}
하지만 대부분의 경우에는 위처럼, 정적으로 리스트만 불러오는 경우는 많지 않습니다. 대부분의 애플리케이션에서 필드를 불러오기 위한 인수는 동적입니다. graphQL 공식 문서에 따르면, 이러한 동적 인수를 query string 에 직접 전달하는 것은 좋은 방법이 아니라고 합니다.
대신 graphQL 은 쿼리 동적 값을 넘기기 위해 variables
를 사용합니다.
// query
hero(episode: $episode) {
name
friends {
name
}
}
}
//variables
{
"episode": "JEDI"
}
이를 apollo client 를 사용한 React.js 에 적용해보면 아래와 같이 작성할 수 있습니다.
그 전에 앞서 우리가 사용하는 API 의 모습은 아래와 같습니다. GraphQL 스키마 시각화를 위해 https://studio.apollographql.com/sandbox/explorer 를 사용했습니다.
위 정보들을 조합해서 code 가 “AD” 인 countries 의 emoji, name, code 필드를 불러오자 라는 쿼리를 작성하면 아래와 같이 작성할 수 있습니다.
이제 graphQL 쿼리를 만들어봤으니, 실제 Next.js 프로젝트에 적용해봅시다. useQuery 의 두번째 인자로는 variables 객체를 넘겨줄 수 있습니다. 이렇게 variables 인자를 통해, 동적인 쿼리 조회가 가능합니다.
import React from "react";
import { useQuery, gql } from "@apollo/client";
interface Country {
code: string;
emoji: string;
name: string;
}
interface CountryData {
countries: Country[];
}
const GET_COUNTRIES = gql`
query Countries($filter: CountryFilterInput) {
countries(filter: $filter) {
code
name
emoji
}
}
`;
// GRAPHQL READ_BYFILTER
export function ApolloReadById() {
const { data, loading, error } = useQuery<CountryData>(GET_COUNTRIES, {
variables: {
filter: {
code: {
eq: "AD",
},
},
},
});
if (loading) {
return <h2>로딩중</h2>;
}
if (error) {
return <h1>에러 발생</h1>;
}
const countries = data?.countries.slice(0, 4);
// https://studio.apollographql.com/sandbox/explorer
return (
<div>
<h1>필터링 건 데이터</h1>
{countries?.map((country: Country, idx: number) => (
<div key={`country-${idx}`}>
{country.code} / {country.emoji} / {country.name}
</div>
))}
</div>
);
}
GraphQL을 사용하면, 하나의 엔드포인트로 데이터 스키마를 한꺼번에 관리할 수 있어 자원과 용도별로 엔드포인트를 나누는 기존의 RESTAPI 를 개발하기 위한 백엔드의 공수를 줄일 수 있다는 점이 가장 큰 장점인 것 같습니다.
또한, 원하는 데이터 필드만 클라이언트에서 주도적으로 요청할 수 있으며, Apollo 가 자체적으로 캐싱을 지원하기 때문에 HTTP 요청 횟수(원하는 정보를 하나의 쿼리에 모두 담아 요청 가능)와, 사이즈를 줄일 수 있습니다. (필요한 정보만 부분적으로 요청가능)
하지만 아직 File 전송 등 text 만으로 하기 힘든 내용들을 처리하기 복잡하고, 재귀적인 query 가 불가능하다는 단점이 있다고 합니다. 그럼에도, 간단한 CRUD 로직에는 아주 강력한 퍼포먼스를 내기 때문에, 간단한 어드민이나 블로그를 제작하는 분들에게는 좋은 선택지가 될 수 있을 것 같습니다. (우리 사이드프로젝트에도^^)