티스토리 뷰

코드스테이츠(SEB_FE_42)

[API] GraphQL

codeyun2 2023. 1. 30. 21:32

REST API의 한계

1. overfetch

필요 없는 데이터까지 제공됨

- 유저 이름만 필요한데 유저의 가입일, 생일, 주소 등이 함께 제공

2. underfetch

endpoint가 필요한 정보를 충분히 제공하지 못해 추가적인 요청을 보내야 함

- endpoint마다 제공하는 정보가 한정되어 유저, 유저의 포스팅 목록, 유저의 팔로워를 얻기 위해 세 번의 요청 필요

3. 클라이언트 구조 변경 시 엔드포인트 변경 또는 데이터 수정 필요

서버가 자원의 크기와 형태를 결정하기 때문에

클라이언트에서 필요한 데이터가 변할 경우 다른 endpoint를 통해 변경된 데이터를 가져오거나 수정해야 함

 

 

=> 원하는대로 정보를 가져올 수 있고, 보다 편하게 정보를 수정할 수 있는 표준화된 Query Language가 만들어짐(=GraphQL)

 

GraphQL(Graph Query Language)

페이스북에서 만든 쿼리 언어(오픈소스). API를 위한 쿼리 언어

(쿼리 언어: 쿼리를 보내 데이터베이스 및 정보 시스템에서 데이터를 요청하고 탐색하는 컴퓨터 프로그래밍 언어)

 

모든 데이터가 그래프 형태로 연결되었다는 것을 전제로 함

트리 구조로 쿼리 결과를 받기 위해 그래프를 탐색하는 쿼리 언어

클라이언트 요청에 따라 그래프를 정렬하여 트리 구조의 JSON 데이터를 응답으로 전송

-> 고정된 자원을 받는 게 아닌 클라이언트 요청에 따라 자원을 유연하게 가져올 수 있음

 

GraphQL 특징

1. HTTP를 통해 API 서버로 요청을 보내고 응답 받음

2. 응답받을 때 데이터 결과를 JSON 형식으로 받음

3. 서버 개발자가 작성한 각 필드에 대응하는 resolver 함수로 각 필드의 데이터를 조회

4. GraphQL 라이브러리가 조회 대상 Schema(객체유형)가 유효한지 검사

 

GraphQL로 데이터 순회

관련 객체 및 필드를 순회하여 한 요청으로 여러 데이터를 받을 수 있음

가지고 있는 데이터 조각들이나 나타내고자하는 엔티티 간의 관계를 나타낼 수 있음

(엔티티: 모델의 구성 요소. 노드. 그 자체로는 의미를 표현할 수 없고 이들이 모여 의미를 제공

ex. 학번, 이름, 학과 => 학생)

 

GraphQL 장점

1. 하나의 endpoint 요청. No under&overfetching

query(조회), mutation(수정)을 resolver 함수로 전달하여 응답. 모든 클라이어트 요청에 POST 사용

하나의 endpoint에서 쿼리를 이용해 원하는 데이터만을 정확하게 API에 요청하고 응답으로 받을 수 있음

 

2. 강력한 playground

graphql 서버를 실행하면 playground라는 GUI를 이용해 resolver와 schema를 한 눈에 보고 테스트해볼 수 있음(POSTMAN)과 비슷

GraphQL 사용하기(Express) 05:40

 

3. 클라이언트 구조 변경에도 지장이 없음

클라이언트 구조가 바뀌어도 필요한 데이터를 결정하고 받는 주체가 클라이언트이기 때문에 서버에 영향을 받지 않음

클라이언트에서 무슨 데이터가 필요한 지에 대해서만 요구사항을 쿼리로 작성하면 됨

 

GraphQL 단점

1. REST API에 친숙한 개발자의 경우 GraphQL을 학습하는데 시간 필요

 

2. 캐싱이 REST API보다 훨씬 복잡

HTTP에선 각 메서드에 따라 캐싱이 구현되어 있으나

GraphQL은 POST 메서드만 사용하여 요청을 보내기 때문에 각 메서드에 따른 캐싱을 지원받을 수 없음

-> 이를 보완하기 위해 Apollo 엔진의 캐싱과 영속 쿼리 등이 등장

(캐싱: 이미 요청을 해서 데이터를 가지고 있다면 서버에 다시 요청하지 않고 웹 캐시 내에 가지고 있는 데이터를 반환)

 

