Graph + Query Language
Server API๋ฅผ ํตํด ์ ๋ณด๋ฅผ ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํด ์ฌ์ฉํ๋ ์ฟผ๋ฆฌ ์ธ์ด์ด๋ค.
GraphQL์ ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ๊ทธ๋ํ ํํ๋ก ์ฐ๊ฒฐ๋์ด ์๋ค๊ณ ๋ณธ๋ค.
(๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ์๋ก ์ฐ๊ฒฐ๋์ด ์์ด์ ์ํธ์์ฉํ๋ค๋ ๊ฒ์ ์๋ฏธ)
GraphQL์์๋ ์ด๋ฌํ ๊ทธ๋ํ ๊ตฌ์กฐ๋ฅผ ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๊ธฐ ์ํด ํ์ํ๋ค. ์ฆ, ํด๋ผ์ด์ธํธ์์ ์์ฒญํ ์ฟผ๋ฆฌ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ฒ๋ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ฐพ์์ ํธ๋ฆฌ ํํ๋ก ๋ฐํํ๋ค. ์ด๋ ๊ฒ ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ๋ฐํ๋๋ ๊ฒฐ๊ณผ๋ ํด๋ผ์ด์ธํธ์์ ํ์ํ ๋ถ๋ถ๋ง ์ ํ์ ์ผ๋ก ๋ฐ์ ์ ์๊ธฐ ๋๋ฌธ์, ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ์ง ์์๋ ๋๊ณ , ๋ฐ์ดํฐ ์ ์ก ์๊ฐ๊ณผ ๋์ญํญ์ ์ ์ฝํ ์ ์๋ค.
โ๏ธGraphQL์ ํน์ง
โ HTTP๋ฅผ ํตํด API ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ต์ ๋ฐ๋๋ค.
โ ์๋ต์ ๋ฐ์ ์, ๋ฐ์ดํฐ ๊ฒฐ๊ณผ๋ฅผ JSON ํ์์ผ๋ก ๋ฐ๋๋ค.
โ ์๋ฒ ๊ฐ๋ฐ์๊ฐ ์์ฑํ ๊ฐ ํ๋์ ๋์ํ๋ resolver ํจ์๋ก ๊ฐ ํ๋์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ์ ์๋ค.
โ GraphQL ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์กฐํ ๋์ schema๊ฐ ์ ํจํ์ง ๊ฒ์ฌํ๋ค.
Query
: ์ ์ฅ๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
Mutation
: ์ ์ฅ๋ ๋ฐ์ดํฐ ์์ ํ๊ธฐ
ย ย ย โ Create: ์๋ก์ด ๋ฐ์ดํฐ ์์ฑ
ย ย ย โ Update: ๊ธฐ์กด์ ๋ฐ์ดํฐ ์์
ย ย ย โ Delete: ๊ธฐ์กด์ ๋ฐ์ดํฐ ์ญ์
Subscription
: ํน์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ ์ ์๋ฒ๊ฐ ๋์ํ๋ ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ํด๋ผ์ด์ธํธ์๊ฒ ์ ์ก
ํ๋(field)
// ์์ฒญ
{
hero {
name
}
}
-------
// ์ป์ ๊ฒฐ๊ณผ
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
์ค์ฒฉ ํ๋
# ์์ฒญ
{
hero {
name
# ์ด๋ฐ ์์ผ๋ก GraphQL ๋ด์์ ์ฃผ์๋ ์์ฑ ๊ฐ๋ฅ
friends {
name
}
}
}
-------
# ์ป์ ๊ฒฐ๊ณผ
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
์ ๋ฌ์ธ์(Arguments)
ํ๋์ ์ธ์๋ฅผ ์ ๋ฌํ์ฌ ์ํ๋ ๋ฐ์ดํฐ๋ง ์ ํ์ ์ผ๋ก ๋ฐ์์ฌ ์ ์๋ค.
// ์์ฒญ
// id๊ฐ 1000์ธ human์ name๊ณผ height๋ฅผ ์ฟผ๋ฆฌ
{
human(id: "1000") {
name
height
}
}
-------
// ์ป์ ๊ฒฐ๊ณผ
// id๊ฐ 1000์ธ human์ ์ด๋ฆ๊ณผ ํค๋ฅผ ์ฟผ๋ฆฌํด ์ด
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 1.72
}
}
}
๋ณ๋ช (Aliases)
ํ๋ ์ด๋ฆ ์ค๋ณต ์, ๋ณ๋ช ์ ์ฌ์ฉํด ์ฟผ๋ฆฌํ๋ค.
// ์์ฒญ
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
-------
// ์ป์ ๊ฒฐ๊ณผ
// hero๊ฐ ์๋ empireHero์ jediHero๋ก ๋ฐ๋
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
์คํผ๋ ์ด์ ๋ค์(Operation name)
์ค์ ๋ก๋ ์ง๊ธ๊น์ง์ ์์ ์ฝ๋์๋ ๋ค๋ฅด๊ฒ
์๋์ฒ๋ผ query keyword์ query name์ ์ ์ด์ฃผ์ด์ผ ํ๋ค.
// ์์ฒญ
// query๋ ์คํผ๋ ์ด์
ํ์
์ด๋ค.
// ์คํผ๋ ์ด์
ํ์
์ข
๋ฅ: query, mutation, subscription, describes ๋ฑ..
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
-------
// ์ป์ ๊ฒฐ๊ณผ
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
๋ณ์(Variables)
์ค์ ์ฑ์ ์ฌ์ฉํ ๋๋ ๊ณ ์ ๋ ์ธ์๋ฅผ ๋ฐ๋ ๊ฒ๋ณด๋ค๋
๋์ ์ผ๋ก ์ธ์๋ฅผ ๋ฐ์ ์ฟผ๋ฆฌ ํ๋ค.
// ๋ณ์๋ฅผ ์จ์ ์์ฑ๋ ์ฟผ๋ฆฌ
// $episode: Episode ๋ค์ !๊ฐ ๋ถ๋๋ค๋ฉด
// episode๋ ๋ฐ๋์ Episode์ฌ์ผ ํ๋ค๋ ๋ป์ด๋ค.
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
์คํค๋ง๋ ์๋น์ค์์ ๊ฐ์ ธ์ฌ ์ ์๋ ๊ฐ์ฒด์ ์ข
๋ฅ์
ํด๋น ๊ฐ์ฒด๊ฐ ๊ฐ์ง๋ ํ๋๋ค์ ์ ์ํ๋ค.
// ๊ฐ ๊ฐ์ฒด ์ ํ๊ณผ ํ๋๋ค์ ๋ฐ์ดํฐ ํ์
, ์ธ์, ๋ฐํ๊ฐ ๋ฑ์ ์ ์ํ ์ ์๋ค.
// !๋ฅผ ๋ถ์ด๋ฉด, ๋ฐ๋์ ๊ฐ์ ๋ฐ๋๋ค๋ ์๋ฏธ์ด๋ค.
// []๋ฐฐ์ด์ !๊ฐ ๋ถ์ผ๋ฉด null๊ฐ์ ํ์ฉํ์ง ์๋๋ค๋ ๋ป!
type Character {
name: String!
appearsIn: [Episode!]!
}
์คํค๋ง์์ ์ ์๋ ํ์
(Query, Mutation, Subscription)์ ๋ํ
๋์ ๋ฐฉ์์ ์ ์ํ๊ณ , ๋ฐ์ดํฐ ์์ค์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ์ ์ ํ ์๋ต์ ๋ฐํํ๋ค.
const db = require("./../db")
const resolvers = {
Query: { // **Query :** ์ ์ฅ๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ (REST ์ GET ๊ณผ ๋น์ทํฉ๋๋ค.)
getUser: async (_, { email, pw }) => {
db.findOne({
where: { email, pw }
}) ... // ์ค์ ๋๋น์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ก์ง์ ์์ฑํฉ๋๋ค.
...
}
},
Mutation: { // **Mutation :** ์ ์ฅ๋ ๋ฐ์ดํฐ ์์ ํ๊ธฐ ( Create , Update , Delete )
createUser: async (_, { email, pw, name }) => {
...
}
}
Subscription: { // **Subscription :** ์ค์๊ฐ ์
๋ฐ์ดํธ
newUser: async () => {
...
}
}
};
GitHub GraphQL API์์ ์ ๊ณตํ๋ explorer๋
์ผ์ข
์ playground๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
GitHub ์์ด๋๋ก ๋ก๊ทธ์ธํ๋ฉด ์์ ์ด ๋ง๋ ๋ ํฌ์งํ ๋ฆฌ์ ์ด๋ ฅ,
๋ค๋ฅธ ์ฌ๋์ด ๋ง๋ ๋ ํฌ์งํ ๋ฆฌ์๋ ์ ๊ทผํ ์ ์๋ค.
โ explorer์ ์ ์ โ https://docs.github.com/en/graphql/overview/explorer
โ ์์ ์ GitHub id๋ก ๋ก๊ทธ์ธ
โ GraphQL ์ฟผ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ ์์ ์ด ๋ง๋ค์๋ repository๋ค์ ์ ๊ทผํด ์กฐํ
โ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๊ธฐ ์ด๋ ต๋ค๋ฉด Reference๋ฅผ ๋ณด๊ณ ์์ฑ โ https://docs.github.com/en/graphql/reference/queries
โ ๋๋ Explorer์ Explorer ๋ฒํผ์ ๋๋ฌ ์ง์ ๊ด๋ จ๋ field๋ฅผ ํ์ํ ์ ์๋ค.
// ์ค์น
// graphQL์ ์ฟผ๋ฆฌ๋ฅผ ๋ก์ปฌ ํ๊ฒฝ์์ ์ฝ๊ฒ ์ํํ ์ ์๊ฒ ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
npm install @octokit/graphql
@octokit/graphql์ ์ฌ์ฉ๋ฒ๊ณผ
Graphql Queries-GitHub Docs๋ฅผ ๋ณด๊ณ ๊นํ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
// graphql ๊ฐ์ ธ์ค๊ธฐ
import { graphql } from "@octokit/graphql";
import React, { useState, useEffect } from "react";
async function myData() {
const CLIENT_TOKEN = process.env.REACT_APP_GHP_TOKEN;
const { repository, viewer } = await graphql(
`
{
repository(name: "์ด๋ฆ", owner: "์์ ์") {
discussions(last: 10) {
edges {
node {
id
title
url
author {
resourcePath
}
}
}
}
issues(last: 10) {
edges {
node {
id
title
}
}
}
pullRequests(last: 10) {
edges {
node {
id
title
}
}
}
}
viewer {
login
}
}
`,
{
headers: {
authorization: `token ${CLIENT_TOKEN}`,
},
}
);
return { repository, viewer };
}
const GetInfo = () => {
const [data, setData] = useState([]);
const [issues, setIssues] = useState([]);
const [pull, setPull] = useState([]);
useEffect(() => {
myData().then((res) => {
setData(res.repository.discussions.edges);
setIssues(res.repository.issues.edges);
setPull(res.repository.pullRequests.edges);
});
}, []);
return (
<>
<div>
{data.map((el) => {
return <div key={el.node.id}>{el.node.title}</div>;
})}
</div>
<br />
<span>ISSUE</span>
<div>
{issues.map((el) => {
return <div key={el.node.id}>{el.node.title}</div>;
})}
</div>
<br />
<span>PULL REQUEST</span>
<div>
{pull.map((el) => {
return <div key={el.node.id}>{el.node.title}</div>;
})}
</div>
</>
);
};
export default GetInfo;
GraphQL์ ์ฅ๋จ์
์ฅ์ | ๋จ์ |
---|---|
ํ๋์ endpoint๋ก ๋ชจ๋ ๋ฐ์ดํฐ ์์ฒญ ๊ฐ๋ฅ | REST API์ ์ต์ํ์ง ์์ ๊ฐ๋ฐ์๋ค์๊ฒ๋ ํ์ต ๊ณก์ ์ด ์์ |
๋ฐ์ดํฐ ์ค๋ฒํ์นญ๊ณผ ์ธ๋ํ์นญ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํจ | ์บ์ฑ์ด REST API๋ณด๋ค ๋ณต์กํจ |
Playground๋ฅผ ์ ๊ณตํด schema์ resolver๋ฅผ ์ฝ๊ฒ ํ ์คํธ | ์ฟผ๋ฆฌ ํฌ๊ธฐ๊ฐ ๊ณ ์ ๋ ์์ฒญ์ ๊ฒฝ์ฐ RESTful API๋ณด๋ค ๋ ํฌ๊ฒ ๋ ์ ์์ |
ํด๋ผ์ด์ธํธ ๊ตฌ์กฐ ๋ณ๊ฒฝ์๋ ์๋ฒ์ ์ง์ฅ์ด ์์ |
REST API | GraphQL | |
---|---|---|
Resource ํํ ์ ์ | URI์ ์ํด ์ ์ | GraphQL Schema์ ์ํด ์ ์ |
๋ฐ์ดํฐ ์์ฒญ ๋ฐฉ๋ฒ | HTTP Method์ ์ํด ์ ์ | Query, Mutation ํ์ ์ ์ํด ์ ์ |
๋ฐ์ดํฐ ๊ตฌ์กฐ | ์๋ฒ์์ ๋ฏธ๋ฆฌ ์ ์๋จ | ํด๋ผ์ด์ธํธ์์ ์์ฒญ ์ ๊ฒฐ์ ๋จ |
์ฌ๋ฌ Resource ์์ฒญ | ์ฌ๋ฌ ๋ฒ์ ์์ฒญ ํ์ | ํ ๋ฒ์ ์์ฒญ์์ ์ฌ๋ฌ Resource ์ ๊ทผ ๊ฐ๋ฅ |
๋ฐ์ดํฐ ํธ๋ค๋ง | ์๋ํฌ์ธํธ ํธ๋ค๋ง ํจ์์ ์ํด ์ฒ๋ฆฌ | ํ๋๋ณ Resolver ํจ์์ ์ํด ์ฒ๋ฆฌ |
ํด๋ผ์ด์ธํธ ์๊ตฌ์ฌํญ | ์๋ฒ์์ ์ ๊ณตํ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ์ผ์นํด์ผ ํจ | ํด๋ผ์ด์ธํธ ์๊ตฌ์ฌํญ์ ๋ง๊ฒ ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋ณ๊ฒฝ ๊ฐ๋ฅ |
๐ GraphQL์ Learn ๋ณด๋ฌ๊ฐ๊ธฐ
๐ @octokit/graphql์ ์ฌ์ฉ๋ฒ
๐ ์ ํ๋ธ ์์ - graphQL๋ก ์๋ฒ๋ฅผ ๊ตฌ์ถํ๊ณ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ฐฉ๋ฒ