2022.08.02(Tues)
[TIL] Day68
[SEB FE] Day69
์ค๋์ ์์นจ 8์๋ถํฐ ์ง ์ด์ฌํ๋ ๋ ์ด์ฌ์ 9์๋ถํฐ ์นดํ๋ก ํผ์ ํ๋ค..
๊ฒ๋ค๊ฐ ๋น๋ ์ฃผ๋ฅต์ฃผ๋ฅต ๐ญ
์์ทจ๋ฐฉ 2๋ฒ ์ด์ฌํ ๋๋ง๋ค ๋ชจ์กฐ๋ฆฌ ๋น ์์๋๋ฐ ๋ณธ๊ฐ ์ด์ฌํ ๋๋ ๋๋๋ ๋น๋ผ๋.. โ๏ธ
์ง ์์ดํ์ด๋ ํ์ด 2์ ์ ์ ๊ฒจ์ฐ ์ฐ๊ฒฐํ๊ตฌ, ํ์ด ์๊ฐ์ ์ฐ๋นํํ ๋๋ฌด ์๋์ค๋ฌ์์ ํ์ด๋๊ป ๋๋ฌด ์ฃ์ก,, โขแทโโขแท
์ผ๋ฅธ ๋ฐฉ ์ ๋ฆฌํ๊ณ ์๋ก์ด ๋ง์์ผ๋ก ๋ค์ ์ด์ฌํ ๊ณต๋ถํ์ใ  ๐ฆพ
: Graph + Query Language. ํ์ด์ค๋ถ์์ ๋ง๋  ์คํ ์์ค ์ฟผ๋ฆฌ ์ธ์ด โ API๋ฅผ ์ํ ์ฟผ๋ฆฌ ์ธ์ด
๐ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๊ธฐ ์ํด ๊ทธ๋ํ๋ฅผ ํ์ํ๋ ์ฟผ๋ฆฌ ์ธ์ด
resolver ํจ์๋ก ๊ฐ ํ๋ ๋ฐ์ดํฐ ์กฐํ ๊ฐ๋ฅ๐นย Graph: ์ฌ๋ฌ ๊ฐ์ ์ ๋ค์ด ์๋ก ๋ณต์กํ๊ฒ ์ฐ๊ฒฐ๋์ด ์๋ ๊ด๊ณ๋ฅผ ํํํ ์๋ฃ๊ตฌ์กฐ
Node / ์ ์ (vertex): ํ๋์ ์ ๊ฐ์ (edge): ํ๋์ ์ ๐ธย ์ํฐํฐ(Entity): ์ฌ๋ฌผ์ ๊ตฌ์กฐ๋ ์ํ, ๋์ ๋ฑ์ ๋ชจ๋ธ๋ก ํํํ๋ ๊ฒฝ์ฐ, ๊ทธ ๋ชจ๋ธ์ ๊ตฌ์ฑ์์
๐นย Tree: ๋ฐฉํฅ์ฑ ์กด์ฌ โญ๏ธ, ์ฌ์ดํด ์กด์ฌ โย โ ๋น์ํ ๊ทธ๋ํ
ใด ๋ฃจํธ & ๋ชจ์๋ฆฌ๋ฅผ ํตํด ๋ ธ๋๋ฅผ ๋ฐ๋ผ ์ํ๊ฐ๋ฅํ๋ ๋์ผ ๋ ธ๋๋ก ๋์์ฌ ์ ์๋ ์์ฑ์ ํน๋ณํ ๊ทธ๋ํ
Overfetch: ํ์์๋ ๋ฐ์ดํฐ๊น์ง ์ ๊ณตUnderfetch: endpoint๊ฐ ํ์ํ ์ ๋ณด๋ฅผ ์ถฉ๋ถํ ์ ๊ณต ๋ชปํจ| REST API | GraphQL | 
|---|---|
| Resource์ ๋ํ ํํ ์ ์ & ๋ฐ์ดํฐ ์์ฒญ ๋ฐฉ๋ฒ ์ฐ๊ฒฐ | Resource์ ๋ํ ํํ ์ ์ & ๋ฐ์ดํฐ ์์ฒญ ๋ฐฉ๋ฒ ๋ถ๋ฆฌ | 
| Resource ํฌ๊ธฐ&ํํ๋ฅผ ์๋ฒ์์ ๊ฒฐ์  | Resource ์ ๋ณด๋ง ์ ์, | 
| ํ์ํ ํฌ๊ธฐ&ํํ๋ ํด๋ผ์ด์ธํธ ๋จ์์ ์์ฒญ์ ๊ฒฐ์  | |
| URL๊ฐ Resource๋ฅผ ๋ํ๋ด๋ฉฐ, Method๊ฐ ์์ ์ ํ์ ๋ํ๋  | GraphQL Schema๊ฐ Resource๋ฅผ ๋ํ๋ด๋ฉฐ, Query, Mutation type์ด ์์ ์ ํ์ ๋ํ๋  | 
| ์ฌ๋ฌ Resource์ ์ ๊ทผ์ ์ฌ๋ฌ๋ฒ์ ์์ฒญ ํ์ | ํ๋ฒ์ ์์ฒญ์ผ๋ก ์ฌ๋ฌ Resource์ ์ ๊ทผ ๊ฐ๋ฅ | 
| ๊ฐ ์์ฒญ์ ํด๋น endpoint์ ์ ์๋ ํธ๋ค๋ง ํจ์๋ฅผ ํธ์ถํ์ฌ ์์ ์ฒ๋ฆฌ | ์์ฒญ ๋ฐ์ ๊ฐ ํ๋์ ๋ํ resolver๋ฅผ ํธ์ถํ์ฌ ์์ ์ฒ๋ฆฌ | 
/graphql๋ก ์์ฒญ์ ๋ฐ๊ณ , ์ด์ ๋ฐ๋ผ query, mutation์ resolver ํจ์๋ก ์ ๋ฌํด์ ์์ฒญ์ ์๋ต (๋ชจ๋  ํด๋ผ์ด์ธํธ ์์ฒญ์ POST ๋ฉ์๋ ์ฌ์ฉ)playgroundPOST ๋ฉ์๋๋ง ์ด์ฉํด ์์ฒญ์ ๋ณด๋ด๋ฏ๋ก ๊ฐ ๋ฉ์๋์ ๋ฐ๋ฅธ ์บ์ฑ์ ์ง์๋ฐ์ ์ ์์Query: ์ ์ฅ๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ (REST์ GET๊ณผ ์ ์ฌ)Mutation: ์ ์ฅ๋ ๋ฐ์ดํฐ ์์ Create: ์๋ก์ด ๋ฐ์ดํฐ ์์ฑUpdate: ๊ธฐ์กด ๋ฐ์ดํฐ ์์ Delete: ๊ธฐ์กด ๋ฐ์ดํฐ ์ญ์ Subscription: ํน์  ์ด๋ฒคํธ ๋ฐ์์ ์๋ฒ๊ฐ ๋์ํ๋ ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ํด๋ผ์ด์ธํธ์๊ฒ ์ ์ก๐นย Field
# query๋ก ๋ฐ์ดํฐ ์กฐํ
{
	hero {
		name
		friends {
			name
		}
	}
}
# ํ๋๋ฅผ ์ค์ฒฉํ์ฌ ์ฟผ๋ฆฌํ๋ ๊ฒ๋ ๊ฐ๋ฅ!
# ์์ ์ฟผ๋ฆฌ ์คํ ๊ฒฐ๊ณผ
{
  "data": {
    "hero": {
      "name": "Beanxx",
			"friends": [   # friends ํ๋ -> ๋ฐฐ์ด ๋ฐํ
				{
					"name": "Yollki"
				},
				{
					"name": "Zzanggu"
				}
			]
    }
  }
}
๐นย ์ ๋ฌ์ธ์(Arguments)
# ํ๋์ ์ธ์์ ๋ฌ ๋ถ๋ถ ์ถ๊ฐ โ ์ฟผ๋ฆฌ์ ํ๋/์ค์ฒฉ๋ ๊ฐ์ฒด๋ค์ ์ ๋ฌ โ ์ํ๋ ๋ฐ์ดํฐ๋ง ๋ฐ์์ฌ ์ ์์
{
  human(id: "10") {
    name
    height
  }
}
# id๊ฐ 10์ธ human์ name, height ์ฟผ๋ฆฌ
{
  "data": {
    "human": {
      "name": "Beanxx",
      "height": 1.70
    }
  }
}
๐นย ๋ณ๋ช
(Aliases)
# ํ๋ ์ด๋ฆ ์ค๋ณต ์ฌ์ฉ ๋ถ๊ฐ -> ๋ณ๋ช
์ ๋ถ์ฌ์ ์ฟผ๋ฆฌ ์งํ
{ 
  beanHero: hero(episode: BEAN) {
    name
  }
  zzangHero: hero(episode: ZZANG) {
    name
  }
}
# BeanHero, ZzangHero๊ฐ ๋ณ๋ช
!
# ๋ณ๋ช
์ ๋ถ์ธ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ
{
  "data": {
    "beanHero": {
      "name": "Beanxx"
    },
    "zzangHero": {
      "name": "Zzanggu"
    }
  }
}
# ๋ค๋ฅธ ๋ณ๋ช
 ์ง์  -> 1๋ฒ์ ์์ฒญ์ผ๋ก 2๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ ์ป์ด๋ผ ์ ์์
