자주 즐겨보는 유투버에게서 Graphql
을 다루는 영상이 업로드 되었다. 예전에 한번 예제로 만들어보려고 하다가 업무와 Java
에 적용하여 하기 어려워서 (라이픽의 특정 프로젝트에 적용하려고) 잠시 덮어준 기억이 있는데 예제가 잘나와있어서 정리한다.
해당 예제는 생활코딩 유투버의 내용을 그대로 가져온 것이다.
출처를 남기고 좋은 내용이 많으니 많은 개발자들이 해당 영상을 보았으면 좋겠다. 특히 목소리가 좋으시다
GraphQL은 Graph Query Language의 줄임말으로 페이스북에서 개발하였으며, 서버 API를 구성하기 위해 만든 데이터베이스 쿼리 언어이다.
쿼리문의 작성이 직관적이며 API를 호출할 때 쿼리문을 원하는 형식으로 작성하여 원하는 데이터만 응답받도록 할 수 있다.
기존 서버 API에서 주로 사용되던 RESTful API는 사용자가 원하는 형태의 모든 API를 구현하는데에 어려움이 있었으며 이러한 니즈는 GraphQL의 등장배경이 되었다.
GraphQL은 단일 endpoint를 사용하므로 단 한번의 호출으로 원하는 Resource를 얻을 수 있기 때문에 REST API에 비하여 HTTP 요청의 횟수를 줄일 수 있다.
또한 응답의 형태를 내가 원하는 부분만 쿼리문으로 작성하여 컨트롤 할 수 있기 때문에 HTTP 응답의 Size도 줄일 수 있다.
하지만 GraphQL이 장점만 가진다면 REST API를 사용할 이유가 없지 않은가
GraphQL은 고정과 요청과 고정 된 응답을 필요로 하는 경우에는 Query 때문에 REST API에 비하여 요청의 크기가 증가한다.
또 Text로 처리되지 않는 요청을 처리하기에 비효율적이며 재귀적인 Query가 불가능하다.
생활코딩님께서 stackblitz
에 코드도 남겨주시고 친절히 fork도 하라고 하셨다. 우리는 이걸 그대로 와서 따라한다기 보다는 초기 코드가 어떻게 구성되어 있으며 어떻게 나오는지 볼 필요가 있다.
영상에서의 초기 예제는 하드코딩인줄 알았는데 아니였다.
Graphql
을 사용하기 위해 다음과 같이 설정
const express = require('express'); // npm install express
const app = express();
const port = 3010;
const path = require('path');
const { buildSchema } = require('graphql'); // npm install graphql
var { graphqlHTTP } = require('express-graphql'); // npm install express-graphql
스키마는 데이터 타입의 집합으로 API 문서 역할을 한다. GraphQL API를 설계할 때엔 스키마를 먼저 정의하게 되며 이 스키마에는 어떤 종류의 객체를 반환할 지 내가 받을 수 있는 자원은 어떤 종류인지 어떠한 자원을 인자로 받는지가 정의되어있다.
const schema = buildSchema(`
type Topic {
id:Int!
title:String!
body:String
}
type Query {
title:String
topics:[Topic]
getTopic(id: Int!):Topic
}
type Mutation {
createTopic(title: String!, body: String): Topic
}
`)
위의 코드를 보면 다음과 같은 해석이 가능하다.
title
, topics
, getTopic
3가지가 가능topic
의 생성이 가능Topic
은 객체이며 id, title, body로 구성Topic
의 id, title은 Not Null
초기 구동시 다음과 같이 3건의 데이터가 설정, 이때 body는 없어도 된다
const topics = [
{id:1, title:'html', body:'html is ...'},
{id:2, title:'css', body:'css is ...'},
{id:3, title:'js', body:'js is ...'}
]
GraphQL
에 존재하는 함수인줄 알았으나, js의 해당 메소드를 실행한 결과를 response에 담아서 주는듯 하다. (이후에 Java 버전으로 테스트 해야 할듯)
const getTopic = function (args) {
const id = args.id;
const topic = topics.find((topic) => topic.id === id);
for (let i = 0; i < topics.length; i++) {
if (topics[i].id === id) {
return topics[i];
}
}
return null;
};
역시 해당 메소드를 실행한다. nextId값이 전역변수로 4로 되어있는건 위의 초기 설정에서 3개의 값을 미리 할당했기 때문에 다음값인 4로 되어있다.
let nextId = 4;
const createTopic = function (args) {
const newTopic = {
id: nextId,
title: args.title,
body: args.body,
};
topics.push(newTopic);
nextId = nextId + 1;
return newTopic;
};
페이지가 로드되면 자동으로 시작된다. 실행 이후 stackblitz
의 하단 terminal을 보면 실행 문구가 적혀있음을 확인할 수 있다. 생활코딩 예제에서 보면 graphiql
이라고 kibana
처럼 도움주는 IDE 쿼리 툴을 로딩하는거 같다.
app.use(
'/graphql',
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
})
);
app.get('/', (req, res) => {
res.sendFile(path.resolve('index.html'));
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
$ npx nodemon index.js
success Install finished in 2.957s
...
Example app listening at http://localhost:3010 # 실행 로그
프론트엔드 개발자가 아니여서 그런가 "어라? 어디서 초기 메소드가 실행되는거야?"라는 의문이 있었는데 html파일 86라인에 다음의 메소드가 존재함을 발견했다.
init(); // createHandle 메소드에서 호출하는 줄..
해당 메소드는 다음과 같은 순서로 동작한다.
title
과 topics
를 요청한다. (스키마 정의에서 되어있는 3가지중 2개만 요청)then이 json형태로 변경하고 다시 그걸 result로 처리해서 2번 하길래 어라? 한번에 가능한거 아닌가? 해서 다음처럼 해봤는데
//.then((type) => type.json()) .then((type) => { console.log(type.json()); result = type.json();
promise관련 예외가 발생하였다. async await가 뭐시기 되어야 하나보다
Uncaught (in promise) TypeError: Failed to execute 'json' on 'Response': body stream already read
init() 메소드에서 만들어진 List를 클릭시 수행하게 된다.
query getTopicSyntax($topicId:Int!) {
getTopic(id: $topicId){id title body}
}
getTopicSyntax
이 문구가 뭐지하고 구글링을 했는데 1건의 문서도 나오지 않는다. 보이는건 Validate 용도 인거 같은데 구글링에 한건도 안나와서 신기했다. (WOW!)이것을 찾으셨나요? get Topic Syntax 모든 검색어가 포함된 검색결과를 찾을 수 없습니다. getTopicSyntax와(과) 일치하는 검색결과가 없습니다.
다음과 같이 js에서 지정한 값(root)로 통신이 되는것 같다
var root = {
title: 'liam blog',
topics: topics,
getTopic: getTopic,
createTopic: createTopic,
};
app.use(
'/graphql',
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
})
);
endpoint = '/graphql';
query = `
query ($id:Int!) {
title
topics {
id title
}
getTopic(id:$id) {
id title
}
}
`
variables = {
'id': 1
}
options = {
method: 'POST',
headers: {'Content-Type':'application/json'},
body:JSON.stringify({
query: query,
variables: variables
})
}
fetch(endpoint, options).then(type => type.json()).then(result => {
console.log(result);
});
{
"data": {
"title": "liam",
"topics": [
{
"id": 1,
"title": "html"
},
{
"id": 2,
"title": "css"
},
{
"id": 3,
"title": "js"
},
{
"id": 4,
"title": "Liam`s blog"
}
],
"getTopic": {
"id": 1,
"title": "html"
}
}
}