Apollo Client GraphQL & React.js 함께 사용하기(한국어)

Ina·2020년 11월 28일
11

문서 번역

목록 보기
3/4
post-thumbnail

본 포스팅은 "Apollo client GraphQL for React From Scratch A-Z" by kirill ibrahim
on Medium
을 우리말로 번역한 글입니다. GraphQl이 무엇인지와 이의 필요성에 대해 제시하고, React와 Apollo Client를 함께 사용 하는 방법을 class기반/Hook기반 두가지 케이스로 나누어 소개하고 있습니다. React와 Apollo Client를 함께 사용하고자 하는 입문자에게 도움이 될 만한 글입니다.

Intro

지난 몇 년 동안 REST API는 Wep API 설계의 표준이었습니다. REST는 웹 서비스를 제공할 수 있는 일련의 규칙들을 제공하지만, 이러한 REST의 규칙들이 오히려 제약이 된다는 문제가 제기되기 시작했고, GraphQL은 이 문제를 극복하기 위해 등장했습니다.

🔹 GraphQL이란?

GraphQL은 Facebook에서 개발한 쿼리 언어입니다. GraphQL이 2015년에 오픈 소스로 전환되기 전에 Facebook은 2012년부터 내부적으로 GraphQL을 사용해오고 있었습니다. GraphQL는 개발자들이 REST API를 다루면서 경험하는 많은 단점과 비효율성을 해결해주었습니다.

REST API는 빠르게 변화하는 클라이언트의 요구 사항을 충족하기에는 유연성이 부족했습니다. 이 때문에 Facebook은 클라이언트의 요구를 충족하기 위해 모바일 애플리케이션에 GraphQl을 사용해오고 있었습니다.

GraphQl은 Front-End(이하 FE) 클라이언트가 실제 필요한 데이터 형식을 정확하게 요청할 수 있으므로 REST API보다 유연합니다. 반면에 REST API는 일반적으로 클라이언트에 필요하지 않은 중복 데이터를 포함하는 고정 데이터 형식을 반환하여 유연성이 떨어집니다.

예를 들어 FE 클라이언트는 사용자 이메일만 필요한데도 이 이메일 정보를 얻기 위해 웹 API를 호출하고 사용자의 모든 정보를 가져와야 했습니다. (email, name, age... 등등)

🔹 왜 GraphQL을 사용해야 하는지

1. 클라이언트가 필요한 데이터를 정확히 지정할 수 있음

REST API를 사용하면 백엔드(이하 BE)가 URL의 각 리소스에 사용할 수있는 데이터를 정의하는 반면, FE는 리소스의 일부만 필요하더라도 항상 리소스의 모든 정보를 요청해야합니다. GraphQL을 사용하면 더도 말고 덜도 말고 딱 필요한 특정 데이터만 받아올 수 있습니다.

2. Data Fetching

일반적으로 REST API를 사용하면 여러 엔드포인트에 액세스하여 데이터를 받아와야 합니다. 예를 들어 user 데이터를 가져 오기 위해서는,

  • 첫번째 - / users / <id> 엔드 포인트
  • 두번째 -사용자의 모든 게시물을 반환하는 / users / <id> / posts 엔드 포인트
  • 세 번째 - 사용자의 친구 목록을 반환하는 / users / <id> / friends

를 받아와야 합니다.

반면에 GraphQL을 사용하면 단일 쿼리에 구체적인 데이터 요구 사항을 적어 GraphQL 서버에 보내기만 하면 됩니다. 그럼 서버는 이러한 요구 사항에 맞게 JSON 객체를 보내줍니다.

query {
  user(id: "fsdf23"){
      name
      status
      posts{
       title
       body
      }
    friends{
       name
     }
  }
}

3. 데이터에 Type 시스템을 사용합니다.

GraphQL 스키마는 GraphQL 서버에서 처리가 가능하며 유효한 형태인지 알려주는 typed language입니다. 위 예시 쿼리에 대한 GraphQL의 스키마는 다음과 같습니다.

type user {
     name: String
     email: String
     phoneNumber: String
}

엄격한 type의 스키마를 사용함으로써 GraphQL 서버는 들어오는 쿼리의 유효성을 검사하여 정의된 스키마에 유효한 쿼리인지 확인합니다.

일단 쿼리가 유효한 것으로 확인되면 GraphQL 서버의 리졸버가 쿼리를 처리합니다. 리졸버 함수는 각 GraphQL 타입의 필드 각각을 지원합니다.

스키마가 정의되게 되면, FE와 BE개발자들은 네트워크를 통해 전송되는 데이터의 명확한 구조를 모두 알게되므로 이상 추가적인 소통이 필요하지 않게 됩니다.

