Next.JS입니다 근데 이제 GraphQL을 곁들인

우빈·2023년 8월 31일
5
post-thumbnail

GraphQL을 도입한 이유

Next.JS에 TypeScript를 베이스로 사용한 프로젝트를 진행하던 도중,
게시판과 관련된 도메인을 구현해야하는 작업이 생겼습니다.

문제점은, 게시판의 카테고리가 바뀔 때마다 글의 속성들이 바뀐다는 점이었습니다.
예를 들면,
common이라는 카테고리의 일반 글에는 title, content, category가,
project라는 카테고리의 글에는 title, content, techs, deadline 등,
lostfound라는 카테고리의 글에는 title, content, location 등으로,

이렇게 카테고리가 다를 경우 공통 값을 제외하고 다다른 값들을 핸들링해야 했습니다.

백엔드 개발자와 회의를 진행하던 도중 모든 글의 속성들을 포함하는 post를 사용하되,
카테고리에 해당치 않는 속성은 null을 주는 식으로 처리를 하자는 생각도 했습니다.

하지만 한 도메인에서 여러가지의 값을 가져오고 핸들링할 수 있는 GraphQL이라는
라이브러리를 사용해보는게 더 좋을 것 같다는 생각을 했습니다.

그래서 저희는 곧바로 스프링부트와 Next.JS에 GraphQL을 곁들였습니다.

@apollo/client 설치하기

Next.JS에서 GraphQL 사용을 도와주는 apollo라는 고마운 라이브러리가 있습니다.
이는 React-Query와 비슷한 문법으로 이루어져 있고, useQuery라는 키워드도
동일하게 사용됩니다. ( 덕분에 헷갈릴 수 있답니다ㅎㅎ.. )

먼저 이를 설치해보겠습니다.

yarn add @apollo/client

or

npm i @apollo/client

Provider 세팅하기

Apollo 또한 React Query처럼 전역적으로 값을 캐싱해주는 기능을 지원합니다.
이에 따라 프로젝트의 루트에서 요소들을 감싸는 Provider가 필요합니다.

코드를 먼저 보겠습니다.

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

const client = new ApolloClient({
  uri: `${process.env.NEXT_PUBLIC_BASE_URL}/api/graphql`,
  cache: new InMemoryCache(),
  connectToDevTools: true,
});

const ApolloClientProvider = ({ children }: PropsWithChildren) => {
  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

export default ApolloClientProvider;

저는 Next의 루트에서 바로 이를 사용하지 않고, ApolloClientProvider라는
컴포넌트를 하나 만든 다음, 이를 root provider에 추가해주는 식으로 구현했습니다.

GraphQL을 처음 쓸 때 들었던 의문점인데요, rest api가 하나밖에 없다는 점입니다.
GraphQL은 신기하게 한 API로 CRUD의 모든 관리를 처리할 수 있습니다.

따라서 백엔드 개발자가 맞춰둔 컨벤션의 api 주소만 적어주면, 이 주소 하나로
모든 처리를 할 수 있답니다.

useQuery, useMutation 사용하기

위에서 말했듯이, apollo도 useQuery와 useMutation이라는 네이밍을
사용합니다.

const { data, ...queryRest } = useQuery(gql`something query`), {
    variables: { type },
});

이런 식으로 사용할 수 있는데, apollo의 useQuery는 React Query의
변수명과 조금씩 다른 부분이 있답니다. 이를 참고하고 사용하면 유용할 것 같습니다.

두 번째 인자에는 variables라는 친구를 삽입해주었는데요,
이는 React Query에서의 queryKey와 비슷한 역할을 하는 친구입니다.

apollo는 GraphQL 쿼리 자체를 key로 삼기 때문에 따로 key를 지정해줄
필요가 없지만, 특정 값이 변경됐을 때 데이터를 refetch시키고 싶다면
이런 식으로 variables를 사용해 데이터를 리패치할 수 있습니다.

query 쓰기

GraphQL은 자신들만의 독자적인 문법을 사용합니다. 이는 간단하면서도
참신한 문법으로 저에게 다가왔습니다.

query를 이렇게 써야한다! 이렇게 해야 맞는거고 클린코드다! 라고 하기에는
GraphQL을 요리하는 법은 너무나도 다양합니다.

따라서 저는 제가 백엔드 개발자와 협업을 하며 사용한 GraphQL을 소개해보겠습니다.

프론트엔드에서는 gql이라는 함수를 import해서 사용해주면 됩니다.

gql`
    query {
      readByCategory ( category: "${type}" ) {
        id
        title
        content
        category
      }
    }
  `;

우리 프로젝트에서는 querymutation이라는 두 키워드로 gql을 크게 나눴습니다.
query는 GET 요청을, mutation은 나머지 요청들을 처리하는 것으로 구분됩니다.

위에서 소개한 요구사항에 맞게, category별로 다른 값들을 get해야합니다.

gql은 함수처럼 사용해주시면 되는데, 우리 프로젝트에서는 readByCategory 라는
네이밍을 사용했습니다.

먼저 함수처럼 키워드를 작성하고, 매개변수에는 백엔드에 전송할 값을 삽입해줍니다.
또 매개변수가 닫히고 중괄호가 열릴 때는, 가져오고 싶은 속성명을 작성해주면 됩니다.

const { data, ...queryRest } = useQuery(gql`{}...`, {
    variables: { type },
  });

