Querise and Mutations

이정훈·2024년 10월 2일

GraphQL

목록 보기
4/13

GraphQL server에 어떻게 쿼리를 하는지 알아보겠습니다.

Graph공식문서를 참고하였습니다.
여기서 쿼리가 상호작용이 되기 때문에 여기 예제를 쓸 것입니다.

Fields

GraphQL 객체에 대해 특정 필드를 요구할 수 있습니다.
예를 들어 다음과 같이 쿼리가 가능합니다.

{
  hero {
    name
  }
}

위에서는 hero객체에 대해 name필드 값을 요구하고 있습니다.
위 쿼리에 대한 결과는 아래와 같습니다.

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

쿼리와 결과를 비교해보면 형태가 같다는 것을 알 수 있습니다.
이것이 GraphQL의 근본인데 언제나 우리가 원하는 것을 얻기 때문입니다.
서버 또한 클라이언트가 어떤 필드를 요구했는지 정확히 알 수 있습니다.

앞선 예시에서는 hero객체의 name필드는 string을 반환했습니다.
그러나 필드는 객체도 반환할 수 있습니다.
예를 들어 hero객체의 friends필드는 객체인데 이 객체에 대해 서브 쿼리가 가능합니다.
아래는 예시입니다.

{
  hero {
    name
    # Queries can have comments!
    friends {
      name
    }
  }
}

아래는 결과입니다.

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

Arguments

GraphQL에서는 모든 필드와 중첩된 객체들이 그들만의 arguments를 가질 수 있습니다. 이는 GraphQL이 여러 API fecheds를 대체 하게 만듭니다.
아래는 예시입니다.

{
  human(id: "1001") {
    name
    height
  }
}

human객체에 대해 id가 1001인 값을 요구하고 있습니다.
아래는 결과입니다.

{
  "data": {
    "human": {
      "name": "Darth Vader",
      "height": 2.02
    }
  }
}

또한 필드에 대해서 데이터 변형을 위해 argumnets를 이용할 수도 있습니다.
아래는 예시입니다.

{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}

height에 대해 피트 단위로 반환해달라고 요구하고 있습니다.
아래는 결과입니다.

{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 5.6430448
    }
  }
}

Aliases

별칭을 이용하면 같은 필드에 대해 서로 다른 arguments를 이용하여 쿼리를 할 수 있습니다.
[별칭]: 객체(arguments) 형태로 작성하면 됩니다.
아래는 예시입니다.

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

아래는 결과입니다.

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

Fragments

쿼리를 하다보면 같은 형태의 쿼리를 여러번 할 때가 있습니다.
그럴때마다 모든 필드를 똑같이 작성하는 것은 효율적이지 못합니다.
fragments를 이용하면 똑같은 필드들에 대해 단 한번의 필드 입력으로 쿼리가 가능합니다.
아래는 예시입니다.

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

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

...fragment [fragment 명칭] on [타입] 형태로 작성하면 fragment를 정의할 수 있습니다.
아래는 결과입니다.

{
  "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"
        }
      ]
    }
  }
}

Using variables inside fragments

fragments를 내부에서 변수를 사용할 수 있습니다.
아래는 예시입니다.

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
      }
    }
  }
}

먼저 query는 쿼리를 선언하는 키워드입니다. 여기서는 HeroComparison이라는 쿼리를 서언해주고 있습니다.
HeroComparison에서 $first라는 변수에 기본값으로 int타입 3 값을 주고 있습니다.
fragment인 comparisonFields에서는 $first 변수를 사용하고 있습니다.

Operation name

query 키워드를 이용해 쿼리를 명시적으로 지정해 두는 것은 재사용 측면에서 매우 유용합니다.
아래는 예시입니다.

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

추가적으로 query키워드를 이용한 쿼리를 명시하는 것을 GraphQL에서는 권장하는데 이유는 디버깅에 매우 도움이 되며 서버 사이드 로깅에도 도움이 되기 때문입니다.

Variables