FE 팀은 필요한 데이터 구조를 가상으로 만들어보고 애플리케이션을 쉽게 테스트 할 수 있게 됩니다. 서버가 구축이 되면 바로 클라이언트에서 실제 API에서 데이터를 받아올 수 있도록 세팅이 되는 셈입니다.

🔹 GraphQL Client 라이브러리

Graphql 클라이언트 라이브러리에는 Apollo Client, Relay, urql 등이 있습니다. 각각 다양한 장점/단점을 가지며 GraphQL에 대한 다양한 수준의 제어를 제공합니다.

프로젝트에 적합한 클라이언트를 선택할 때 도움이 될 만한 몇 가지 팁을 알려드리겠습니다.

Relay

Facebook이 개발하여 2015년에 GraphQL과 함께 오픈 소스화하였습니다. Relay는 퍼포먼스 최적화가되어 있으며 네트워크 트래픽을 줄여줍니다.

이러한 Relay의 성능에 대한 이점은 누리기 위해서는 높은 러닝커브를 뛰어넘어야 합니다. Relay는 복잡한 프레임 워크이므로 실제로 적용하려면 시간이 꽤 걸리는 편입니다.

Apollo Client

공식 문서
Apollo Client는 이해하기 쉽고 유연하며 강력한 GraphQL 클라이언트를 구축하기 위한 커뮤니티 중심 라이브러리입니다. 또한 JavaScript 앱을 위한 상태 관리 라이브러리이기도 합니다. GraphQL 쿼리를 작성하기만하면 Apollo Client가 데이터를 요청하고 캐싱하고 UI까지 업데이트해줍니다. Apollo Client는 React, Angular, VUE, Ember 등 다양한 플랫폼을 지원합니다.

urql

Relay와 Apollo 이후 출시되었으며, 보다 역동적인 라이브러리입니다. React에 포커스되어 있는 편이며, 단순성과 확장성에 중점을 둡니다.

🔹 Apollo Client와 React 사용하기

React Apollo를 사용하면 React로 복잡한 UI를 구축하는 동시에 React Apollo로 GraphQL 서버에서 데이터를 가져올 수 있게 됩니다. React Apollo는 React가 사용될 수 있는 모든 상황에서 사용될 수 있습니다.

Apollo Client 설치 및 구성방법 :

Hooks 없이 Apollo Client 사용하기

yarn:
yarn add apollo-boost react-apollo graphql
npm:
npm install apollo-boost react-apollo graphql

GraphQL API의 엔드포인트를 구성하기 위해 src/index.js를 열어줍니다.

import React from 'react'
import ReactDOM from 'react-dom'
import './styles/index.css'
import App from './components/App'
import * as serviceWorker from './serviceWorker';
//
// 1. 디펜던시 import 합니다
//
import { ApolloProvider } from 'react-apollo'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
//
// 2. Apollo Client 인스턴스를 GraphQL API와 연결하는 http Link생성합니다.
//
const httpLink = createHttpLink({
  uri: 'http://localhost:4000'
})
//
// 3. httpLink 및 InMemoryCache의 새 인스턴스를 전달하여 ApolloClient를 인스턴스화합니다.
//
const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache()
})
//
// 4. 클라이언트를 인자로 받는 HOC(higher-order component)인 ApolloProvider로 App을 감싸줍니다.
//
ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
)
serviceWorker.unregister();

Hooks으로 Apollo Client 사용하기

npm
install apollo-boost @apollo/react-hooks graphql
yarn
yarn add apollo-boost @apollo/react-hooks graphql

src/index.js 파일을 세팅해줍니다.

import React from 'react';
import ReactDOM from 'react-dom'
import './styles/index.css'
import App from './components/App'
import * as serviceWorker from './serviceWorker';
import { ApolloProvider } from '@apollo/react-hooks';
import ApolloClient from 'apollo-boost';
const client = new ApolloClient({
  uri: 'https://48p1r2roz4.sse.codesandbox.io',
});
ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
)
serviceWorker.unregister();

React-Redux와 Apollo Client 사용하기

위의 경우들과 마찬가지로 동일한 단계로 진행합니다. ApolloProvider 안쪽에 Provider로 app을 감싸주어 Redux를 사용할 수 있습니다. 이제 Redux를 Apollo Client 인스턴스와 동일하게 React 컴포넌트에서 사용할 수 있습니다.

import { Provider } from 'react-redux';
import { createStore } from 'redux';
...
ReactDOM.render(
  <ApolloProvider client={client}>
    <Provider store={store}>
      <App />
    </Provider>
  </ApolloProvider>,
  document.getElementById('root'),
);

( + react-redux와 Apollo client를 함께 사용하는 다른 방법)

🔹 Query

