티스토리 뷰

Study/GraphQL

[GraphQL] 개념 & 기초 + 예제

Hoon's Blog 2019. 12. 20. 17:19

목표

- (1)에서는 개념에 대해 조금씩 이해함.

- (2)에서는 개념을 바탕으로 구현. (아마도.. 다음글?)

#1. GraphQL?

 

GraphQL은 API를 위한 쿼리언어.

타입 시스템을 사용하여 쿼리를 실행하는 서버사이드 런타임임.

GraphQL을 위한 언어가 존재하는게 아닌 기존의 Node.js, Pythone 등 여러 환경에서 사용 가능.

 

Ref. https://graphql-kr.github.io/learn/

 

GraphQL: API를 위한 쿼리 언어

GraphQL은 API에 있는 데이터에 대한 완벽하고 이해하기 쉬운 설명을 제공하고 클라이언트에게 필요한 것을 정확하게 요청할 수 있는 기능을 제공하며 시간이 지남에 따라 API를 쉽게 진화시키고 강력한 개발자 도구를 지원합니다.

graphql-kr.github.io


#2. 용어

(모든 예제는 graphql-kr을 참고하였습니다. 자세한 내용은 아래 링크로 연결된 사이트에서 더욱 자세하고 친절하게 설명된 내용을 볼수있습니다)

 

- 필드(Fields)

{
  hero {
    name
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

"name" : field

"R2-D2" : "name" field에 대한 String Type의 반환값


- 인자(Arguments)

field에 인자를 전달하기 위해 사용됨.

{
  human(id: "1000") {
    name
    height
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

기존의 RestAPI의 경우 요청에 쿼리 파라미터와 URL 세그먼트 같은 단일 인자들만 전달할 수 있었음.

GraphQL에서는 모든 필드와 중첩된 객체가 인자를 가질수 있음.

 

{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 5.6430448
    }
  }
}

(이 경우 unit을 METER, FOOT 등으로 정의되어 있으며 인자를 통해 FOOT으로 반환받음. 앞 예제에서는 기본 인자값인 METER unit으로 반환받음 - 자세한 내용 : 스키마 참고 - 링크 )


- 별칭(Aliases)

Field에 대한 별칭을 부여할 수 있음.

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

hero field에 episode에 대한 argument를 각각 "EMPIRE", "JEDI"를 전달하고 hero에 대한 Aliases를 "empireHero"와 "jediHero"라고 부여함.


 

 

 

- 프래그먼트(Fragments)

반복되는 필드로 인해 쿼리가 복잡해지는것을 해결하기 위해 사용.

Fragements라는 재사용 가능한 유닛을 포함하여 아래와 같이 표현할 수 있음.

{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        },
        {
          "name": "C-3PO"
        },
        {
          "name": "R2-D2"
        }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name,
    id
  }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Han Solo",
          "id": "1002"
        },
        {
          "name": "Leia Organa",
          "id": "1003"
        },
        {
          "name": "C-3PO",
          "id": "2000"
        },
        {
          "name": "R2-D2",
          "id": "2001"
        }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Luke Skywalker",
          "id": "1000"
        },
        {
          "name": "Han Solo",
          "id": "1002"
        },
        {
          "name": "Leia Organa",
          "id": "1003"
        }
      ]
    }
  }
}

 

프래그먼트 안에서 변수도 사용이 가능함.

query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "friendsConnection": {
        "totalCount": 4,
        "edges": [
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          },
          {
            "node": {
              "name": "C-3PO"
            }
          }
        ]
      }
    },
    "rightComparison": {
      "name": "R2-D2",
      "friendsConnection": {
        "totalCount": 3,
        "edges": [
          {
            "node": {
              "name": "Luke Skywalker"
            }
          },
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          }
        ]
      }
    }
  }
}
query HeroComparison($first: Int = 4) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "friendsConnection": {
        "totalCount": 4,
        "edges": [
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          },
          {
            "node": {
              "name": "C-3PO"
            }
          },
          {
            "node": {
              "name": "R2-D2"
            }
          }
        ]
      }
    },
    "rightComparison": {
      "name": "R2-D2",
      "friendsConnection": {
        "totalCount": 3,
        "edges": [
          {
            "node": {
              "name": "Luke Skywalker"
            }
          },
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          }
        ]
      }
    }
  }
}

friendsConnection Field에 first:3 이라는 Argument를 전달하여 각각 3개에 대한 결과값을 반환받음.

