React에서 GraphQL Code Generator 활용하기

Woosik Kim
식스샵 기술 블로그
10 min readMay 12, 2022

--

GraphQL Code Generator — React Apollo Typescript template

안녕하세요. 저는 식스샵 스토어프론트팀에서 서버를 개발하고 있는 김우식이라고 합니다 🙌

최근 팀에서 GraphQL을 도입하게 되었는데 React 환경에서 사용하는 GraphQL 클라이언트 라이브러리(apollo-client, urql 등)의 공식 문서에서 타입 생성에 대한 부분을 힘있게 전달하고 있지 않은 거 같아서 graphql-codegen 을 도입했을 때 어떤 이점을 가져올 수 있는 지에 대해 다뤄보게 되었습니다.

🚧 들어가기 전에

GraphQL API를 개발할 때는 Code First, Schema First 두 가지 방법론이 있습니다.

Code First 방식말 그대로 SSOT(Single Source Of Truth)를 코드에 두는 것인데요. NestJS 기준으로 백엔드 개발자가 적절한 데코레이터를 붙여주면서 비즈니스 로직을 작성하고 해당 소스가 성공적으로 컴파일되면 컴파일된 소스를 기반으로 스키마를 생성합니다.

Schema First 방식은 반대로 SDL(Schema Definition Language)을 먼저 작성한 후 스키마를 기반으로 타입을 생성해서 Resolver에서 엄격한 타입에 맞게 적절한 응답이 나갈 수 있도록 개발하는 방식을 말합니다.

저희 식스샵 스토어프론트팀에서는 Schema First 방식으로 개발하고 있는데요. 이 방식으로 선정한 이유는 실제 개발이 진행되지 않은 상태에서 클라이언트 개발자와 서버 개발자가 스키마를 중심으로 사전에 논의할 수 있기 때문에 추후 변경 사항에 대한 비용을 최소화할 수 있다는 장점이 크다고 판단했기 때문입니다. 실제로 저희 팀 프론트엔드에서는 백그라운드에서 서비스 워커를 돌려서 개발 및 테스트를 도와주는 라이브러리인 MSW(Mock Service Worker) 를 사용하고 있기 때문에 Schema First 방식의 장점을 더 극대화할 수 있다고 생각했습니다. 팀 내에서 MSW를 도입했던 이야기를 듣고 싶으시다면 이곳에서 확인해주세요!

개인적으로는 그 외에도 코드를 작성하기 전에 미리 비즈니스 요구사항에 집중하면서 사전 설계를 해볼 수 있다는 이점이 있었고, 크진 않지만 스키마가 비즈니스가 아닌 코드 기반으로 생성된다면 코드에 대한 종속성이 생길 수도 있을 거라는 노파심도 들었던 거 같습니다.

그렇다고 Code First 방식이 나쁘다는 것은 아닙니다. NestJS에서는 Apollo Server를 래핑해서 @nestjs/graphql 라는 모듈을 제공하고 있는데요. 공식 문서에서 Code First, Schema First 각각에 대한 가이드를 제공하지만 늘 Code First 방식에 대한 가이드가 앞으로 오도록 문서를 구성하고 있다는 점을 미루어봤을 때 Code First 방식을 더 추천하고 있다고 봐도 될 거 같습니다 😅

실제로 NestJS 커뮤니티에서 왕성한 활동을 보이고 있는 컨트리뷰터 Scott은 Code First 방식을 더 선호했습니다.

이전 스크린샷에서 볼 수 있듯 Scott백엔드 팀은 코드에만 집중할 수 있기 때문에 Schema First 방식보다 Code First 방식에서 더 좋은 DX(Developer Experience)를 기대할 수 있다고 말했습니다. 저 역시 이 부분에 대해 공감하며 GraphQL SDL 문법이 어려운 건 아니지만 본인이 전문성을 가지고 있는 프로그래밍 언어로 A-Z 개발했을 때 더 좋은 생산성을 낼 수 있다는 점을 간과하긴 어렵다고 생각합니다.

Code First 방식과 Schema First 방식 모두 장단점이 존재하니 우리 팀 상황에 대입해봤을 때 더 좋은 아웃풋을 낼 수 있을 거라는 판단이 서는 쪽으로 선택하는 게 중요한 거 같습니다.

