1. REST API의 한계
- REST API는 URL과 HTTP Method(get, post, ...)를 조합해 사용하기 때문에 다양한 Endpoint가 존재합니다.(복잡성 증가)
- Over-Fetching으로 인해 필요하지 않은 데이터까지 모두 받게 되어 낭비가 되는 상황이 발생합니다.
- Under-Fetching으로 인해 한 번의 요청으로 필요한 데이터를 받지 못해 여러 번 요청을 해야 하는 상황이 발생합니다.
2. GraphQL?
- 위와 같은 REST API의 한계를 보완하기 위해 만들어진 API입니다.
- Graph Query Language는 Facebook에서 개발한 API로써, 서버 런타임이며 SQL과 같은 쿼리 언어입니다.
- SQL은 데이터베이스에서 데이터를 효율적으로 가져오기 위한 질의 언어이지만, GQL은 클라이언트 측이 서버 측에서 데이터를 효율적으로 가져오기 위한 질의 언어입니다.
- 정확히 어떤 데이터가 필요한지 서버에 질의하면, 서버는 클라이언트 측에서 요청한 내용을 처리합니다.
3. GraphQL 사용해보기
프로젝트 생성
$ mkdir graphql-start && cd graphql-start
$ npm init -y
$ npm i apollo-server graphql
$ npm i nodemon -D
- apollo-server : GraphQL을 제공하는 서버를 만들 수 있게 도와주는 Apollo 패키지입니다.
- nodemon : 파일이 수정되었을 때 서버에 바로 적용해주는 패키지입니다.
index.js 생성
- https://github.com/apollographql/apollo-server 에 나와있는 index.js 샘플 코드를 복사해 붙여넣습니다.
const { ApolloServer, gql } = require('apollo-server');
// The GraphQL schema
const typeDefs = gql`
type Query {
"A simple type for getting started!"
hello: String
}
`;
// A map of functions which return data for the schema.
const resolvers = {
Query: {
hello: () => 'world',
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
- typeDefs : gql의 스키마(Object, Query, Mutation, Input)를 지정하는 부분으로, gql 함수에 Tagged Template Literals을 사용해 스키마를 지정합니다.
- resolvers : 스키마에 대한 구현체를 작성하는 부분으로, 요청에 대한 DB 연결 등의 비즈니스 로직이 들어갑니다. 예제에선 'hello'를 요청하면 'world'를 응답하도록 작성되어 있습니다.
package.json의 "scripts" 내용 변경
{
...
"scripts": {
"dev": "nodemon index.js"
},
...
}
Apollo 서버를 실행
$ npm run dev
브라우저에서 localhost:4000으로 접속 후 Playground 실행
여러개의 데이터 조회하기
typeDefs Query에 배열 타입 books를 추가합니다.
const { ApolloServer, gql } = require('apollo-server');
// The GraphQL schema
const typeDefs = gql`
type Query {
"A simple type for getting started!"
hello: String
books: [Book]
}
type Book {
bookId: Int
title: String
message: String
author: String
url: String
}
`;
...
resolvers Query에 books 구현체를 작성합니다.
const { ApolloServer, gql } = require('apollo-server');
const { readFileSync } = require("fs");
const { join } = require('path')
...
// A map of functions which return data for the schema.
const resolvers = {
Query: {
hello: () => 'world',
books: () => {
// DB 연결 등의 작업
// 아래에선 DB 대신 books.json 파일의 내용을 이용하고 있다
return JSON.parse( readFileSync( join(__dirname, 'books.json') ).toString() );
}
},
};
...
/* books.json */
[
{
"bookId": 1,
"title": "title test",
"message": "message test",
"author": "author test",
"url": "url test"
},
{
"bookId": 2,
"title": "title test2",
"message": "message test2",
"author": "author test2",
"url": "url test2"
}
]
이제 서버에 쿼리를 해서 결과를 확인합니다.
query {
books {
bookId
title
author
}
}
특정 데이터 조회하기
typeDefs Query에 bookId를 인자로 받고, Book 타입의 데이터를 리턴하는 스키마를 추가합니다.
const typeDefs = gql`
type Query {
...
book(bookId: Int): Book
}
type Book {
bookId: Int
title: String
message: String
author: String
url: String
}
`;
resolvers Query에 book에 대한 구현체를 작성합니다.
// A map of functions which return data for the schema.
const resolvers = {
Query: {
...
book: (parent, args, context, info) => {
const books = JSON.parse(readFileSync(join(__dirname, 'books.json')).toString());
return books.find( book => book.bookId === args.bookId );
}
},
};
쿼리를 통해 결과를 확인합니다.
query {
book(bookId: 1) {
title
author
}
}
데이터 추가하기
typeDefs Mutation에 데이터 추가에 대한 스키마를 작성합니다.
// The GraphQL schema
const typeDefs = gql`
type Query {
...
}
type Mutation {
addBook(title: String, message: String, author: String, url: String): Book
}
type Book {
bookId: Int
title: String
message: String
author: String
url: String
}
`;
resolvers Mutation에 구현체를 작성합니다.
// A map of functions which return data for the schema.
const resolvers = {
Query: {
...
},
Mutation: {
addBook: (parent, args, context, info) => {
const books = JSON.parse(readFileSync(join(__dirname, 'books.json')).toString());
const maxId = Math.max(...books.map( book => book.bookId ));
const newBook = { ...args, bookId: maxId + 1 };
writeFileSync(
join(__dirname, "books.json"),
JSON.stringify([...books, newBook ])
);
return newBook;
}
}
};
이제 Mutation 요청을 보내서 결과를 확인합니다.
Mutation {
addBook(title: "title test3", message: "message test3") {
title
message
}
}
/* books.json */
[
...,
{
"bookId": 3,
"title": "title test3",
"message": "message test3"
}
]