react-native 어플리케이션에 Apollo client 통합하기 #1

Irrationnelle
Cross-Platform Korea
8 min readFeb 11, 2020

dooboolab 님께서 작성한 Integrate apollo client in react-native app #1 글의 번역입니다. 피드백 환영합니다.

이 포스트는 react native application 에서 graphql API를 사용하기 위해 apollo client 를 통합하는 과정을 다루고 있습니다.

우선, Apllo 와 graphql 이 제공하는 필수 패키지를 설치합니다.

yarn add @apollo/react-hooks apollo-boost apollo-link apollo-link-context apollo-link-error apollo-link-ws apollo-utilities graphql

다음은 블로그 작성 시점에 설치한 패키지 버전입니다.

"@apollo/react-hooks": "^3.1.3",
"apollo-boost": "^0.4.7",
"apollo-link": "^1.2.13",
"apollo-link-context": "^1.0.19",
"apollo-link-error": "^1.1.12",
"apollo-link-ws": "^1.0.19",
"apollo-utilities": "^1.3.3",
"graphql": "^14.5.8",

프로젝트 내부에서 동일한 버전의 graphql 버전을 사용하기 위해 package.jsonresolutions 항목을 추가하는 것이 좋습니다. 이미 설치한 다른 패키지와 서로 다른 버전의 graphql 을 사용하면 부수효과로 인해 버그가 발생할 수도 있습니다.

"resolutions": {
"graphql": "14.5.8"
}

@apollo/react-hooks 를 컴포넌트 내부에서 사용하기 위해서 부모 컴포넌트를 ApolloProvider 로 감싸줍니다. 과거에는 @trojanowskireact-apollo-hooks 패키지를 사용하여 이런 형태를 구현할 수 있었지만, 이제는 Apollo 에서 공식적으로 구현하고 있습니다.

<ApolloProvider client={client}>
<App />
</ApolloProvider>

위 코드에서 눈여겨 봐야하는 것은, ApolloProvider 에 전달하는 client 입니다. 이제부터 이 변수에 대해 이야기 해보겠습니다.

ApolloProvider 에는 client = new ApolloClient 와 같이 ApolloClient 클래스 변수를 전달해 주어야 합니다.

ApolloApolloClient 를 초기화 하는 다양한 방법을 제공해주고 있으며, 이를 통해 client.ts 라는 별도의 파일로 따로 분리하여 ApolloClient 를 초기화 하겠습니다.

  1. graphql API 의 엔드포인트에 접속하기 위한 http link 생성하기
const httpLink = new HttpLink({
uri: `${GRAPHQL_URL}`,
});

2. subscription에 사용할 web socket link 생성하기

const wsLink = new WebSocketLink({
uri: `ws://${GRAPHQL_URL}`,
options: { reconnect: true },
});

3. 보다 나은 성능을 위해 Apollo 가 제공하는 InMemoryCache 를 사용해 보겠습니다. fragments 와 함께 querymutation 쿼리로 동일한 결과값을 얻을 때 이 값들을 캐싱할 수 있어서 유용합니다.

const cache = new InMemoryCache();

4. 네번째 단계는 대부분의 어플리케이션이 설정하는 부분이기 때문에 상당히 유용합니다. 대다수의 어플리케이션들이 auth flow 를 다루기 때문에, Apollocontext 개념을 사용하는 미들웨어를 설정할 수 있도록 제공합니다.
이 방법은 React 와는 조금 다를수도 있습니다. React 에서는 AsyncStorage 대신 브라우저 캐시를 사용할 수도 있기 때문입니다.
authLink 를 만든 후에 앞서 만든 httpLink 를 연결합니다.

const authLink = setContext(async (_, { headers }) => {
const token = await AsyncStorage.getItem('token');
return {
headers: {
...headers,
Authorization: token,
},
};
});
const httpAuthLink = authLink.concat(httpLink);

5. (선택사항 — 사실 앞서 설명한 단계들도 선택사항으로 볼 수 있지만, 개인적으로 굉장히 필수적인 부분이라 생각합니다.)
이 단계에서는 에러 로그를 제공하기 위해 errorLink 를 추가합니다. 이 에러 로그들은 엔드포인트에서 예상치 못한 결과값이 나왔을 때 유용하게 사용할 수 있습니다. 별도의 패키지로 제공하는 apollo-link-error 로도 같은 기능을 수행할 수 있습니다.

const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
// eslint-disable-next-line no-console
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
}
// eslint-disable-next-line no-console
if (networkError) console.log(`[Network error]: ${networkError}`);
});

6. subscription 과 다른 graphql 쿼리들을 구분하기 위해, apollo-link 에서 제공하는 split 함수를 사용합니다. 여기서는 2단계에서 만든 websocket link 와 4단계에서 만든 httpAuthLink 를 사용합니다. 여기서 주의해야 할 점은, 아래 코드와 같이 반드시 httpAuthLink 를 최하단에 위치시켜주어야 한다는 것입니다. httpAuthLink 위치와 관련된 몇몇 이슈들이 있기 때문에 꼭 지켜주도록 합니다.

const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpAuthLink,
);

7. 마지막으로, 앞에서 설정했던 값들로 ApolloClient 를 생성하고 export 합니다.

export default new ApolloClient({
link: ApolloLink.from([
errorLink,
link,
]),
cache,
});

어플리케이션에 Apollo 클라이언트를 통합하는 것은 다소 혼란스러운 과정일 수 있습니다. 통합하는 방법이 여러가지가 있으며 패키지들 또한 나누어져 있기 때문입니다. 이것은 다양성을 주면서도 과정을 난해하게 만드는 양날의 검과 같습니다. 하지만 사용하기 전에 이 모든 것을 전부 이해해야 하는 것은 아니기 때문에 걱정하지 않아도 좋습니다. 또한, 다른 좋은 예시들을 찾아볼 수도 있습니다.

지금까지 이야기한 것들을 다시 정리하여 목록으로 요약해보겠습니다.

  1. querymutation 을 제공하는 graphql api 사용을 위해 Apollo 클라이언트를 어플리케이션에 통합한다.
  2. 사용자가 이미 인증을 거쳤다면, header 정보와 함께 graphql 쿼리를 요청하기 위해 auth flow 기능을 추가한다.
  3. subscription 쿼리 사용을 위해 web socket 을 결합한다.
  4. in-memory caching 을 사용한다
  5. 디버깅을 위해 errorLink 를 추가한다.

코드가 어떻게 작동하는지 이해했다면, 아래 코드를 단순히 복사 붙여넣기 하여 사용할 수 있습니다.

또한, 더 나은 best practice 가 있다면 Client.ts 를 더 발전시킬 수 있도록 공유해주세요!

--

--

Irrationnelle
Cross-Platform Korea

Avec la violence suffisante, il faut faire ta vie être l’adversité