first:4로 둘 경우 "R2-D2"의 경우 totalCount가 3이기 때문에 3개에 대한 결과값만 받아옴.

 


- 작업 타입(Operation Type)과 작업 이름(Operation name)

 

아래 예제에서 작업 타입(Operation Type)은 query이고 작업 이름(Operation name)은 HeroNameAndFriends임.

(Operation name은 디버깅이나 서버측 로깅시 식별 목적으로 사용)

Operation Type에는 query, mutation, subscription이 있음. - 각각에 대해서는 추후 자세히 설명

query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}

 

 

 

 

 

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

- 변수 (Variables)

 

동적으로 인자를 전달하기 위해 사용.

(얜 포맷)

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

 

 

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

(얜 변수)

{
  "episode": "JEDI"
}

위에서 "($episode: Episode)" 을 통해 변수를 정의함.

 

다음과 같이 변수의 기본값을 명시 & 할당할 수 있음.

query HeroNameAndFriends($episode: Episode = "JEDI") {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

만약 별도로 변수가 전달된다면 기본값을 덮어쓰게됨.

 

 


- 지시어 (Directies)

 

지시어는 Fields나 Fragments 안에 삽입될 수 있으며 서버가 원하는 방식으로 쿼리 실행에 영향을 줄 수 있음.

코어 GraphQL 사양에는 두 가지 지시어가 포함됨.

(1) @include(if: boolean) : Argument가 true인 경우에만 이 필드를 결과에 포함함.

(2) @skip(if: Boolean) : Arguemnt가 true이면 이 필드를 건너뜀.

 

다음은 @include 지시어를 사용하여 friends를 결과에 포함 / 미포함 시키도록 함.

query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}
@include 지시어에 withFriends 변수를 false로 설정하여 friends 필드를 제외시킴

{
  "episode": "JEDI",
  "withFriends": false
}

{ 
  "data": { 
    "hero": { 
      "name": "R2-D2" 
    } 
  } 
}

@include 지시어에 withFriends 변수를 true로 설정하여 friends 필드를 포함시킴

{
  "episode": "JEDI",
  "withFriends": true
  
}

 

 

 

 

 

 

 

 

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

 


 

 

- 뮤테이션 (Mutation)

Mutation의 사용 목적은 데이터의 변형(update, remove, add)가 있을 경우 사용함.

(기존은 단순히 fetch의 목적이었다면 뮤테이션은 값을 변경하는데 그 목적이 있음)

 

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

{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

 

 

 

 

{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}

createReview 필드가 새로 생성된 리뷰의 starts와 commentary 필드를 반환한다.

Mutation을 사용하여 위와 같은 요청을 할 경우 해당 값으로 값이 update(또는 add)된다.

 

Query Fileds는 병렬로 실행되지만 Mutation Fileds는 하나씩 차례대로 실행되며 두개 이상의 incrementCredits Mutation을 보내면 첫번째는 두번째 요청 전에 완료되는것을 보장함.

(즉, 요청이 두개면 첫번째가 처리된 뒤에 두번째가 처리되는 순차처리 방식)

 

(Ref. https://hoony-gunputer.tistory.com/164, https://devcoding.tistory.com/38)

 

 

 


- 인라인 프래그먼트 (Inline Fragments)

 

인터페이스나 유니언 타입을 반환하는 필드를 쿼리하는 경우 인라인 프래그먼트를 사용해야함.

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}
{
  "ep": "NEWHOPE"
}

위 예제에서 hero 필드는 촘Character를 반환하는데 episoe 인자에 따라서 Human 또는 Droid 중 하나일 수 있음. 

(ep 값에 따라 Droid or Human인지 결정되고 그에 따라 primaryFunction 또는 height를 반환함)

 

Human인 경우

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}

{
  "data": {
    "hero": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

 

 

 

{
  "ep": "EMPIRE"
}

- 메타 필드 (Meta fields)

 

todo


 

 

 

 

 

 

 

위 모든 예제는 아래 사이트를 참고하였습니다.

Ref. https://graphql-kr.github.io/learn/queries/

 

GraphQL: API를 위한 쿼리 언어

GraphQL은 API에 있는 데이터에 대한 완벽하고 이해하기 쉬운 설명을 제공하고 클라이언트에게 필요한 것을 정확하게 요청할 수 있는 기능을 제공하며 시간이 지남에 따라 API를 쉽게 진화시키고 강력한 개발자 도구를 지원합니다.

graphql-kr.github.io

 

 

(작성중 ...)

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함