apollo client에서 GraphQL query 다뤄보기

date
Jun 25, 2022
slug
apollo-gettings-started-query
status
Published
tags
Next.js
GraphQL
Apollo
summary
apollo client를 사용해 GraphQL을 사용해보자
type
Post
Updated At
Aug 1, 2022 01:18 AM
Created At
Jun 25, 2022 09:29 AM

시작하며

저번에 GraphQL에 대해서 개념적으로 파악해보았으니, 이번에는 실제로 Next.js에서 사용해보면서 기존에 사용 했었던 Flux 기반의 상태관리 라이브러리와 어떠한 차이점이 있는지 살펴보자. 🙂
당연하겠지만 GraphQL API를 호출하기 위해서 라이브러리를 사용하는게 필수는 아니다. fetch 함수 만으로도 간단하게 호출이 가능하지만, 라이브러리를 사용하면 다양한 기능을 제공하고 있으며 개발 경험 등 여러 측면에서 이점이 있기 때문에 해당 사항을 이해하는 정도로 넘어가고 바로 라이브러리를 설치해 진행해 볼 것이다.
그중에서 우리는 가장 많이 사용 되는 apollo client를 사용해 보자.

개발 환경 구축

우선 create-next-app을 사용해 next 환경을 구축하고, apollo와 graphql 패키지를 설치해주자.
yarn create next-app [프로젝트 명]
cd [프로젝트 명]
yarn add @apollo/client graphql
  • graphql : js에서 gql을 통해 데이터를 가져올 수 있도록 해주는 패키지이다.
  • @apollo/client : graphql 패키지에 의존하고 있으며, gql을 클라이언트에서 사용할 수 있도록 여러 기능을 제공한다.
이제 원하는 패키지를 설치하였고 yarn dev 를 입력하면 package.json에 명시된 script가 실행되며 node.js에 의해 next.js가 정상적으로 동작하는 모습을 볼 수 있을 것이다.

ApolloClient 인스턴스 초기화하기

해당 라이브러리를 사용하기 위해 apollo client 인스턴스를 생성하는 코드를 작성해보자.
// ./apollo-client.js

import { ApolloClient, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
    uri: "https://countries.trevorblades.com",
    cache: new InMemoryCache(),
});

export default client;
uri에는 gql server의 URL을 넣어주고 cache에는 InMemoryCache 인스턴스를 넣어주는데, 해당 인스턴스는 client에서 가져온 데이터를 캐싱 할때 사용된다.
이제 쿼리를 날리기 위해서는 기재한 URL이 어떠한 데이터를 가져올 수 있는지 알아야 하는데, 이때 Apollo Explorer를 사용한다.

Apollo Explorer

Apollo Explorer 를 사용하면 Rest API의 api 명세서처럼 해당 url을 통해 어떠한 데이터를 가져올 수 있는지 알 수있다. 우선 해당 링크를 통해 계정을 만들고, 우리가 기입했던 endpoint인 https://countries.trevorblades.com 를 입력해주면 다음과 같은 화면이 나온다.
notion image
이곳에서 스키마를 확인해볼 수 도 있고 쿼리도 간편하게 날려볼 수 있다. (REST API의 Postman과 비슷한 도구라고 생각하면 된다.)
Schema 페이지에 가면 가져올 수 있는 데이터가 명시 되어있다. 이중에 우리는 Country 중에서 code name emoji를 가져와 사용해 볼 것이다.
notion image
Operations 페이지에서 아래 쿼리를 실행해서 정상적으로 받아오는지 확인해볼 수 있다.
query ExampleQuery {
  countries {
    code
    name
		emoji
  }
}
아래 이미지처럼 데이터가 정상적으로 나온다면 성공이다.
notion image

Data fetching in Client-Side-Rendering

Next.js에서 데이터를 가져오는 방식에는 크게 getServerSideProps를 통한 SSR방식과 getStaticProps 를 통한 SSG방식, 그리고 CSR방식이 있는데, 각각이 불러오는 시점만 다를 뿐 gql을 통해 data fetching하는 것은 동일하기에 우리는 CSR을 통해서 데이터를 가져와 로딩까지 고려한 로직을 작성해보자.
우선 _app.js 에 컴포넌트를 ApolloProvider 로 감싸주어야한다.
import { ApolloProvider } from '@apollo/client'
import client from '../apollo-client'
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  )
}


export default MyApp
이제 클라이언트의 환경에서만 렌더링해주는 ClientOnly컴포넌트와 데이터를 가져오는 Countries 컴포넌트를 작성해보자.
 
ClientOnly 컴포넌트
import { useEffect, useState } from "react";

export default function ClientOnly({ children, ...delegated }) {
  const [hasMounted, setHasMounted] = useState(false);

  useEffect(() => {
    setHasMounted(true);
  }, []);

  if (!hasMounted) {
    return null;
  }

  return <div {...delegated}>{children}</div>;
}
 
Countries 컴포넌트
import { useQuery, gql } from "@apollo/client"

const QUERY = gql`
  query Countries {
    countries {
      code
      name
      emoji
    }
  }
`

export default function Countries() {
  const { data, loading, error } = useQuery(QUERY)

  if (loading) {
    return <h2><a href="#loading" aria-hidden="true" id="loading"></a>Loading...</h2>
  }

  if (error) {
    console.error(error)
    return null
  }

  const countries = data.countries.slice(0, 4)

  return (
    <div >
      {countries.map((country) => (
        <div key={country.code} >
          <h3><a href="#country-name" aria-hidden="true" id="country-name"></a>{country.name}</h3>
          <p>
            {country.code} - {country.emoji}
          </p>
        </div>
      ))}
    </div>
  )
}
useQuery라는 hook에 gql query를 넘겨주면 해당 쿼리를 통해 데이터를 가져온다. data, loading, error가 담긴 객체를 return 하는데, 알아서 비동기처리에 대한 상태관리를 해준다.
해당 컴포넌트를 index.js 에 넣어주자.
import Head from "next/head"
import styles from "../styles/Home.module.css"
import ClientOnly from "../components/ClientOnly"
import Countries from "../components/Countries"

export default function ClientSide() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <ClientOnly>
          <Countries />
        </ClientOnly>
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{" "}
          <img src="/vercel.svg" alt="Vercel Logo" className={styles.logo} />
        </a>
      </footer>
    </div>
  )
} 
아래와 같이 정상적으로 데이터가 출력되면 된다.
notion image

마치며

gql에 라이브러리 까지 사용하면 데이터를 처리하기 위해 많은 코드를 작성해야 하는 Redux에 비해 훨씬 간단하게 구현이 된다는 점이 좋았다. 다만 편한 만큼 추상화가 많이 되어있는 것 같아 내부 로직을 이해하지 어떠한 방식으로 동작 하는지 알고 사용하는 것이 좋을 듯 싶다.

reference