ABOUT ME

-

오늘
-
어제
-
-
  • [GraphQL] Apollo-Server를 이용하여 프로젝트 시작해보기
    Back-end/GraphQL 2021. 1. 24. 03:47

    GraphQL

    GraphQL(GQL)은 갓 페이스북에서 만들어낸 쿼리 언어(Query Language) 입니다. 백엔드 개발을 알고 계시다면 SQL(Structed Query Language)에 대해서도 알고 계실텐데요, 왠지 단어도 비슷하고 해서 두 언어가 동일하다고 느낄 수 있지만 전혀 그렇지 않습니다.

    먼저 기존에 주로 사용하던 SQL의 경우에는 DB에 존재하는 데이터를 효율적으로 가져오는 것에 중점을 두었고

    GQL의 경우에는 클라이언트에서 데이터를 서버로부터 효율적으로 가져오는 것이 목적입니다.

     

    그래서 이전에는 주로 백엔드에서 쿼리를 작성해서 클라이언트로 데이터를 전달해주는 역할을 했다면, GQL을 사용하게 되면 클라이언트에서 직접 필요로 하는 데이터를 호출하고, 사용하게 됩니다.

    즉, 프론트의 역할이 이전보다 더욱 커진다는 것이죠

     

    그리고 또하나의 엄청난 특징이 있는데, 바로 그 것은 Endpoint가 하나라는 것입니다.

    기존의 API의 경우에는 프로젝트 규모가 커질수록 동일하게 API가 많아져 정말 Endpoint를 관리하기 쉽지 않았습니다.

    하지만 GQL은 Endpoint를 하나로 두어 필요한 데이터만 쏙쏙 골라서 가져올 수 있습니다.

     

    이러한 장점들 때문에 최근 많은 기업들이 GraphQL을 이용하여 서비스를 운영하고 있습니다.

    Apollo

    Apollo는 Javascript 진영의 GraphQL 관련 오픈소스입니다. Apollo를 사용하여 자바스크립트로 GQL을 사용하는 것이죠

    Server 뿐만 아니라 Client에서도 동일하게 Apollo를 사용하여 개발하게 됩니다.

     

     

    ❗️ 이 글은 개인적으로 학습한 정보와 지식, 예제에서는 Apollo Docs를 살펴보며 작성된 글입니다. 혹시 잘못된 부분이 있거나 수정사항이 있다면 말씀해주시면 반영하겠습니다. 🙏

     

    프로젝트 셋업

    디렉토리 생성

    mkdir graphql-server-example
    cd graphql-server-example

    디렉토리를 생성하고 이동해줍니다.

    npm init --yes

    package.json을 생성해줍니다.

    의존성 설치

    npm install apollo-server graphql

    apollo-server와 graphql 두가지를 설치해줍니다.

    • apollo-server는 Apollo 라이브러리의 코어한 부분이며, 데이터를 정의하고 다루는데에 사용됩니다.
    • graphql은 GraphQL의 스키마와 쿼리에 사용됩니다.
    touch index.js

    index.js 파일을 생성하고 앞으로 이 파일 안에서 작업을 할 것입니다.

    여기까지 생성시 프로젝트의 상황은 아래와 같습니다.

     

    Schema 생성

    모든 GraphQL 서버는 Client에서 쿼리를 통해 데이터를 요청할 수 있도록 스키마를 정의합니다.

    const { ApolloServer, gql } = require('apollo-server');
    
    const typeDefs = gql`
      type Book {
        title: String
        author: String
      }
    
      type Query {
        books: [Book]
      }
    `;
    

    스키마는 apollo-server의 gql를 활용하여 정의합니다.

    스키마를 정의 할 때는 type을 사용하여 정의하게 되는데요, Typescript 처럼 정의할 때 해당 필드에 대한 자료형을 함께 선언해야합니다.

    Book은 title과 author에 문자열인 String을 선언해주었으며, Query로는 Book의 배열 형태로 선언해준 상태입니다.

     

    스키마의 종류에는 데이터 베이스 구조Query, Muatation이 있습니다.

    먼저, 데이터 베이스의 구조를 정의하는 이유는 해당 데이터 베이스를 GraphQL이 인지해서 쿼리를 처리할 수 있게끔 나타내주어야 하기 때문입니다. 

    또한 Query와 Mutation이 있는데 현재 자세하게 다룰 내용은 아니니 간단한 역할만 알아보겠습니다.

    Query

    데이터 베이스에서 데이터를 읽는 요청을 하며 HTTP Method로 따지면 GET의 역할을 수행합니다.

    Mutation

    데이터 베이스에서 POST, PUT, DELETE 등 Query와는 반대의 역할을 수행합니다.

    임시 데이터 생성하기

    const { ApolloServer, gql } = require('apollo-server');
    
    const typeDefs = gql`
      type Book {
        title: String
        author: String
      }
    
      type Query {
        books: [Book]
      }
    `;
    
    const books = [
      {
        title: 'The Awakening',
        author: 'Kate Chopin',
      },
      {
        title: 'City of Glass',
        author: 'Paul Auster',
      },
    ];
    

    정식 데이터 베이스가 세팅이 되어있지 않기 때문에 만든 스키마(Book)에 맞는 임시 데이터를 선언해줍니다.

    Resolver 구현

    Resolver는 Client에서 쿼리를 요청 했을 때 서버에서 해당 쿼리를 가지고 어떤 로직을 실행할지에 대한 부분입니다.

    현재는 Query에 대한 부분만 작성 되어 있으므로 한 번 구현해보겠습니다.

    const { ApolloServer, gql } = require('apollo-server');
    
    const typeDefs = gql`
      type Book {
        title: String
        author: String
      }
    
      type Query {
        books: [Book]
      }
    `;
    
    const books = [
      {
        title: 'The Awakening',
        author: 'Kate Chopin',
      },
      {
        title: 'City of Glass',
        author: 'Paul Auster',
      },
    ];
    
    const resolvers = {
      Query: {
        books: () => books,
      },
    };
    

    resolver 라는 변수명을 가진 객체에 Query를 선언하고 books라는 쿼리를 호출 했을 때, 현재 임시 데이터인 books를 반환해준다는 로직의 resolver를 만들었습니다.

    서버 구현

    서버는 ApolloServer를 활용해서 기존에 사용하던 express처럼 구현이 가능합니다. ApolloServer도 express를 기반으로 하고 있기 때문에 express에서 사용하는 함수들을 동일하게 사용할 수도 있습니다.

    const { ApolloServer, gql } = require('apollo-server');
    
    const typeDefs = gql`
      type Book {
        title: String
        author: String
      }
    
      type Query {
        books: [Book]
      }
    `;
    
    const books = [
      {
        title: 'The Awakening',
        author: 'Kate Chopin',
      },
      {
        title: 'City of Glass',
        author: 'Paul Auster',
      },
    ];
    
    const resolvers = {
      Query: {
        books: () => books,
      },
    };
    
    const server = new ApolloServer({ typeDefs, resolvers });
    
    server.listen().then(({ url }) => {
      console.log(`🚀  Server ready at ${url}`);
    });
    

    new 연산자를 통해 서버를 생성하고, 객체 타입으로 정의한 schema와 resolver를 넣어줍니다.

    그리고 listen 함수를 통해 서버를 실행시키면, 실행시킨 url이 console에 뜨도록 합니다.

    서버 실행

    node index.js // 🚀  Server ready at http://localhost:4000/

    서버를 실행시키면 기본 포트 4000번을 활용하여 실행된 URL이 출력됩니다.

    출력된 URL을 실행시키면 아래와 같은 화면이 뜹니다.

     

    이 URL은 GraphQL에서 제공해주는 Playground이며, 여기서 개발자가 작성한 schema를 전부 테스트할 수 있고, 심지어 자동 문서화가 되어 우측에 DOCS를 통해 한 번에 필요한 상세정보들을 전부 확인할 수 있습니다.

    쿼리 요청해보기

    우리는 Query라는 스키마를 통해 books를 Book이 배열인 타입으로 선언하였었습니다.

    그리고 임시 데이터를 만든 후 Resolver를 만들어 books를 요청시 임시로 만든 books를 리턴하는 로직을 작성했었죠

    이제 Playground에서 아래의 쿼리를 직접 요청해보겠습니다.

    {
      books {
        title
        author
      }
    }

    books라는 요청을 하였고, title과 author의 정보를 달라는 요청입니다. 

    요청하면 아래와 같은 결과를 받아냅니다.

     

    여기서 중요한 점이 있습니다.

    바로 GraphQL의 장점인 부분인데요, Client가 필요한 데이터만 가져올 수 있다는 것입니다.

     

    기존 API들은 Client와의 의지와는 상관없이 해당 Endpoint에 대한 데이터들을 필요 있던 없던 꼭 다 가져와서 프론트에서 가공을 해야하는 번거로움이 존재했었습니다. 이런 데이터들이 많아지면 응답의 무게가 커지게 되어 페이지가 느려질 가능성이 있겠죠 😱

     

    하지만 GraphQL을 사용하면 그럴 걱정이 없습니다😀  그냥 해당 데이터에 대한 쿼리를 날리지 않으면 가져오질 않습니다.

    한 번 볼까요?

     

    단지 author를 제외했을 뿐인데 반환된 데이터는 Client가 원하는 부분만 가져왔습니다.

     

     

    간단하게 Apollo를 통한 GraphQL 서버를 구현해보았습니다. 더욱 상세한 내용은 Docs에 있으니 더 많은 개념이 필요하시면 참고하시면 좋을 것 같습니다.

    댓글