[GraphQL #4] GraphQL API 만들기
해당 포스팅은 프로그래밍 인사이트에서 출판한 <웹 앱 API 개발을 위한 GraphQL> (이브 포셀로, 알렉스 뱅크스 저)을 바탕으로 작성한 글임을 먼저 밝힙니다.
GraphQL 서비스를 만들 때는 여러가지 언어로 개발을 할 수가 있다. 그 중에서 아무래도 가장 많이 쓰이는 언어 중 하나는 자바스크립트이며 아폴로팀에서 만든 오픈 소스 apollo-server를 현업에서는 많이 사용하는 편이다. 아폴로 서버는 설정하기가 간편하고 프로덕션 레벨에서 사용할 수 있는 여러 가지 기능들을 제공한다. 여기에는 서브스크립션, 파일 업로드, 데이터 소스 API 등등이 포함된다. 그래서 이번 포스팅에서는 아폴로 서버를 가지고 코드를 짜 보려고 한다.
프로젝트 세팅은 다음과 같이 할 수 있다.
apollo-server와 graphql을 설치해야 아폴로 서버 인스턴스를 설정할 수 있다. nodemon은 파일에서 바뀐 부분이 없는지를 감시하면서 변경 사항이 생길 때 마다 서버를 재시작한다. 그리고 나서 package.json에는 다음과 같이 초기 설정을 해줄 수가 있다.
리졸버(resolver)
앞에서는 주로 쿼리에 대한 내용을 다루었다. 스키마에는 쿼리를 정의해 두고 데이터 요구 사항에 대한 내용은 들어있지만 실제로 데이터를 가져오는 일은 스키마가 아닌 리졸버의 역할이다. 리졸버는 특정 필드의 데이터를 반환하는 함수이다. 스키마에 정의된 타입과 형태(shape)에 따라 데이터를 반환한다. 비동기로 작성할 수 있으며 REST API, 데이터베이스, 혹은 기타 서비스의 데이터를 가져오거나 업데이트 작업을 할 수 있다.
루트 쿼리(/index.js)에 들어가는 리졸버는 아래와 같이 작성할 수 있다. 먼저 type Query에 totalPhotos 필드를 추가한다. 그리고 totalPhotos 같은 쿼리를 작성하려면 쿼리와 같은 이름을 가진 리졸버 함수가 반드시 있어야 한다. 타입 정의하는 곳에 필드에서 반환한 데이터 타입을 적으면 리졸버 함수가 그 타입에 맞는 데이터를 가져다가 반환해 주게 된다. 초기 타입 정의는 다음과 같이 할 수 있으며 아폴로 서버를 사용한 예제는 아래와 같다.
이렇게 서버를 실행하면 다음과 같이 요청을 보내고 응답을 받을 것이다.
GraphQL API는 Query, Mutation, Subscription 루트 타입을 가진다. 이 세 타입은 최상단 레벨에 위치하며, 이들을 통해 사용 가능한 모둔 API 엔트리 포인트를 표현할 수 있다. Mutation과 Subscription도 Query와 만드는 방식은 동일하다. 먼저 type Mutation/Subscription에 필드를 만들고 resolvers 객체에 대응되는 리졸버 함수를 추가한다.
뮤테이션 루트 타입 예제를 아래에 작성해 보았다. postPhoto라는 필드를 만들고 name과 description을 인자로 받는다. postPhoto 뮤테이션을 생성 후, resolvers 객체에 이에 대응하는 리졸버 함수를 추가한다.
postPhoto 리졸버 함수의 경우 첫 번째 인자는 부모 객체에 대한 참조를 전달한다. postPhoto 리졸버 함수의 부모는 Mutation이다. 그리고 두 번째 인자는 GraphQL 인자로 뮤테이션에 사용한다. 이 때 name은 필수 값이며, description은 옵션이다. postPhoto 뮤테이션은 다음과 같이 사용해 볼 수가 있다.
리졸버 함수에는 Query, Mutation 이외에도 여러가지 타입을 추가할 수 있다. 그리고 각각의 타입에서 리졸버 함수를 추가해줄 수 있다. 예를 들어 사진 공유 어플리케이션에서는 Photo와 User라는 타입을 생각해 볼 수 있으며, 각각의 타입에서는 사진의 url, 작성자를 반환하는 리졸버 함수 등을 추가해 줄 수 있다.
apollo-server-express
실제 서비스에 아폴로 서버를 추가해야 하거나, 익스프레스 미들웨어를 서버에 사용해 장점을 누리고 싶은 경우가 있다면 apollo-server-express가 하나의 선택지가 될 수가 있다.
index.js 파일도 리팩토링을 해야 한다.
그리고 나서 playground 라우트를 추가로 만들어 주고 싶다면 npm 헬퍼 패키지인 graphql-playground-middleware-express 패키지를 설치한다. 그리고 플레이그라운드 라우트를 만들어 주면 된다. 서버 리팩토링 후에는 데이터베이스를 연결한다.
컨텍스트(Context)
컨텍스트에 전역으로 사용할 값을 저장해 두면, 리졸버 함수에서 이 값에 접근할 수가 있다. 컨텍스트에는 모든 정보를 저장할 수 있는데 인증 정보, 데이터베이스 세부 정보, 로컬 데이터 캐시, 그리고 GraphQL 리졸버 기능에 필요한 여러 가지 정보들을 넣어둘 수 있다.
리졸버 함수 안에서 REST API와 데이터베이스 호출을 직접 해도 되나, 일반적으로 이런 로직은 객체로 추상화하여 컨텍스트에 넣어둔다. 이를 통해 관심사 분리를 강제적으로 진행할 수 있으며, 나중에 리팩토링을 더 쉽게 할 수 있다. 데이터를 메모리에 저장하는 경우 확장성이 매우 떨어지며 ID 값도 데이터 변형 시점에 하나씩 증가하고 있다는 점은 좋은 구조가 아니다. 데이터베이스를 통해 값을 저장하고 ID를 생성하는 쪽으로 수정한다.
컨텍스트에 데이터베이스를 추가한 index.js 코드는 아래와 같다. 사용된 의존성 라이브러리는 모두 설치를 했다고 가정한다. DB는 mongoDB를 사용한다.
비동기 start 함수를 호출하면 데이터베이스가 앱에 연결이 된다. 데이터베이스에 연결되었다면 컨텍스트 객체에 연결 정보가 추가되고 서버가 시작된다. 그러면 쿼리 리졸버는 MongoDB 콜렉션 안의 정보를 반환 하도록 만들 수 있다.
사용자가 데이터베이스에 사진을 추가하려면 로그인이 되어 있어야 한다. 깃허브 인증 절차는 양이 많아서 다음 포스팅에서 다루어 보려고 한다.