본문 바로가기

개발바닥/GraphQL

[개발] GraphQL - Apollo 웹 서버 구축하기

안녕하세요. devport 입니다. 이번 포스팅에서는 NodeJS로 간단한 Apollo 서버를 구축하고 GraphQL Playground로 Query 및 Mutation이 어떻게 동작되는지 자세하게 훑어보려고합니다. 예제는 처음 공식 홈페이지의 서버 구축 예제로 시작하도록 하겠습니다.

Npm 설치

npm install --save apollo-server-express express

Apollo 라이브러리를 웹서버에 올려 사용할 수있는 환경을 구축하기 위해서 관련 라이브러리를 프로젝트에 설치합니다.

server.js

프로젝트 하위에 server.js파일을 생성하여 아래 코드를 삽입하여 줍니다.

const express = require('express');
const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => 'Hello world!',
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
server.applyMiddleware({ app });

app.listen({port: 4000}, () => {
	console.log('Now browse to http://localhost:4000' + server.graphqlPath)
})

express 모듈은 웹서버를 모듈이며 apollo-server모듈은 /graphql 경로의 진입부를 마운트할 수 있도록 하였습니다.

node server.js

위 커맨드를 쉘에서 실행하게 되면 Express 웹서버가 실행됩니다.

브라우저에서 http://localhost:4000/graphql 경로로 접근하게 되면 아래와 같은 Graphql Playground 화면을 볼 수 있게됩니다.

 

이 화면에서 Docs에서는 Query, Mutation에 목록을 확인할 수 있으며 Schema에서는 전체적인 구조를 확인할 수 있습니다. 또한 Query(검색) 및 Mutation(추가,수정,삭제)을 Playground에 작성하고 실행시킨다면 결과값을 바로 확인할 수 있다는 이점을 가지고 있습니다.

Query 및 Mutation을 해보자

이제 간단하게 Query 및 Mutation을 해보기 위해서 스키마를 재정의 해보도록 하겠습니다.

const typeDef = gql`
  type Author {
    id: ID
    name: String
    books: [Book]
  }
  type Book {
    id: ID
    title: String
    author: String
  }
  type Query {
    allAuthor: [Author]
    allBook: [Book]
  }
  type Mutation {
    createAuthor(name: String!): Author
    createBook(title: String!, author: String!): Book
    deleteAuthor(id: ID!): Author
    deleteBook(id: ID!): Book
  }
`

스키마에 Author, Book type을 선언 하였습니다.

Author 타입은 저자의 객체를 담을 수 있는 구조로써 이름과 책들의 정보를 가져올 수 있는 구조입니다.

Book 타입은 책의 객체를 담을 수 있는 구조로써 책제목, 저자의 이름 정보를 가져올 수 있는 구조입니다.

 

Query에서

allAuthor는 저자들의 목록을 반환 받을 수 있습니다.

allBook은 책들의 목록을 반환 받을 수 있습니다. 

 

Mutation에서

createAuthor는 저자를 추가하는 변이로써 저자의 이름을 필수적으로 인자를 삽입해야 합니다.

createBook은 책을 추가하는 변이로써 책의 이름과 저자이름을 필수적으로 인자를 삽입해야 합니다.

deleteAuthor는 특정 저자를 제거 할 수 있으며 대상의 id를 필수적으로 삽입해야 합니다.

deleteBook은 특정 책을 제거할 수 있으며 대상의 id를 필수적으로 삽입해야 합니다.

 

위 스키마에서 저자와 책에 대하여 선언을 하였으며 검색, 추가, 삭제에 관련된 Query 및 Mutation들을 선언하였습니다. 선언된 내용들에 대해서 아래와 같이 구현을 하였습니다.

let authorList = []; // 저자의 목록
let bookList = []; // 책의 목록
let authorCnt = 0, bookCnt = 0; // 저자,책의 id 순번
let resolvers  = {
    Query: {
        // 저자 목록 검색
        allAuthor: () => authorList,
        // 책 목록 검색
        allBook: () => bookList,
        findAuthor: ({id}) => authorList.find((author) => {author.id == id}),
        findBook: ({id}) => bookList.find((book) => {book.id == id})
    },
    Mutation: {
        // 저자 추가
        createAuthor (parent, {name}) {
            const author = {id: authorCnt++, name}
            authorList.push(author);
            return author;
        },
        // 책 추가
        createBook (parent, {title, author}) {
            const book = {id: bookCnt++, title, author};
            bookList.push(book);
            return book;
        },
        // 저자 삭제
        deleteAuthor (parent, {id}) {
            const idx = authorList.findIndex((author) => author.id == id);
            let author = {};
            if (idx>=0) {
                author = authorList[idx];
                authorList.splice(idx, 1);
            }
            return author;
        },
        // 책 삭제
        deleteBook (parent, {id}) {
            const idx = bookList.findIndex((book) => book.id == id);
            let book = {};
            if (idx>=0) {
                book = bookList[idx];
                bookList.splice(idx, 1);
            }
            return book;
        }
    },
    // 연쇄 리졸버
    Author: {
        // 저자의 books 정보를 검색
        books (parent) {
            const list = []
            bookList.forEach((book) => {
                if (book.author === parent.name) list.push(book);
            })
            return list;
        }
    }
};

authorList(저자의 목록), bookList(책의 목록)에 대한 검색, 추가, 삭제를 구현한 리졸버입니다.

위 예제에서는 간단히 배열을 사용하고 있지만 구현된 위치에 DB를 사용하고 CRUD 로직만 변경해준다면 웹서버 + 데이터베이스로 GraphQL을 서비스할 수 있는 구조로 탈바꿈하게 됩니다.

 

GraphQL Playground에서 간단하게 저자 및 책을 추가하고 검색을 해보도록 하겠습니다.

 

 

Tom이라는 저자를 추가
Hello Wold! 책을 추가
Do it! Language! 책을 추가
모든 저자 검색

"모든 저자 검색"을 하였을때에 books는 Author.books 리졸버를 호출하여 저자명과 일치하는 책을 반환하게 되며 의존 정보를 쉽게 검색할 수 있는 것을 알 수 있습니다. 이러한 기법을 연쇄 리졸버라고 하여 연관데이터를 쉽게 구할 수 있다는 것을 알 수 있었습니다.