API에 쿼리를 보낼 때는 필드별로 데이터를 요청합니다. 이러한 필드는 서버에서 받은 JSON 데이터 응답의 동일한 필드에 매핑됩니다. GraphQL 쿼리는 문자열 형태로 서버로 전송되어, 서버에서 해석되고 이행된 뒤 클라이언트에 다시 JSON 형태로 반환됩니다.

예를 들어 users의 name, status 필드를 요청하는 쿼리를 보낸다면, users 배열 안에 각 user의 name, status가 문자열로 포함된 JSON 응답을 받게 됩니다.

query {
  users{
     name
     status
  }
}

Hooks 없이 React Apollo로 Query 보내기

Apollo를 사용할 때 서버에 쿼리를 보내는 두 가지 방법이 있습니다.

  1. Apollo Client에서 직접 쿼리 메서드를 사용하는 방법입니다. 데이터를 가져오는 매우 직접적인 방법이며 응답을 Promise로 처리할 수 있습니다.
client.query({
  query: gql`
    {
      users{
       name
       status
      }
    }
  `
}).then(response => console.log(response.data.users))
  1. Render Prop API 사용하기

첫번째로, 아래 디펜던시를 import 해줍니다.

import { Query } from 'react-apollo'
import gql from 'graphql-tag'

두번째, gql parser 함수를 사용하여 쿼리를 JavaScript const로 작성합니다.

const USERS_QUERY = gql`
  {
    users{
       id
       name
       status
    }
  }
`

셋째, GraphQL 쿼리를 prop으로 전달하는 컴포넌트를 사용합니다.

return (
  <Query query={USERS_QUERY}>
    {({ loading, error, data }) => {
          if (loading) return <div>Fetching</div>
          if (error) return <div>Error</div>   
          const usersToRender = data.users
          return (
            <div>
              {usersToRender.map(user=> <User key={user.id} info={user} />)}
            </div>
          )
     }}
  </Query>
)

Apollo는 컴포넌트의 render prop 함수에 네트워크 요청 상태 정보를 제공하는 props를 전달합니다.

  • loading : 요청이 아직 진행 중이고 응답이 수신되지 않는 한 true입니다.
  • error : 요청이 실패한 경우이 필드에는 정확히 무엇이 잘못되었는지에 대한 정보가 포함됩니다.
  • data : 서버에서 받은 실제 데이터입니다.

Hooks로 React Apollo로 Query 보내기

useQuery 훅은 Apollo 앱에서 쿼리를 실행하며, useQuery에 GraphQl 쿼리 문자열을 전달하여 호출합니다.

컴포넌트가 렌더링되면 useQuery는 UI 렌더링시 사용할 수 있는 {load, error, data}속성들이 포함된 Apollo Client의 객체를 반환합니다.

→ useQuery훅 사용 예시

import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';
const USERS_QUERY = gql`
  {
    users{
       id
       name
       status
    }
  }
`
function Users() {
  const { loading, error, data } = useQuery(USERS_QUERY);
  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;
  return (
    {data.users.map(user=> <User key={user.id} info={user} />)}
  );
}

(공식문서에서 useQuery API에 대한 보다 자세한 내용 확인하기 )

🔹 Mutation

Mutation은 애플리케이션의 verbs(직역: 동사 / 의역 : 애플리케이션이 수행할 일들)을 나타내며, 사용자가 서버로 수행할 있는 작업으로 구성됩니다.

GraphQL 뮤테이션은 REST의 PUT, POST, DELETE 요청과 유사하게 객체를 만들고 수정합니다. 뮤테이션은 요청은 쿼리 요청과 동일한 엔드 포인트로 전송됩니다.

const USER_MUTATION = gql`
  mutation UserMutation($name: String!, $status: String!) {
    user(name: $name, status: $status) {
      id
      createdAt
      name
      status
    }
  }
`

React Apollo Mutation 사용하기 (no Hook)

  1. Apollo Client에서 직접 mutate 메서드 를 사용합니다. 데이터를 변경하는 매우 직접적인 방법이며 응답을 promise로 처리 할 수 있습니다.
client.mutate({
  variables: { name: "John", status: "primary" },
  mutation: gql`
    mutation UserMutation($name: String!, $status: String!){
      user(name: $name, status: $status) {
        id
        createdAt
        name
        status
      }
    }
  `,
})
.then(result => { console.log(result) })
.catch(error => { console.log(error) });
  1. Render Prop API 사용하기

첫번째로 아래 디펜던시들을 import해줍니다.

import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'

둘째, gql parser 함수를 사용하여 쿼리를 JavaScript const로 작성합니다.

const USER_MUTATION = gql`
  mutation UserMutation($name: String!, $status: String!) {
    user(name: $name, status: $status) {
      id
      createdAt
      name
      status
    }
  }
`

