Logo

[GraphQL] 원격 서버로 부터 스키마 가져오기

지난 포스팅에서 SchemaLink를 이용하여 서버 없이 클라이언트에서 GraphQL API를 호출하는 방법에 대해서 알아보았습니다. 이번 포스팅에서는 로컬에서 직접 스키마를 작성하지 않고 원격 서버로 부터 스키마를 가져오는 몇 가지 방법에 대해서 알아보겠습니다.

패키지 설치

예제 프로젝트에서 필요한 GraphQL과 Apollo Client 관련 패키지를 설치하고 시작하겠습니다. 여기서 graphql-tools 패키지가 가장 중요한데, 스키마 생성을 위해 makeExecutableSchema(), introspectSchema(), addMockFunctionsToSchema()와 같은 함수를 제공합니다.

$ npm i apollo-client apollo-cache-inmemory apollo-link-http apollo-link-schema graphql graphql-tag graphql-tools
  • package.json
  "dependencies": {
    "apollo-cache-inmemory": "1.6.3",
    "apollo-client": "2.6.4",
    "apollo-link-http": "1.5.15",
    "apollo-link-schema": "1.2.3",
    "graphql": "14.4.2",
    "graphql-tag": "2.10.1",
    "graphql-tools": "4.0.5"
  }

원격 서버의 GraphQL API 호출

일반적으로 HTTP 프로토콜을 통해서 원격에 있는 서버로 Graph API를 호출할 때는 서버의 주소(uri)만 알면 됩니다. createHttpLink() 함수에 서버 주소를 인자로 넘겨 호출하면 HttpLink 객체가 리턴되고, 이 HttpLink 객체를 ApolloClient() 생성자에 넘기면 됩니다.

예를 들어, 인터넷에 공개된 GraphQL API를 사용해서 대륙 데이터를 조회하는 쿼리를 호출해보겠습니다.

import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { createHttpLink } from "apollo-link-http";
import gql from "graphql-tag";
(async function () {
  const client = new ApolloClient({
    link: createHttpLink({ uri: "https://countries.trevorblades.com" }),
    cache: new InMemoryCache(),
  });

  const { loading, error, data } = await client.query({
    query: gql`
      query {
        continents {
          code
          name
        }
      }
    `,
  });

  console.log("continents:", JSON.stringify(data.continents));
})();

위 코드를 실행해보면 해당 서버와 연동하여 다음과 같이 7개 대륙의 데이터가 출력되는 것을 확인할 수 있습니다.

continents:
[{"code":"AF","name":"Africa","__typename":"Continent"},{"code":"AN","name":"Antarctica","__typename":"Continent"},{"code":"AS","name":"Asia","__typename":"Continent"},{"code":"EU","name":"Europe","__typename":"Continent"},{"code":"NA","name":"North America","__typename":"Continent"},{"code":"OC","name":"Oceania","__typename":"Continent"},{"code":"SA","name":"South America","__typename":"Continent"}]

원격 서버로 부터 스키마 조회

클라이언트 단에서 서버에서 정의한 GraphQL 스키마를 모킹해야할 때가 종종 있습니다. 대표적인 예로 서버 단에서 API 구현이 완료되지 않은 상황에서 클라이언트 개발을 진행해야할 경우가 있겠습니다.

서버에서 스키마를 조회할 때는 createHttpLink() 함수를 서버 주소를 인자로 넘겨 호출하면 HttpLink 객체를 얻습니다. 그 다음, graphql-tools 패키지에서 제공하는 introspectSchema() 함수에 이 HttpLink 객체를 인자로 넘겨 호출합니다.

여기서, 이 schema 객체는 타입 정의(typeDefs)만 가지고 리졸버(resolvers)를 가지고 있지 않기 때문에, 모킹 리졸버 객체인 mocks를 생성합니다. 그 다음, 역시 graphql-tools 패키지에서 제공하는 addMockFunctionsToSchema() 함수에 이 schemamocks를 인자로 넘겨 호출하면, 해당 스키마에 모킹 리졸버가 장착됩니다.

import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { createHttpLink } from "apollo-link-http";
import { SchemaLink } from "apollo-link-schema";
import gql from "graphql-tag";
import { introspectSchema, addMockFunctionsToSchema } from "graphql-tools";
(async function () {
  const link = createHttpLink({ uri: "https://countries.trevorblades.com" });
  const schema = await introspectSchema(link);
  const mocks = {
    Query: () => ({
      continents: () => [
        {
          code: "Mocked Code",
          name: "Mocked Name",
        },
      ],
    }),
  };

  addMockFunctionsToSchema({ schema, mocks });

  const client = new ApolloClient({
    link: new SchemaLink({ schema }),
    cache: new InMemoryCache(),
  });

  const { loading, error, data } = await client.query({
    query: gql`
      query {
        continents {
          code
          name
        }
      }
    `,
  });

  console.log("continents:", JSON.stringify(data.continents));
})();