🤔 GraphQL Code Generator, 왜 필요할까?

결국 Code First, Schema First 두 가지 방식 모두 산출물은 GraphQL Schema입니다. 이걸로 뭘하면 좋을까요? 단순히 개발자들이 알아보기 편하니까 이걸 문서로 쓰면 되는 걸까요?

당연히 아닙니다. 우리가 REST API 방식으로 통신할 때 자주 사용하는 JSON 포맷의 장점은 단순하고 직관적인 key/value 형태로 구성되어 있고, 다양한 프로그래밍 언어에서 직렬화 / 역직렬화가 가능하기 때문에 높은 호환성을 가진다는 점입니다.

GraphQL Schema 역시 마찬가지입니다. 스키마의 기본적인 타입은 우리에게 친숙한 객체 형태를 띠고 있습니다. 이를 구성하는 Scalar 타입 역시 우리가 보편적으로 사용하는 타입으로 구성되어 있구요.

마치 객체 타입같다.. 😮

이런 점에서 우리가 사용하는 TypeScript, Java, C# 등의 타입 언어(Typed Language)로 변환하기 좋은 형태를 띠고 있기 때문에 호환성이 좋습니다.

이렇듯 GraphQL을 사용하면 클라이언트와 서버간 통신 과정에 주고 받는 데이터의 타입을 보다 엄격하게 규정하기 때문에 런타임 오류를 최소화할 수 있습니다. 저희 스토어프론트 팀은 프론트엔드와 BFF 서버(Backend For Frontend) 모두 TypeScript를 사용해 개발하고 있습니다. 많은 개발자들이 JavaScript 대신 TypeScript를 사용하는 이유는 IDE에서 정적 분석을 수행하는 과정에서 잘못된 속성에 접근하거나 잘못된 타입 메서드를 사용할 때 발생할 수 있는 에러를 사전에 방지할 수 있다는 점 때문일겁니다.

post.tsx

만약 타입스크립트로 개발하고 있는 프론트엔드 앱에서 서버로부터 생성된 GraphQL Schema를 활용하지 않는다면 어떻게 될까요? 아마 타입스크립트의 장점을 살리기 위해서 직접 Schema 혹은 API 문서를 보면서 타입을 선언해야 할 겁니다. 워낙 지루한 작업이고, 시간이 오래 들어갈 수 있는 일이기 때문에 생산성을 저하시키는 요인이 될 수 있습니다. 이때 GraphQL Schema를 타입으로 변환해주는 라이브러리 graphql-codegen 을 사용하면 이 문제를 해결할 수 있습니다. 아래는 저희 팀이 사용하고 있는 urql 기준으로 작성되었으며, 라이브러리마다 조금씩 기본 셋업이 다르니 공식 문서에서 참고하시면 좋을 거 같습니다 😊

🔨 기본 설정

graphql-codegen으로 타입을 변환하기 위해서 먼저 설정 파일을 만들어야 합니다. 저희는 아래와 같이 구성했는데요. 각 설정들에 대해 간단하게 설명하고 넘어가려고 합니다.

codegen.yaml