๐นย Operation name
# ์ถ์ํ ๊ตฌ๋ฌธ x -> ์ค์  ์ฑ์์  ์ฝ๋ ๋ชจํธํ์ง ์๊ฒ ์์ฑํ๋๊ฒ ์ค์!
# ๋งจ ์ 'query'๊ฐ Operation name!
# query ์ธ์๋ mutation, subscription, describes ๋ฑ ์กด์ฌ
query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "Beanxx",
      "friends": [
        {
          "name": "Yollki"
        },
        {
          "name": "Zzanggu"
        },
        {
          "name": "Jane"
        }
      ]
    }
  }
}
๐นย ๋ณ์(Variables)
# ๋์ ์ผ๋ก ์ธ์๋ฅผ ๋ฐ์ ์ฟผ๋ฆฌํ๋ ๊ฒฝ์ฐ '๋ณ์' ์ฌ์ฉ
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
# `$๋ณ์ ์ด๋ฆ: ํ์
`์ผ๋ก ์์ฑ
# `$ep: Episode` ๋ค์ `!`๊ฐ ๋ถ์ผ๋ฉด ep๋ ๋ฐ๋์ Episode์ฌ์ผ ํจ์ ์๋ฏธ (Optional)
# server์ธก ๋ฐ์ดํฐ ์์ 
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
# GraphQL Schema ๊ธฐ๋ณธ ๊ตฌ์ฑ ์์: ๊ฐ์ฒด ์ข
๋ฅ, ๊ฐ์ฒด ์ ํ(ํฌํจํ๋ ํ๋๋ฅผ ๋ํ๋)
type Character {
  name: String!
  appearsIn: [Episode!]!
}
# Character: GraphQL ๊ฐ์ฒด ํ์
 => ์ฆ, ํ๋๊ฐ ์๋ ํ์
 ์๋ฏธ (์คํค๋ง ๋๋ถ๋ถ์ ํ์
 - ๊ฐ์ฒด ํ์
)
# name, appearIn: Character ํ์
์ ํ๋
# String: ๋ด์ฅ๋ ์ค์นผ๋ผ ํ์
 ์ค ํ๋๋ก, ๋จ์ผ ์ค์นผ๋ผ ๊ฐ์ฒด (์ฟผ๋ฆฌ์์ ํ์ ์ ํ ๊ฐ์ง ์ ์์)
# !: ํด๋น ํ๋๋ nullableํ์ง ์๊ณ  ๋ฐ๋์ ๊ฐ์ด ๋ค์ด์จ๋ค๋ ์๋ฏธ
# [ ]: ๋ฐฐ์ด ์๋ฏธ. ๋ฐฐ์ด์๋ !๊ฐ ๋ถ์ ์ ์์. -> ํญ์ 0๊ฐ ์ด์์ ์์๋ฅผ ํฌํจํ ๋ฐฐ์ด ๊ธฐ๋
: ์์ฒญ์ ๋ํ ์๋ต์ ๊ฒฐ์ ํด์ฃผ๋ ํจ์๋ก์จ ๋ก์ง์ ์์ฑํจ.
๐ย ํด๋น ์คํค๋ง ํ๋์ ์ฌ์ฉ๋๋ ํจ์์ ์ค์  ํ๋์ Resolver์์ ์ ์
const db = require("./../db")
const resolvers = {
  Query: {   # ์ ์ฅ๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
		getUser: async (_, { email, pw }) => {
			db.findOne({
				where: { email, pw }
			}) ... # ์ค์  DB์์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๋ ๋ก์ง ์์ฑํ๊ธฐ
			...
		}
  },
  Mutation: {   # ์ ์ฅ๋ ๋ฐ์ดํฐ ์์ ํ๊ธฐ
		createUser: async (_, { email, pw, name }) => {
			...
		}
  }
  Subscription: {   # ์ค์๊ฐ ์
๋ฐ์ดํธ
    newUser: async () => {
      ...
		}
  }
};
์ฒ์์ ํ์ด๋๊ณผ GraphQL๋ฅผ React์ ์ ์ฉํด์ผํ ์ง ๋ง๋งํด์ ๊ฑฐ์ 1์๊ฐ๋์ ํค๋งจ๋ฏ..
graphql.js ๊ณต์๋ฌธ์๋ฅผ ๋ณด๊ณ ๊ทธ๋ฅ App.js์ ๋ฃ์ด๋ดค๋๋ฐ๋ Error ๐ฑ
์๊ณ ๋ณด๋ github ๋ด์์ access token๋ ๋ฐ๊ธ๋ฐ์์ผ ํ๊ณ , await๋ก๋ง ์์ฑ๋์ด ์๋ ํจ์๋ฅผ async๋ก ๊ฐ์ธ์ค์ ์ฝ๊ฐ ์์ ํด์ผ ํ์๋ค..! ๋งํ์ ๋ฐ์์ค๋๊ฑฐ ์ฑ๊ณตํด์ ์ฌ๋ฌ ์์ฑ๋ค์ console๋ก ํ์ธํด๋ณด์๋ค.
์๋์ ๊ฐ์ด data๋ฅผ edges ๋ด์ ๋ฐฐ์ด ๋ด์ ๊ฐ์ฒด ํํ๋ก ๋ฐ์์ค๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
โจย ์์ ํ๊ธฐ ์์ ์ผ๋จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น๋ถํฐ!
# graphQL์ ์ฟผ๋ฆฌ๋ฅผ ๋ก์ปฌ ํ๊ฒฝ์์ ์ฝ๊ฒ ์ํํ  ์ ์๊ฒ ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
$ npm install @octokit/graphql
import { graphql } from "@octokit/graphql";
import { useEffect, useState } from "react";
async function getRepository() {
  const { repository } = await graphql(
    `
      {
        repository(name: "agora-states-fe", owner: "codestates-seb") {
          discussions(first: 100) { // ๋ฐ์ดํฐ ๋ช ๊ฐ ์ถ๋ ฅํ ์ง
            edges {
              node {
                id
                title
                createdAt
                url
                author {
                  login
                  avatarUrl
                }
                category {
                  name
                }
                answer {
                  author {
                    login
                  }
                }
              }
            }
          }
        }
      }
    `,
    {
      headers: {
				// ๊ผญ ์์ 'token' ๋ฌธ์์ด ๋ถ์ฌ์ฃผ๊ธฐ (์๋๋ฉด 'bearer'๋ ๊ฐ๋ฅ!)
        authorization: `token ${MY_TOKEN_ID}`,
      },
    }
  );
  return repository;
}
function App() {
  const [repository, setRepository] = useState({});
  const { discussions } = repository;
	// ์ฒ์ ํ๋ฒ๋ง ์คํ๋๋๋ก useEffect ์ฌ์ฉ!
  useEffect(() => {
    getRepository()
      .then((data) => {
        setRepository(data);
      })
      .catch((error) => {
        console.log(Error, error);
      });
  }, []);
	// map์ผ๋ก discussions.edges์ ์ ๊ทผํ์ฌ ๋ฐ์ดํฐ ๋ฟ๋ ค์ฃผ๊ธฐ
  return (
    <div className="main">
			<ul>
        {discussions.edges.map((edge) => {
          return (
            <li key={edge.node.id}>
                <img
                  src={edge.node.author.avatarUrl}
                  alt={`avatar of ${edge.node.author.login}`}
                />
                <div>
                  {`[${edge.node.category.name}]`}
                </div>
                  <a href={edge.node.url}>{edge.node.title}</a>
                <p>{edge.node.answer ? "โ" : "โ"}</p>
            </li>
          );
        })}
      </ul>
    </div>
  );
}
export default App;