
안녕하세요. 일루넥스 개발팀 박두현입니다.
이번 글의 내용은 최근 진행한 충북 테크노파크 프로젝트에서 적용했던 React, Apollo, GraphQL에 대한 정리입니다.
1. Apollo
React에서 GraphQL API를 호출하기 위해 Apollo Client가 제공하는 Apollo Hooks를 사용했습니다.
1.1 Apollo Hooks?
기존 react-apollo 패키지에서 HOC(Higher Order Components)를 사용한 나을 통해 GraphQL API를 호출하는 방법에서
React Hooks를 도입하여 useQuery, useMutation과 같은 함수로 GraphQL API를 호출하는 새로운 방법
- @apollo/react-hooks 패키지를 사용하며, 클래스 대신 함수형 컴포넌트를 사용하게 되어 코드의 가독성과 유지보수에 대한 이점이 있습니다.
1.2 Apollo Client 사용하기
1.2.1 패키지 설치
React 프로젝트에 Apollo Client와 GraphQL 패키지들을 설치합니다.
$ npm install @apollo/react-hooks apollo-cache-inmemory apollo-client apollo-link-http graphql graphql-tag
1.2.2 Apollo Client 생성
apollo-client 패키지의 ApolloClient 생성자를 통해 Apollo Client를 생성합니다.
- link : GraphQL API의 endpoint HttpLink 객체
import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
const client = new ApolloClient({
link: createHttpLink({ uri: "GraphQL API URI" }),
cache: new InMemoryCache()
});
1.2.3 연결
ApolloClient를 React에 연결합니다.
모든 컴포넌트에서 GraphQL API 호출을 하기 위해 최상단의 App 컴포넌트를 ApolloProvider로 wrapping 해주었습니다.
import React from 'react';
import App from './App';
import client from "./apollo";
import {ApolloProvider} from "@apollo/react-hooks";
ReactDOM.render(
<ApolloProvider client={client}>
<App/>
</ApolloProvider>
, document.getElementById('root')
);
1.2.4 GraphQL API 호출
graphql-tag 패키지의 gql을 사용하여 자바스크립트 문자열을 GraphQL 구문으로 변환한 뒤,
@apollo/react-hooks의 useQuery 함수에 변환된 쿼리를 인자로 넘겨서 GraphQL API를 호출합니다.
- useQuery 함수는 기본적으로 비동기함수며 data, loading, error를 리턴합니다.
useMutation, useSubscription도 동일하며, 이를 이용하여 쉽게 예외처리를 할 수 있습니다.
→ https://www.apollographql.com/docs/react/data/queries/ - @apollo/react-hooks의 함수형 컴포넌트 방식
import React from 'react'
import gql from 'graphql-tag';
import { userQuery } from '@apollo/react-hooks';
const GET_USER = gql`
query user($idx:Int!){
user(idx: $idx) {
idx
name
email
}
}
`;
function User() {
const { loading, error, data } = useQuery(GET_USER, {variables: {idx: id}});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!(</p>;
return (
<p>{data.name}, {data.email}</p>
);
}
export default User;
- 기존 react-apollo 패키지의 HOC 방식
import React from "react";
import { gql } from "apollo-boost";
import { Query } from "react-apollo";
const GET_USER = gql`
query user($idx:Int!){
user(idx: $idx) {
idx
name
email
}
}
`;
function User() {
return (
<>
<Query query={GET_USER} variables={{idx: id}}>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!(</p>;
return (
<p>{data.name}, {data.email}</p>
);
}}
</Query>
</>
);
}
export default User;
2. GraphQL
2.1 REST API와 GraphQL API의 차이
기존 react-apollo 패키지에서 HOC(Higher Order Components)를 사용한 나을 통해 GraphQL API를 호출하는 방법에서
React Hooks를 도입하여 useQuery, useMutation과 같은 함수로 GraphQL API를 호출하는 새로운 방법
- REST API를 사용할 때에는 특정 엔드포인트를 통하여 데이터를 불러오게 됩니다. 클라이언트의 요구 사항은 접근하는 해당 API를 호출하는 URL에 작성됩니다.
- GraphQL은 단 하나의 엔드포인트만을 노출시킵니다. 필요한 데이터를 서버에 알려주기 위하여 클라이언트가 보다 많은 정보를 보내야 합니다.
따라서 클라이언트 측에서 필요한 데이터를 결정할 수 있습니다. 여기서 클라이언트가 보내는 정보를 쿼리라고 부릅니다.
2.2 문법
GraphQL의 문법은 Query, Mutation, Subscription의 세 가지 종류가 있습니다.
GraphQL에 대한 자세한 내용은 https://graphql.org/ 에서 확인할 수 있습니다.
- Query : 데이터를 받아올 때 사용 → REST의 “GET” 역할
- Mutation : 데이터를 생성, 수정, 삭제할 때 사용 → REST의 “POST, PUT, PATCH, DELETE” 역할
- Subscription : 웹소켓을 사용해 실시간 양방향 통신을 구현할 때 사용
2.3 GraphQL 사용하기(Server)
2.3.1 GraphQL 스키마 작성
GraphQL에서 사용할 스키마를 작성합니다.
- 스키마(Schema)는 GraphQL API가 할 수 있는 행위를 정해주고, 클라이언트가 데이터를 요청하는 방법을 정의합니다.
- 대부분의 경우 스키마는 GraphQL 타입들의 집합입니다. 하지만 API를 위한 스키마를 작성할 때에는, 아래와 같은 특별한 루트 타입이 존재합니다.
type Query { ... }
type Mutation { ... }
type Subscription { ... }
Query, Mutation, Subscription 타입은 클라이언트가 보내는 요청을 위한 진입점입니다.
- The Schema Definition Language (SDL)
GraphQL은 API의 스키마를 정의하기 위한 고유의 타입 시스템을 갖추고 있습니다.
스키마를 작성하는 문법을 스키마 정의 언어(SDL)이라고 합니다. - GraphQL은 반드시 JSON으로만 요청과 응답을 실행합니다.
요청한 쿼리 JSON과 동일한 형태의 JSON으로 응답합니다.
useQuery를 사용할 Query 타입과, 주고 받을 사용자 정의 타입(JSON)을 정의합니다. - Type.js
const typeDefs = `
type User {
idx: Int
name: String
email: String
}
type Query {
user(idx: Int!): User
}
`
module.exports = { typeDefs };
2.3.2 GraphQL 서버 환경 설정 & Resolver 함수 작성
- index.js
GraphQL 서버를 설정합니다. 여기서는 graphql-yoga라는 패키지를 사용했는데,
graphql-yoga는 GraphQL 서버를 쉽게 실행시켜주는 도구 입니다.
const { GraphQLServer } = require('graphql-yoga');
const { UserResolvers } = require("./resolvers/UserResolvers");
const { typeDefs } = require("./Type");
const resolvers = {
Query: {
user: UserResolvers.user,
}
};
const server = new GraphQLServer({
typeDefs,
resolvers
});
server.start(options,() => console.log(`Server address: http://localhost:4000`));
- graphql-yoga를 통해 서버를 띄운 뒤, localhost:4000으로 접속하면
GraphQL Playground라는 GraphQL IDE를 사용할 수 있습니다.
(GraphQL Subscriptions, interactive docs & collaboration) - UserResolver.js
실제 GraphQL API 호출 시 처리할 서비스 로직을 작성합니다.
여기서는 Sequelize ORM을 통해 User를 찾는 서비스를 작성했습니다.
자세한 내용은 → https://github.com/prisma-labs/graphql-playground
const { User } = require("../db");
const UserResolvers = {
user: async(_, args) => {
const idx = args.idx;
const user = User.findByPk(idx);
return user;
}
};
module.exports = {
UserResolvers
};
2.3.3 GraphQL API 호출
GraphQL의 엔드포인트로 쿼리를 전송합니다. (위에서는 localhost:4000)
- Client에서 useQuery, useMutation을 통한 호출
- GraphQL 서버의 PlayGround IDE에서 호출 Test
- 지금까지의 프로젝트 구조를 정리하면 다음과 같습니다.
※ GraphQL 서버에 DB를 연결할 때 ORM은 Sequelize를 사용하였지만, Prisma도 많이 사용되고 있다고 합니다.
→ https://www.prisma.io/

마치며
이번 프로젝트를 통해 REST API 대신 GraphQL 방식을 처음 사용해봤는데, REST API를 사용했을 때 보다 쉽고 간편했던 것 같습니다.
클라이언트에서 필요한 데이터를 직접 정해서 서버로 호출하기 때문에 직관적이고, 필요한 데이터만 호출할 수 있어서 효율적이었습니다.
엔드포인트가 단 하나라는 점에서 REST API에서 작성했던 URL 패턴을 고민할 필요가 없었고,
호출된 쿼리에 따라 Resolver가 실행되므로 서버 라우팅을 따로 하지 않아도 돼서 작업 시간도 줄일 수 있었습니다.
결과적으로 REST API와 GraphQL 모두 장단점이 있기때문에 어느 방법이 더 좋다고는 할 수 없지만,
개발할 프로젝트의 성격에 따라 골라서 사용할 수 있는 선택지가 생겨서 좋은 것 같습니다.