위 예시는 대부분 정적인 형태로 값을 가져오는 예시였습니다. 그러나 현실에서는 특정 조건을 두고 동적으로 값을 가져오는 경우가 더 많습니다.
그래서 GraphQL은 동적 값을 쿼리에서 분리하여 전달할 수 있는 방법을 제공합니다.
이때 variables를 이용하면 됩니다.

variables를 이용할 때는 아래 3가지 일만 하면 됩니다.
1. 정적인 값을 $variableName으로 교체한다.
2. 쿼리에서 받는 하나의 변수를 $variableName으로 선언한다.
3. variableName: value를 각각 특정 포맷으로 딕션어리 형태로 보냅니다.
아래는 예시입니다.

curl -X POST -H "Content-Type: application/json" --data '{
  "query": "query HeroNameAndFriends($episode: Episode) { hero(episode: $episode) { name friends { name } } }",
  "variables": { "episode": "EMPIRE" }
}' http://your-graphql-endpoint

Variable definitions

변수의 정의는 $를 이용하고 이후 type을 명시해주면 됩니다.
위에서는 HeroNameAndFriends 쿼리가 $episode라는 변수에 Episode타입 변수를 받습니다.
이후 이 변수를 hero객체에서 episode에 대입하고 있습니다.

또한 변수는 옵셔널로 설정할 수 있습니다.
위에서 Episode라는 타입을 지정한 것이 옵셔널입니다.
만약 required 변수로 만들고 싶다면 타입 뒤에 !를 붙이면 됩니다.
예를 들어 $episode: Episode!형태입니다.

Default variables

변수에 기본 값을 할당해 줄 수 있습니다.
아래는 예시입니다.

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

Directives

쿼리를 할 때 지시어를 작성할 수 있습니다.
지시어는 특정 필드를 포함할지 아니면 포함하지 않을지 설정할 수 있게 만들어 줍니다.
지시어로는
@include(if: Boolean)과 @skip(if: Boolean)이 있습니다.
아래는 지시어 사용 예시입니다.

query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}

데이터

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

결과

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

Mutations

앞서 배운 것은 데이터를 가져오기 위한 방법에 대한 것이였습니다.
GraphQL이 데이터를 가져오는데 특화되어 있지만 데이터를 수정할 수도 있습니다.

query와 유사하게 mutation을 이용하여 데이터 수정이 가능합니다.
아래는 예시입니다.

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

위의 CreateReviewForEpisode 뮤테이션은 Epdisode와 ReviewInput을 입력값으로 받고 CreateRevies 필드는 mutation의 결과에 대해 stars와 commentary를 반환합니다.
아래는 보내는 데이터 예시입니다.

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

아래는 결과입니다.

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

Multiple fields in mutations

쿼리처럼 뮤테이션도 여러 필드를 가질 수 있습니다.
차이점이 있다면 쿼리의 필드는 병행적으로 실행되지만 뮤테이션의 필드는 시리즈 형식으로 하나가 끝난뒤 다음 것을 처리하는 형식으로 이루어집니다.

Inline Fragments

GraphQL의 스키마는 인터페이스나 유니온 타입을 정의할 수 있습니다.

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

위 쿼리에서 hero 필드는 Character 타입을 리턴합니다.
Character타입은 Droid타입이나 Human타입을 반환할 수 있다고 가정해봅시다.
위 쿼리는 ... on 을 이용해 각 타입에 따라 무엇을 반환할지 설정하고 있습니다.

Meta fileds

어떤 상황에서는 GraphQL서버로부터 어떤 타입을 받는지 모를 수도 있습니다.
이떄 __typename을 이용하면 어떤 타입이 반환되었는지 알 수 있습니다.
아래는 예시입니다.

{
  search(text: "an") {
    __typename
    ... on Human {
      name
    }
    ... on Droid {
      name
    }
    ... on Starship {
      name
    }
  }
}

다음은 위 쿼리의 결과입니다.

{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo"
      },
      {
        "__typename": "Human",
        "name": "Leia Organa"
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1"
      }
    ]
  }
}
profile
기록으로 흔적을 남깁니다.

0개의 댓글