위 코드를 실행해보면 다음과 같이 모킹 리졸버의 리턴값이 콘솔에 출력되는 것을 알 수 있습니다.

continents:
[{"code":"Mocked Code","name":"Mocked Name","__typename":"Continent"}]

원격 스키마로 부터 타입 정의 추출

모킹 리졸버 대신에 정식으로 리졸버를 설정하고 싶은 경우에는 서버에서 조회한 스키마에서 문자열로된 타입 정의 부분을 추출해야 합니다.

graphql/utilities 패키지에서 제공하는 printSchema() 함수를 통해 원격 스키마로 부터 타입 정의 문자열(typeDefs)을 얻어올 수 있습니다. 그 다음, graphql-tools 패키지에서 제공하는 makeExecutableSchema() 함수에 방금 얻은 typeDefs와 직접 구현한 resolvers를 넘겨서 호출하면 타입 정의와 리졸버를 모두 갖춘 실행 가능한 스키마를 얻을 수 있습니다.

import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { createHttpLink } from "apollo-link-http";
import { SchemaLink } from "apollo-link-schema";
import gql from "graphql-tag";
import { introspectSchema, makeExecutableSchema } from "graphql-tools";
import { printSchema } from "graphql/utilities";
(async function () {
  const link = createHttpLink({ uri: "https://countries.trevorblades.com" });
  const remoteSchema = await introspectSchema(link);

  const typeDefs = printSchema(remoteSchema);
  const resolvers = {
    Query: {
      continents: () => [
        {
          code: "Resolved Code",
          name: "Resolved Name",
        },
      ],
    },
  };

  const schema = makeExecutableSchema({
    typeDefs,
    resolvers,
  });

  const client = new ApolloClient({
    link: new SchemaLink({ schema }),
    cache: new InMemoryCache(),
  });

  const { loading, error, data } = await client.query({
    query: gql`
      query {
        continents {
          code
          name
        }
      }
    `,
  });

  console.log("continents:", JSON.stringify(data.continents));
})();

코드를 실행해보면 다음과 같이 리졸버에서 구현한 결과값이 콘솔에 출력되는 것을 알 수 있습니다.

continents:
[{"code":"Resolved Code","name":"Resolved Name","__typename":"Continent"}]

로컬 파일에 스키마 다운로드

간혹 네트워크 연결이 불가능하여 미리 원격 서버로 부터 스키마를 다운로드 받아 로컬 파일에 저장해두고 사용해야 할 때가 있습니다.

먼저, Apollo CLI의 apollo schema:download 커맨드를 이용하여 서버로 부터 스키마를 다운로드 받아 schema.json 파일에 저장합니다.

$ npx apollo schema:download --endpoint=https://countries.trevorblades.com schema.json

그 다음, schema.json 파일을 임포트 하고, graphql/utilities 패키지의 buildClientSchema() 함수를 통해 스키마를 얻고, printSchema() 함수를 통해 타입 정의 문자열을 추출합니다. 그 이후 과정은 이전 섹션과 동일합니다.

import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { createHttpLink } from "apollo-link-http";
import { SchemaLink } from "apollo-link-schema";
import gql from "graphql-tag";
import { makeExecutableSchema } from "graphql-tools";
import { printSchema, buildClientSchema } from "graphql/utilities";
import schemaFile from "./schema.json";
(async function () {
  const clientSchema = buildClientSchema({ __schema: schemaFile.__schema });
  const typeDefs = printSchema(clientSchema);

  const resolvers = {
    Query: {
      continents: () => [
        {
          code: "Local Code",
          name: "Local Name",
        },
      ],
    },
  };

  const schema = makeExecutableSchema({
    typeDefs,
    resolvers,
  });

  const client = new ApolloClient({
    link: new SchemaLink({ schema }),
    cache: new InMemoryCache(),
  });

  const { loading, error, data } = await client.query({
    query: gql`
      query {
        continents {
          code
          name
        }
      }
    `,
  });

  console.log("continents:", JSON.stringify(data.continents));
})();

위 코드를 실행해보면 다음과 같이 리졸버에서 구현한 결과값이 콘솔에 출력되는 것을 알 수 있습니다.

continents:
[{"code":"Local Code","name":"Local Name","__typename":"Continent"}]

전체 코드