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"}]

전체 코드