Abstract
Introducing how to apply GraphQL
with GraphQL Code Generator
& React Query
on Next.js
framework base.
GraphQL
. 기존의 REST API
호출 방식을 넘어 schema
를 이용해 마치 DB
를 다루는 sql
과 같이 데이터 호출을 다룰수 있는 새로운 개념.
GQL
은 이미 개발자라면 익숙한 용어가 되버렸지만 아직도 현재 진행형이며 이번 포스팅에는 Query 솔루션인 React Query
와 기존의 GQL
의 pain point 중 하나인 Type
과 Schema
관리, 불필요한 반복적인 코드 작성을 자동으로 해결해주는 GraphQL Code Generator
를 이용해 GQL
을 직관적으로 관리하는 방법을 소개하고자한다.
GQL에 대한 내용은 하기 참조
GQL 이란?: https://tech.kakao.com/2019/08/01/graphql-basic/
Getting Started
원하는 프로젝트 폴더에 Next.Js TypeScript
프로젝트를 생성
Terminal
pnpm create next-app . --typescript
React Query
로 GQL
를 사용하고자 필요한 패키지를 설치
Note
최근
React Query
는 패키지 명이TanStack Query
큰 카테고리로 묶였는데 추후Sevelte Query
등 다양한 플랫폼을 지원할 >예정이라 한다.
Terminal
pnpm add -S @tanstack/react-query graphql graphql-request pnpm add -D @tanstack/react-query-devtools
환경 변수도 사용할 예정이기에 dotenv
패키지도 설치
Terminal
pnpm add -S dotenv
.env.local
을 생성한뒤 API URL 을 등록
GraphQL
API 주소는 Fake GraphQL을 제공하는 GraphQLZero
를 이용하였다.
GraphQLZero Link: https://graphqlzero.almansi.me/
.env.local
NEXT_PUBLIC_GRAPHQL_URL=https://graphqlzero.almansi.me/api
env 항목이 Type Error 에 잡히지 않도록 next-constants.d.ts
파일을 생성하고 default type으로 변수 선언
next-constants.d.ts
declare namespace NodeJS { export interface ProcessEnv { NEXT_PUBLIC_GRAPHQL_URL: string; } }
Common Approach by React Query
+ GraphQL
react query
설정을 위해 _app.tsx
을 다음과 같이 수정
Note
SSR
이기에SEO
와UX
를 최적화 하기위해Hydration State
를 설정Hydration
개념은 하기 링크 참조pageProps.dehydratedState
는Serverside Props
에서 이용할 예정refetch
를 최소화해서UX
최적화
const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, }, }, });
pages/_app.tsx
import '../styles/globals.css'; import type { AppProps } from 'next/app'; import { Hydrate, QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; export const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, }, }, }); function MyApp({ Component, pageProps }: AppProps) { return ( <QueryClientProvider client={queryClient}> <Hydrate state={pageProps.dehydratedState}> <Component {...pageProps} />; <ReactQueryDevtools initialIsOpen /> </Hydrate> </QueryClientProvider> ); } export default MyApp;
GraphQlZero
의 album
Resolver Query를 react query
로 요청
legacy.tsx
파일을 생성하고 실제로 데이터가 들어오는 것을 확인한다.
Note
type
이나schema
는GraphQlZero
의 Docs를 참고해서 원하는 데이터만 임의로 작성
interface AlbumQuery { album: { id: string; title: string; user: { id: string; name: string; username: string; email: string; company: { name: string; bs: string; }; }; photos: { data: { id: string; title: string; url: string; }; }; }; } const albumQueryDocument = gql` query album($id: ID!) { album(id: $id) { id title user { id name username email company { name bs } } photos { data { id title url } } } } `;
-
getStaticProps
를 이용해Server
에서 미리 캐시된dehydratedState
를 내려준다.
export const getStaticProps = async () => { await queryClient.prefetchQuery(['album'], useAlbumFetcher); return { props: { dehydratedState: dehydrate(queryClient), }, }; };
pages/legacy.tsx
import type { NextPage } from 'next'; import { useQuery, dehydrate } from '@tanstack/react-query'; import { request, gql } from 'graphql-request'; import { queryClient } from './_app'; interface AlbumQuery { album: { id: string; title: string; user: { id: string; name: string; username: string; email: string; company: { name: string; bs: string; }; }; photos: { data: { id: string; title: string; url: string; }; }; }; } const albumQueryDocument = gql` query album($id: ID!) { album(id: $id) { id title user { id name username email company { name bs } } photos { data { id title url } } } } `; const useAlbumFetcher = async () => await request<AlbumQuery, { id: string }>( process.env.NEXT_PUBLIC_GRAPHQL_URL, albumQueryDocument, { id: '2', } ); export const getStaticProps = async () => { await queryClient.prefetchQuery(['album'], useAlbumFetcher); return { props: { dehydratedState: dehydrate(queryClient), }, }; }; const Legacy: NextPage = () => { const { data } = useQuery<AlbumQuery>(['album'], useAlbumFetcher); const { album } = data!; return ( <> <header style={{ textAlign: 'center' }}> <h1>Hello GraphQL + React Query !</h1> </header> <hr /> <main> <p style={{ textAlign: 'center', color: 'grey' }}>{JSON.stringify(album)}</p> </main> </> ); }; export default Legacy;
Result - React Query
+ GraphQL
Note - Pain Point
여기까지가 기존의
gql
+react query
방식을 통해 데이터를 받아오는 과정이다.
하지만 이런 방식에는 Pain Point가 존재한다.
schema
에 대응하는Type
을 직접 작성schema
변경이 있다면Type
역시 Docs 를 확인한 후 직접 변경 필요- 요청할때마다 반복적인 코드 반복 작성
New Approach by React Query
+ GraphQL
+ GraphQL Code Generator
이런 Pain Point를 자동으로 해결해주는 것이 GraphQL Code Generator
다.
GQL Code Generator
관련 패키지를 설치
Terminal
# code generator core 패키지 pnpm add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations # code generator react query 관련 패키지 pnpm add -D @graphql-codegen/typescript-react-query @graphql-codegen/typescript-graphql-request # code generator yaml loader 패키지 pnpm add -D yaml-loader
codegen.yml
파일을 생성한 뒤 설정값 입력
Note
schema
는graphql
폴더 안에[filename].graphql
로 관리하기로 한다. (ts나 다른 확장자도 가능)documents
에는gql schema
파일 형식과 위치를 설정해준다.documents: 'graphql/**/!(*.generated).{graphql,ts}'
schema
는GQL URL
위치. 여기서는 환경 변수로 관리 하기때문에 다음과 같이 작성schema: ${NEXT_PUBLIC_GRAPHQL_URL}
- 중요 옵션들은 다음과 같다. 나머지 내용들은 하기 링크에서 확인: https://www.graphql-code-generator.com/plugins/typescript/typescript-react-query
exposeFetcher
:GetStaticProps
,GetServerSideProps
에 prefetch로 사용할 query fetcher 함수를 exportexposeQueryKey
:react quey
의query key
도 exportfetcher
: fetcher로 사용할 모듈. 여기서는 기존에 사용했던graphql-request
사용한다.
codegen.yml
documents: 'graphql/**/!(*.generated).{graphql,ts}' schema: ${NEXT_PUBLIC_GRAPHQL_URL} require: - ts-node/register generates: graphql/generated.ts: plugins: - typescript - typescript-operations - typescript-react-query config: interfacePrefix: 'I' typesPrefix: 'I' skipTypename: true declarationKind: 'interface' noNamespaces: true pureMagicComment: true exposeQueryKeys: true exposeFetcher: true withHooks: true fetcher: graphql-request
pakage.json
에 graphql-codegen
script추가
package.json
{ ... "scripts": { ... "generate:gql": "graphql-codegen --require dotenv/config --config codegen.yml dotenv_config_path=.env.local" }, ... }
graphql
폴더를 생성한뒤 요청할 schema
파일을 작성
여기서는 album
관련 schema를 album.graphql
에 작성
graphql/album.graphql
query album($id: ID!) { album(id: $id) { id title user { id name username email company { name bs } } photos { data { id title url } } } }
여기까지 진행했다면 모든 준비를 완료한 상태
현재 파일 구조는 다음과 같다.
structure
. ├── graphql/ │ └── album.graphql ├── pages/ │ ├── _app.tsx │ ├── index.tsx │ └── legacy.tsx ├── ... ├── codegen.yml ├── next-constants.d.ts ├── package.json └── ...
이제 GraphQL Generator
를 사용해 Type
, Method
를 자동 생성이 가능
script를 실행하면 graphql
폴더 안에 generated.ts
가 생성
이 파일안에는 schema
에 대응하는 Query Function
, Type
들이 자동으로 생성되어 있는 것을 확인할 수 있다. (파일 내용은 생략)
Terminal
pnpm generate:gql ✔ Parse Configuration ✔ Generate outputs
자동생성된 Query Method
와 Type
을 통해 보다 쉽게 album
의 데이터들을 호출해보자
pages
폴더안에 new.tsx
파일을 다음과 같이 작성
legacy.tsx
와 정확히 동일한 기능을 하는 페이지이다.
Note
useQuery
관련 코드가 자동 생성된useAlbumQuery
한줄로 대체
const { data } = useAlbumQuery(gqlClient, { id: '3' });
-
prefetchQuery
에서key
,fetcher
코드를 직접 작성할 필요없이useAlbumQuery.getKey()
,useAlbumQuery.fetcher()
로 대체
await queryClient.prefetchQuery( useAlbumQuery.getKey({ id: '3' }), useAlbumQuery.fetcher(gqlClient, { id: '3' }) );
-
Type
은 자동 선언되어 연결되어있기 때문에 따로 작성 필요 불가 - 이후
server spec
변경으로 인한schema
변경시code generate
명령 한줄로 반복적인type
매칭, 재작성 과정을 생략할 수 있다.
pages/new.tsx
import type { NextPage } from 'next'; import { dehydrate } from '@tanstack/react-query'; import { GraphQLClient } from 'graphql-request'; import { useAlbumQuery } from '../graphql/generated'; import { queryClient } from './_app'; const gqlClient = new GraphQLClient(process.env.NEXT_PUBLIC_GRAPHQL_URL); export const getStaticProps = async () => { await queryClient.prefetchQuery( useAlbumQuery.getKey({ id: '2' }), useAlbumQuery.fetcher(gqlClient, { id: '2' }) ); return { props: { dehydratedState: dehydrate(queryClient), }, }; }; const New: NextPage = () => { const { data } = useAlbumQuery(gqlClient, { id: '2' }); const { album } = data!; return ( <> <header style={{ textAlign: 'center' }}> <h1>Hello GraphQL + React Query !</h1> </header> <hr /> <main> <p style={{ textAlign: 'center', color: 'grey' }}>{JSON.stringify(album)}</p> </main> </> ); }; export default New;
Result
👉 CodeSandBox Sample Link
Conclusion
본 포스팅에서는 GraphQL Code Generator
를 통해 Server Spec 변경할때마다 schema
변경뿐만 아니라 type
까지 재작성을 해야하는 GQL
의 Pain Point 를 해결하는 방법을 소개하였다. 추가적으로 SSR
을 통해 데이터 관련하여 hydration
하는 technique도 같이 소개하였다.
현재까지도 주류는 REST API
이다. 하지만 큰 변화가 없는 REST API
와 달리 GQL
에서는 여러가지 기능이 꾸준히 소개되고 발전하고 있다. 특히 Backend 와 Frontend 사이의 Communication Gap 을 줄여주는 방향으로 GQL
은 꾸준히 발전하고 있으며 이는 실무의 인적 비용과도 직접적으로 연결되는 방향이다.
이는 개발자라면 GQL
에 관해 앞으로도 꾸준히 관심을 가질만한 충분한 이유가 될것이다.
GQL Code Generator
에 대해 자세한 내용 하기 링크에서 확인할 수 있다.
Link: https://www.graphql-code-generator.com/
Top comments (0)