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)