셋째, GraphQL 뮤테이션 및 변수들(옵션)을 prop으로 전달하는 Mutation 컴포넌트를 사용하고, prop으로 USER_MUTATION을 전달하는 Mutation 컴포넌트를 사용하여 submit button을 감싸서 name, status 상태를 아래와 같이 전달합니다.

다음 예제는 두 개의 input 필드가 있는 React 컴포넌트의 일반적인 구조입니다. form을 제출하고 GraphQl 서버로 데이터를 전송하는 방법 (뮤테이션이 전송 될 때)에 대해 개괄적으로 이해할 수 있는 예제입니다.

import React, { Component } from 'react'
import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'
const USER_MUTATION = gql`
  mutation UserMutation($name: String!, $status: String!) {
    user(name: $name, status: $status) {
      id
      createdAt
      name
      status
    }
  }
`
class CreateUser extends Component {
  state = {
    name: '',
    status: '',
  }
  render() {
    const { name, status } = this.state
    return (
      <div>
        <div className="flex flex-column mt3">
          <input
            className="mb2"
            value={name}
            onChange={e => this.setState({ name: e.target.value })}
            type="text"
            placeholder="User Name"
          />
          <input
            className="mb2"
            value={status}
            onChange={e => this.setState({ status: e.target.value })}
            type="text"
            placeholder="State"
          />
        </div>
        <Mutation mutation={USER_MUTATION} variables={{ name, state }}>
          {UserMutation => <button onClick={UserMutation}>Submit</button>}
        </Mutation>
      </div>
    )
  }
}
export default CreateLink

Hooks로 React Apollo Mutation 사용하기

useMutation 훅은 Apollo 앱에서 뮤테이션을 실행하며, useMutation에 뮤테이션을 나타내는 GraphQl 문자열을 전달하여 호출합니다. 컴포넌트가 렌더링 될 때 useMutation은 다음을 포함하는 튜플을 반환합니다.

"뮤테이션을 실행하고, 현재 뮤테이션의 실행 상태를 나타내는 정보를 담은 객체를 호출할 수 있는 mutate 함수"

useMutation 사용 예시

useMutation 훅은 컴포넌트가 렌더링 될 때 전달한 뮤테이션을 자동으로 실행하지 않습니다. 대신, mutate 함수가 들어있는 튜플을 반환합니다.

그리고 mutate 함수를 호출해서 Apollo Client에 뮤테이션을 실행하도록 하면 됩니다. 아래 예시에서는 사용자가 form을 제출할 때 UserMutation을 호출합니다.

import React, { useState } from 'react'
import gql from 'graphql-tag';
import { useMutation } from '@apollo/react-hooks';
const USER_MUTATION = gql`
  mutation UserMutation($name: String!, $status: String!) {
    user(name: $name, status: $status) {
      id
      createdAt
      name
      status
    }
  }
`
const CreateUser = () =>{
  const [UserMutation, { data }] = useMutation(USER_MUTATION);
  const [state, setState] = useState({
        name: '',
        status: ''
  });
    return (
       <div>
        <div className="flex flex-column mt3">
          <input
            className="mb2"
            value={state.name}
            onChange={e => setState({...state, name: e.target.value }) }
            type="text"
            placeholder="User Name"
          />
          <input
            className="mb2"
            value={state.status}
            onChange={e => setState({...state, status: e.target.value }) }
            type="text"
            placeholder="Status"
          />
        </div>
       <button onClick={e => {
          e.preventDefault();
          UserMutation({ variables: { name: state.name, status: state.status } });
        }}>Submit</button>}
      </div>
     );
}
export default CreateLink

(+공식문서에서 useMutation API에 대한 자세한 내용 확인하기)

결론

GraphQL은 오픈 소스 API 생태계에서 상대적으로 새롭게 등장한 흥미로운 기술입니다. GraphQL로 인해 이미 기업들은 클라이언트 & API 애플리케이션 구축 방법에 대한 접근을 바꾸기 시작했습니다. 본 기사에서는 GraphQl이 무엇인지, GraphQl이 필요한 이유, 그리고 GraphQl Client 및 React와 Apollo Client를 함께 사용할 수 있는 방법을 알아보았습니다.

React 및 Node.js로 실제 웹 앱을 빌드하여 GraphQL을 익히고 싶다면 다음 과정을 추천합니다.

👉 GraphQL with React: The Complete Developers Guide

Node.js(with Prisma v1, 인증, Apollo Client)를 사용하여 GraphQL 애플리케이션을 만드는 방법에 대해 자세히 알아보고 싶다면 다음 과정이 가장 적합합니다.

👉 The Modern GraphQL Bootcamp (with Node.js and Apollo)

팬데믹으로 인해 집에 머무르는 시간동안 지식을 넓히거나 커리어 패스를 바꾸는 기회로 삼아보길 바랍니다. 🙏

profile
프론트엔드 개발자. 기록하기, 요가, 등산

0개의 댓글