graphQL
기술

graphQL

작성일: 2023년 01월 30일0

01. What GraphQL?


graphQL는 Graph + Query Lenguage의 줄임말로 페이스북이 개발하여 오픈소스로 제공하는 쿼리 언어입니다. Server API를 통해 정보를 주고받기 위해 사용합니다. 즉 API를 위해 사용하는 쿼리 언어입니다.

Query Lenguage

서버에 CRUD를 요청하는 질의 언어입니다.

Graph형 자료구조를 사용하는 이유

GraphQL에서는 모든 데이터가 그래프 형태로 연결되어 있다고 전제합니다. 그래프라는 자료구조는 인간의 뇌 구조 및 언어적인 설명과 비슷하기 때문에 많은 분야에서 사용되는 방법입니다.

GraphQL는 그래프의 효율적인 탐색 방법을 차용하여 클라이언트 요청에 따라 유연하게 트리 구조의 JSON 데이터를 응답으로 전송합니다. 즉 클라이언트 요청에 따라 유연하게 자원을 가져올 수 있다는 이점이 있습니다.

그래프에서 트리 추출하는 방법

다음은 graphQL 쿼리와 그래프에서 트리를 추출하는 과정입니다.

// 요청을 보낸다.
query {
	책(ISBN:"9780674430006") {
		책 이름
		저자 {
			이름
		}
	}
}

// ISBN과 일치하는 값으로 응답을 받는다.
{
	책 : {
		책 이름 : "GraphQL은 어렵지 않다",
		저자 : [
			{ 이름 : "김코딩"},
			{ 이름 : "박해커"},
		]
	}
}

GraphQL는 ISBN 번호로 선택한 “책" 노드부터 시작하여 중첩된 각 필드로 표시된 간선을 따라 그래프를 탐색하기 시작합니다. 즉 쿼리 내 중첩된 “책 이름” 필드를 통해 책의 제목이 있는 노드로 이동합니다. 그러면서 “저자”로 레이블이 지정된 “책”의 간선을 따라가 “저자” 노드를 가져오고, 각 저자의 “이름"을 얻어오는 것입니다.

Notion Image

02. GraphQL VS REST API


REST API의 한계

Overfetch — 요청하고자 하는 데이터 외의 데이터도 포함될 수 있습니다.

Underfetch — 하나의 속성 혹은 데이터의 여러 정보를 요청하기 위해 각각의 자원이 해당된 endpoint에 여러번 요청해야 합니다.

유지보수에 불리함 — 클라이언트 구조 변경 시 엔드포인트 변경 또는 데이터 수정이 필요합니다

GraphQL의 장점

No! under & overfetching — 하나의 endpoint로 원하는 데이터를 요청하고 응답받습니다.

강력한 playground — playground라는 GUI를 이용해 resolver 와 schema 를 한 눈에 보고 테스트 해 볼 수 있습니다. (POSTMAN과 유사)

유지보수에 용이함 — 클라이언트 구조가 바뀌어도 필요한 데이터를 결정하고 받는 주체가 클라이언트이기 때문에 서버에 지장이 없습니다.

GraphQL의 단점

캐싱이 REST보다 훨씬 복잡합니다 — File 전송 등 Text 만으로 하기 힘든 내용들을 처리하기 복잡하며 이를 보완하기 위해 Apollo 엔진의 캐싱과 영속 쿼리 등이 등장하게 되었습니다.

무거운 Query — 고정된 요청과 응답만 필요할 경우에는 Query로 인해 요청의 크기가 RESTful API의 경우보다 더 커집니다.

재귀적인 Query가 불가능합니다 — 결과에 따라 응답의 깊이가 얼마든지 깊어질 수 있는 API를 만들 수 없습니다.

03. Sprint: GraphQL 다루기


Getting Started

Creat-React-App — CRA를 설치합니다.

npx create-react-app 'app name'

graphQL 설치 — 하나의 속성 혹은 데이터의 여러 정보를 요청하기 위해 각각의 자원이 해당된 endpoint에 여러번 요청해야 합니다.

npm install @octokit/graphql

Github 토큰 생성 — Github endpoint를 불러오기 위해 토큰을 발급받습니다.

환경 변수 설정 — Node.js의 환경 변수로 발급받은 토큰을 root폴더 최상위단에 넣어줍니다. .env 작성 시 주의할 점은 값에 "" 혹은 ''를 사용하면 안됩니다. 또한 끝에 ;를 찍어서도 안됩니다.

// .env
REACT_APP_TOKEN=발급받은 토큰

Bare Minimum Requirements

데이터 받아오기 — 배운 GraphQL 쿼리를 이용해 아고라 스테이츠에 접근하여 다양한 데이터를 조회해보았습니다.

//app.js
import { graphql } from "@octokit/graphql";
import { useEffect, useState } from "react";
import Output from "./Output";

function App() {
  const token = process.env.REACT_APP_TOKEN;
  
  // 환경변수에서 토큰값을 가져온다
  
  const [repo, setRepo] = useState({});

  const getRepo = async () => {
    
    // 공식문서대로 graphql에서 구조분해할당해온 repository를 꼭 리턴해준다.
    
    const { repository } = await graphql(
      `
        {
          repository(owner: "codestates-seb", name: "agora-states-fe") {
            discussions(first: 10) {
              edges {
                node {
                  id
                  title
                  createdAt
                  author {
                    avatarUrl
                  }
                }
              }
            }
          }
        }
      `,
      {
        headers: {
          authorization: `token ${token}`,
        },
      }
    );
    return repository;
  };
  
// 컴포넌트에 영향을 줄 수 있는 외부 데이터를 불러오는 것이기 때문에 useEffect로 제어한다. then의 인자로 들어오는 익명 함수의 인자인 data는 /graphql를 앤드포인트로 하여 요청된 repository의 데이터를 불러온다.
  
  useEffect(() => {
    getRepo()
      .then((data) => setRepo(data))
      .catch((error) => console.log(Error, error));
  }, []);

  return (
    <div className="App">
      <Output repo={repo} />
    </div>
  );
}

export default App;

컴포넌트 분리로 에러 핸들링하기 — discussion.edges가 새로고침 시 에러를 반환하며 값이 할당되지 않아 여러 방법을 찾아보다가 컴포넌트를 분리시켜 주었습니다.

//Output.js
const Output = ({ repo }) => {
  let arr = [];
  if (repo.discussions) {
    // props로 전달받은 데이터에 discussions이 담겨있을 때 빈 배열에 edges를 할당
    arr = repo.discussions.edges;
  }
  
  return (
    <ul>
      {arr.map((el) => {
        const data = el.node;
        return <li key={data.id}>{data.title}</li>;
      })}
    </ul>
  );
};

export default Output;
참조