schema: GraphQL Schema 소스를 어디서 가져올 것인지를 설정하는 옵션입니다. 직접 서버와 통신해서 가져올 수도 있고, 만약 클라이언트와 서버 소스를 모노레포로 관리하고 있다면 파일 경로를 지정해줄 수도 있습니다. (e.g. ./typeDefs/*.graphql)

documents: gql 태그를 사용해서 쿼리 언어를 작성하거나 .graphql 파일 내에 작성해 둔 쿼리 언어를 Document로 변환할 수 있도록 파일 경로를 지정하는 옵션입니다.

plugins: 타입으로 변환할 때 사용할 플러그인들을 명시할 수 있는 옵션입니다.

제공하는 옵션에 대해 자세하게 보고 싶으시다면 codegen.yml 공식 문서를 참고해주세요 🙌

📮 타입 생성

이처럼 우리 프로젝트에 맞게 적절하게 옵션을 지정해서 파일을 만들어주셨다면 이제 graphql-codegen cli를 이용해서 타입을 생성해주시면 됩니다.

graphql-codegen cli
generated/graphql.tsx

이렇게 생성된 타입을 활용하면 아까 개발자가 직접 정의해두었던 타입 대신 이걸 사용하면 되겠네요. 작성해 둔 코드를 수정해보면 아래와 같을 겁니다.

post.tsx

이제는 프론트엔드 개발자가 직접 타입을 선언하지 않고, 자동 생성된 타입을 불러와서 사용할 수 있게 되었습니다. graphql-codegen GraphQL Schema의 메타 정보를 읽어들여서 타입스크립트가 이해할 수 있는 타입으로 변환해주는 작업을 해줬기 때문이에요. 또한 스키마를 SSOT로 사용해 정확한 타입을 생성해주니 개발자가 타입을 선언하는 과정에서 휴먼 에러가 발생할 수 있는 부분도 방지할 수 있습니다 😄

🎨 커스텀 훅 생성

프론트엔드에서 Resolver에 대한 Input, Ouput 정보를 활용해서 안전한 타입 시스템 아래에서 개발할 수 있게 되었다는 점에서 많이 개선되긴 했지만 프론트엔드 개발자 입장에서는 .tsx 파일 안에서 GraphQL 쿼리 언어를 작성하고 싶지 않을 수도 있고, 데이터를 가져온 후에 타입을 붙여주는 것도 불필요한 작업처럼 보입니다. 아까 위에 작성해 둔 documents 옵션을 통해 이를 개선해 볼 수 있습니다.

post.graphql

이처럼 .graphql 파일을 만들어서 쿼리 언어를 작성하고나서 다시 cli 명령어를 입력하면 미리 작성해둔 쿼리에 해당하는 hook이 자동으로 생성됩니다. 이는 graphql-codegen의 writeHooks 옵션을 통해 생성되는데요. 앞서 보여드렸던 설정파일에는 해당 옵션을 주지 않았지만 writeHooks 옵션의 기본값이 true 이기 때문에 커스텀 훅이 자동으로 생성됩니다. 또한 .graphql 파일을 생성하지 않고, gql 태그를 이용해서 .tsx 파일 내에 쿼리 언어를 작성하더라도 커스텀 훅이 생성됩니다.

generated/graphql.tsx

이제 커스텀 훅이 생겼으니 이를 활용해서 아까 직접 쿼리문을 작성해서 데이터를 받아오는 부분을 개선할 수 있겠네요. 컴포넌트를 수정하면 아래와 같이 수정해 볼 수 있을 겁니다.

post.tsx

아까와 다르게 따로 변수에 타입을 입혀주지 않아도 자동으로 응답값에서 타입을 추출해오는 걸 볼 수 있습니다. 더불어 variable 속성에 인자로 파라미터를 넣어줄 때도 타입스크립트가 유효성 검사를 수행하기 때문에 유효하지 않은 파라미터를 넣게 되면 타입 에러를 던져줍니다. 서버 개발자와 불필요한 커뮤니케이션 비용을 줄일 수 있고, 런타임에서 테스트 해보지 않아도 오류를 잡을 수 있기 때문에 많은 시간을 절약하게 되었습니다.

👣 마치며

GraphQL을 잘 활용하기 위해서는 어느정도는 러닝커브가 있다고 생각합니다. 이 러닝커브는 기존에 익숙한 방식은 REST API 통신에 대한 사고 방식을 깨는 것도 포함되지만 관련된 다양한 도구들을 알지 못하는 경우도 많은 거 같습니다. 저도 아직 능숙하게 다루지 못하기 때문에 늘 아쉬움을 많이 느끼고 있습니다. 하지만 GraphQL이 잘 맞는 제품에 적용하거나 효과적으로 사용한다면 많은 장점이 있다고 생각합니다. 비교적 국내에서 많은 기업들이 사용하는 기술은 아니지만 관심 가져볼 만한 기술인 건 확실한 거 같습니다.

긴 글 읽어주셔서 감사합니다 🙂

References

식스샵은 올인원 이커머스 플랫폼을 만들고 있는 팀입니다.

B2B | E-Commerce | SaaS 시장에 대해 함께 고민하고, 빠른 실험과 검증을 통한 사업의 성장을 경험하고 싶은 분을 모시고 있습니다.

채용 중인 포지션은 이곳에서 확인하실 수 있습니다 🙂

--

--