해당 gql을 이런 식으로 useQuery의 첫 번째 인자에 넣고 실행해보면,
정상적으로 값이 불러와지는 것을 알 수 있습니다.

클린 코드 작성하기

사실 클린코드는 상대적이지만, 이 GraphQL을 조금 더 깔끔하고 번거롭지 않게
사용하는 방법이 있지 않을까 생각했습니다.

그래서 원래는 10분이면 도입할 기술을 클린 코드를 위해 한 시간, 두 시간 정도
고민하곤 했답니다ㅎㅎ..

저는 먼저 모든 게시글에 공통적으로 들어가는 title, content같은 속성들을
묶은 다음, 이를 템플릿 리터럴로 할당해주기로 했습니다.

const DEFAULT_POST = `
    category
    title
    content
`;

// const PROJECT = `
	${DEFAULT_POST}
	techs
    deadline
`

이런 식으로 말이죠. 또한 이런 카테고리마다 다른 DTO들을 객체에 카테고리명으로
저장하였습니다.

// data.ts
const DEFAULT_POST = `
    category
    title
    content
`;

export const posts = {
  COMMON: `
    ${DEFAULT_POST}
  `,
  NOTICE: `
    ${DEFAULT_POST}
  `,
  PROJECT: `
    ${DEFAULT_POST}
    techs
    startTime
    endTime
    field
  `,
  CODE_REVIEW: `
    ${DEFAULT_POST}
    prUrl
  `,
};

이렇게 하면 query를 날릴 때 백엔드에 전달할 매개변수에 넣는 type이란 변수를,
해당 posts 객체에도 똑같이 post[type]처럼 넣어 많은 처리 없이 깔끔하게 이를
사용할 수 있어 매우 코드가 깔끔해집니다.

이렇게 data.ts라는 파일에서 불러오기를 원하는 속성값들만을 선언해주고,
queries.ts라는 파일에서 이를 실제 gql로 바꿔주는 함수들을 선언했습니다.

// queries.ts
import { gql } from "@apollo/client";
import { PostCategoryType } from "@/types";
import { posts } from "./data";

interface IPostProps {
  type: PostCategoryType;
}

interface IPostQueryProps extends IPostProps {
  id: number;
}

interface IPostMutateProps extends IPostProps {
  data: unknown;
}

export const GET_POST = ({ type, id }: IPostQueryProps) => {
  return gql`
    query {
        readOne ( id: ${id} ) {
          ${posts[type]}
        }
    }
  `;
};

export const GET_POST_LIST = ({ type }: IPostProps) => {
  return gql`
    query {
      readByCategory ( category: "${type}" ) {
        id
        title
        content
        category
      }
    }
  `;
};

export const UPDATE_POST = ({ type, data }: IPostMutateProps) => {
  return gql`
    mutation {
      update ( input: ${data} ) {
        ${posts[type]}
      }
    }
  `;
};

export const CREATE_POST = ({ type, data }: IPostMutateProps) => {
  return gql`
    mutation {
      create ( input: ${data} ) {
        ${posts[type]}
      }
    }
  `;
};

data는 임의적으로 unknown을 넣어주었습니다.
이런 식으로 create, update, get을 담당하는 함수들을 만들어주었고,
이를 useQuery의 첫 번째 인자에서 사용해주는 식으로 코드를 설계했습니다.

import { useQuery } from "@apollo/client";
import { GET_POST_LIST } from "@/gql/post/queries";
import { PostCategoryType } from "@/types";

export const usePostListQuery = (type: PostCategoryType) => {
  const { data, ...queryRest } = useQuery(GET_POST_LIST({ type }), {
    variables: { type },
  });

  return { postList: data, ...queryRest };
};

저는 이런 식으로 클린 코드를 설계했는데, 더욱 좋은 방법이 있을 것이라 생각해
계속 다른 방법을 찾아보고 있습니다.

GraphQL의 장점

GraphQL을 쓰며 느낀 장점은 많았습니다. 한 api 주소에 여러가지의 요청을
처리할 수 있다는 점도 인상깊었습니다.

또한, 내가 원하는 데이터만 골라서 가져올 수 있고, 이를 GraphQL만의 참신한 문법으로
이를 사용할 수 있다는 점이 매우 신기했습니다.

다음에도 이와 비슷한 도메인을 구현해야 하는 일이 생긴다면, 꼭 팀원들과 협의해
GraphQL 도입을 추천할 것 같습니다.

마무리

GraphQL에 대해서 제가 직접 사용해볼 거란 생각은 하지 않았고,
제가 하는 개발과는 다른 영역의 기술이라고 생각했었습니다.

하지만 이렇게 GraphQL을 직접 사용해보니 일시적인 러닝 커브는 있었지만
개발을 하며 신박하고 좋은 경험을 할 수 있었던 것 같습니다.

한 도메인 내에서 여러가지의 값들을 다르게 사용해야할 때 GraphQL을
사용해보시는 것을 추천드립니다. 참고로 velog도 GraphQL을 사용한답니다!

앞으로도 여러가지 라이브러리를 도입하고 좋은 경험을 위해 정진하겠습니다.

profile
프론트엔드 공부중

1개의 댓글

comment-user-thumbnail
2023년 8월 31일

개발자가 천직이싱 것 가타요.. 존경스럽습니다

답글 달기