[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()
함수에 이 schema
와 mocks
를 인자로 넘겨 호출하면, 해당 스키마에 모킹 리졸버가 장착됩니다.
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"}]