3. 고정된 요청과 응답만 필요한 경우 Query로 인해 요청의 크기가 RESTful API 경우보다 커짐

 

REST API와 GraphQL

  REST API GraphQL
Resource에 대한 형태 정의와
데이터 요청 방법
연결 분리
Resource의 크기와 형태 결정 서버가 결정 클라이언트가 요청 시 결정
(서버는 Resource 정보만 정의)
Resource와 작업 유형 URI, Method GraphQLSchema, Mutation 타입
여러 Resource에 접근하려면 endpoint마다 요청 한 번에 여러 Resource 접근 가능
작업 처리 해당 endpoint에 핸들링된 함수 호출 요청받은 각 필드에 대한 resolver 호출

 

GraphQL Keywords

query, mutation은 전통적인 요청/응답 모델을 따르고, subscription은 발행/구독(pub/sub) 모델을 따름

 

1. query 쿼리

저장된 데이터 가져오기(REST API의 GET)

 

① filed 필드

GraphQL 필수 요소. 쿼리와 결과가 같은 모양임

// 요청
{
  hero {
    name    
    friends {
      name
    }
  }
}

// 응답
# #으로 주석처리 가능
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

 

② arguments 전달인자

필드에 인자를 추가하여 원하는 데이터만 받을 수 있음

// 요청
{
  human(id: "1000") {
    name
    height
  }
}

// 응답
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

 

③ aliases 별명

필드 이름을 중복해서 사용할 수 없기 때문에

필드 이름을 중복해서 사용해야할 때 별명을 붙여 그 별명을 필드의 키로한 객체를 응답 받음

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}

// 응답
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

 

④ operation name 오퍼레이션 네임

위 예시들은 쿼리와 쿼리 네임을 생략한 축약형 구문

실제 사용 시에는 코드에 명확하게 작성하는 것이 좋음

# 오퍼레이션 타입(query/mutation/subscription/describes) , 이름
query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}

 

⑤ Variables 변수

동적으로 변수를 받아 쿼리를 작성하고 싶을 때 사용

* 템플릿 리터럴(${})은 쿼리 주입 공격에 취약하기 때문에 변수$를 사용할 것 *

$변수 이름: 타입

`
  query HeroNameAndFriends($episode: Episode) {
    hero(episode: $episode) {
      name
      friends {
        name
      }
    }
  }
`
{
  episode: EMPIRE
}

 

import { graphql } from '@octokit/graphql';
const token = process.env.REACT_APP_TOKEN

const graphqlWithAuth = graphql.defaults({
    headers: {
      // token 대신 bearer를 입력하기도 함 -> 토큰 인증 방식의 하나
      authorization: `token ${token}`,
    },
  });

const getHeroNameAndFriends = async () => {
  const { hero } = await graphqlWithAuth(
    `
      query HeroNameAndFriends($episode: Episode) {
        hero(episode: $episode) {
          name
          friends {
            name
          }
        }
      }
    `
    {
      episode: EMPIRE
    }
  return hero
}

 

 

2. mutation 뮤테이션

데이터 수정

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}

 

3. schema/type 스키마/타입

스키마의 기본 구성요소: 서비스에서 가져올 수 있는 객체의 종류, 포함하는 필드를 나타내는 객체 유형

type Character {
  name: String!
  appearsIn: [Episode!]!
}

Character: GraphQL의 객체 타입(=필드가 있는 객체 타입) . 스키마에 있는 대부분의 타입이 이에 해당

String: 내장된 스칼라 타입 중 하나. 단일 스칼라 객체로 확인되는 유형. 쿼리에서 하위 선택을 가질 수 없음

(Scalar: 단 하나의 값만 저장할 수 있는 타입 +ID, Int)

!: Non-Null. null이 들어오면 실행 오류 발생. 이 필드는 반드시 값이 들어와야 함. 옵셔널

[]: 배열. []!은 0개 이상의 요소를 포함한 배열을 뜻함

 

4. Resolver 리졸버

요청에 대한 응답을 결정해주는 함수

데이터를 가져오는 구체적인 과정을 구현한 함수(ex. 데이터베이스 쿼리, 원격 API 요청)

 

스키마 정의 -> 스키마 필드에 사용되는 함수를 정의(리졸버)

이 리졸버들의 모음을 resolvers라 함

// 백엔드
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)

 

 

 

 

 

23.01.30

코스 S4U6 GraphQL

댓글
공지사항