ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [GraphQL] GraphQL 시작하기 (1)
    프론트엔드 2020. 12. 12. 01:24
    728x90

    이번 포스트는 freeCodeCamp의 GraphQL 코스를 진행하면서 정리한 내용으로 GrapghQL 서버를 구축해보겠습니다.

    GrapghQL을 처음 접하시는 분들은 많은 도움이 되길 바랍니다.

     

     

    패키지 설치

     저는 몽고 디비도 함께 활용했기에 몽구스도 설치했습니다.

    $ npm i -D apollo-server graphql mongoose

     

    서버 코드 작성

     ApolloServer는 GraphQL 서버 인스턴스를 생성합니다. 데이터를 정의하고 Fetch 된 데이터를 불러오는 역할을 합니다.

     typeDefs에서 GraphQL 스키마를 정의하며, resolvers는 데이터를 어떻게 활용할지를 쿼리를 정의합니다.

     PageSub는 뒤에서 subscription이 나올때 함께 이야기하겠습니다.

     몽고 디비에 연결이 된 다음 ApolloServer를 실행합니다. 포트는 5000번으로 설정했습니다.

    [index.js]
    
    const { ApolloServer, PubSub } = require('apollo-server');
    const mongoose = require('mongoose');
    
    const { MONGODB } = require('./config.js');
    const resolvers = require('./graphql/resolvers/index');
    const typeDefs = require('./graphql/typeDefs');
    
    const pubsub = new PubSub();
    
    const server = new ApolloServer({
        typeDefs,
        resolvers,
        context: ({ req }) =>({ req, pubsub })
    });
    
    mongoose.connect(MONGODB, {useNewUrlParser: true, useUnifiedTopology: true})
    .then(() => {
        console.log("MongoDB Connected");
        return server.listen({port : 5000});
    })
    .then((res) => {
        console.log(`Server running at ${res.url}`);
    });

     

    GrapghQL 스키마 정의 (typeDefs)

      gql은 자바스크립트로 GraphQL 스키마를 정의하기 위해 사용되는 템플릿 리터럴 태그입니다.

      스키마를 통해 백엔드 데이터 저장소 데이터의 계층구조를 정의합니다.

      -  스키마 정의 언어 (SDL : schema definition language)

          GrapghQL은 사람이 쉽게 이해할 수 있는 SDL을 포함합니다. 아래와 같이 Post, Comment 등을 정의하면 됩니다.

      -  스키마 타입

          Scalar : 기본 프로그래밍 언어의 기본 타입들과 유사합니다. (ex: Int, Float, String, Boolean, ID)

                       구체적인 사용을 위해 사용자 지정 스칼라 유형도 생성 가능합니다.

          Object : 아래 정의한 유형이 대부분 Object입니다. (ex: Post, Comment)

          Query : 클라이언트에서 GrapghQL의 데이터를 요청하는 시작점입니다.

                       선언된 이름과 타입으로 정의되며, 정의된 데이터가 클라이언트로 반환됩니다.

          Mutation : Query와 구조와 목적이 유사합니다. Query는 단지 읽기 작업, Mutation은 쓰기 작업의 진입점을 정의합니다.

          Input : Query와 Mutation에서 사용할 수 있는 객체 타입입니다.

    const { gql } = require('apollo-server');
    
    module.exports = gql`
        type Post {
            id: ID!
            body: String!
            createdAt: String!
            username: String!
            comments: [Comment]!
            likes: [Like]!
            likeCount: Int!
            commentCount: Int!
        }
        type Like {
            id: ID!
            createdAt: String!
            username: String!
        }
    
        input RegisterInput{
            username: String!
            password: String!
            confirmPassword: String!
            email: String!
        }
        
        type Query{
            getPosts: [Post]
            getPost(postId: ID!): Post
        }
        
        type Mutation{
            register(registerInput: RegisterInput) : User!
            login(username: String!, password: String!) : User!
            createPost(body: String!): Post!
            deletePost(postId: ID!): String!
            createComment(postId: String!, body: String!): Post!
            deleteComment(postId: ID!, commentId: ID!): Post!
            likePost(postId: ID!): Post!
        }
        
        type Subscription {
            newPost: Post!
        }
    `;

     

     GrapghQL Resolver 정의

      Resolver는 사용자가 지정한 스키마에 대하여 데이터를 채우는 역할을 합니다.

    const { AuthenticationError, UserInputError } = require('apollo-server');
    
    const Post = require('../../models/Post');
    const checkAuth = require('../../util/check-auth');
    
    module.exports = {
      Query: {
        async getPosts() {
          try {
            const posts = await Post.find().sort({ createdAt: -1 });
            return posts;
          } catch (err) {
            throw new Error(err);
          }
        }
      },
      Mutation: {
        async createPost(_, { body }, context) {
          const user = checkAuth(context);
    
          if (body.trim() === '') {
            throw new Error('Post body must not be empty');
          }
    
          const newPost = new Post({
            body,
            user: user.id,
            username: user.username,
            createdAt: new Date().toISOString()
          });
    
          const post = await newPost.save();
    
          context.pubsub.publish('NEW_POST', {
            newPost: post
          });
    
          return post;
        }
      },
      Subscription: {
        newPost: {
          subscribe: (_, __, { pubsub }) => pubsub.asyncIterator('NEW_POST')
        }
      }
    };

     

    ✔ Subscription

      실시간 애플리케이션 구현을 위해 사용되는 GraphQL의 operation입니다.

      같은 opertaion인 query와 mutation이 서버/클라이언트 모델로 구성된 것과 달리 subscription은 발행/구독 모델 구성입니다.

      

      서버/클라이언트 모델에서 시스템에 이벤트가 동시다발적으로 발생하는 경우,

       -> 클라이언트에서 데이터를 계속적으로 호출한다 하더라도 실시간성 보장엔 어려움이 있습니다

      

      발행/구독 모델에서 시스템에 이벤트가 동시다발적으로 발생하는 경우,

       -> 위의 모델이 HTTP 프로토콜을 사용하는 것과 달리 해당 모델은 웹소켓 프로토콜을 사용합니다.

            웹소켓 프로토콜을 통해 클라이언트와 서버를 연결한 채 유지하며, 서버의 이벤트를 실시간으로 수신받을 수 있습니다.

            위에서 설명 안 하고 넘어갔던 PubSub이 Apollo Server의 자체 발행/구독 엔진입니다.

     

      Subscription 구현

      -> PubSub 객체를 임포트 및 typeDefs에 Subscription 타입을 정의합니다.

           마지막으로 앞서 정의한 Subscription의 동작을 Resolver에서 추가합니다.

           pubsub 객체의 asyncIterator()를 호출하고 'NEW_POST'라는 이벤트를 등록하면, 해당 이벤트가 발생할 때마다 동작하게 됩니다.

     

    ✔ 서버 구동

      index.js 파일에 5000번 포트에서 서버가 구동되도록 해두었으니 실행시키면 됩니다.

      5000번 포트에 접속하면 아래와 같이 웹 기반 툴을 확일할 수 있습니다. 직접 쿼리를 날려 잘 동작하는지 확인해보시면 됩니다.

    $ node index.js
    Listening at http://localhost:5000/

     

    ✔ 마무리

      GraphQL를 시작하면서 먼저 간단하게나마 ApolloServer를 구현하고 실행시켜 보았습니다.

      아직 다루지 못한 내용들이 많은데 추후에 계속 포스트를 업데이트하도록 하겠습니다.

Designed by Tistory.