GraphQL server에 어떻게 쿼리를 하는지 알아보겠습니다.
Graph공식문서를 참고하였습니다.
여기서 쿼리가 상호작용이 되기 때문에 여기 예제를 쓸 것입니다.
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"
}
]
}
}
}
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
}
}
}
별칭을 이용하면 같은 필드에 대해 서로 다른 arguments를 이용하여 쿼리를 할 수 있습니다.
[별칭]: 객체(arguments) 형태로 작성하면 됩니다.
아래는 예시입니다.
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
아래는 결과입니다.
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
쿼리를 하다보면 같은 형태의 쿼리를 여러번 할 때가 있습니다.
그럴때마다 모든 필드를 똑같이 작성하는 것은 효율적이지 못합니다.
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"
}
]
}
}
}
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 변수를 사용하고 있습니다.
query 키워드를 이용해 쿼리를 명시적으로 지정해 두는 것은 재사용 측면에서 매우 유용합니다.
아래는 예시입니다.
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
추가적으로 query키워드를 이용한 쿼리를 명시하는 것을 GraphQL에서는 권장하는데 이유는 디버깅에 매우 도움이 되며 서버 사이드 로깅에도 도움이 되기 때문입니다.
위 예시는 대부분 정적인 형태로 값을 가져오는 예시였습니다. 그러나 현실에서는 특정 조건을 두고 동적으로 값을 가져오는 경우가 더 많습니다.
그래서 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
변수의 정의는 $를 이용하고 이후 type을 명시해주면 됩니다.
위에서는 HeroNameAndFriends 쿼리가 $episode라는 변수에 Episode타입 변수를 받습니다.
이후 이 변수를 hero객체에서 episode에 대입하고 있습니다.
또한 변수는 옵셔널로 설정할 수 있습니다.
위에서 Episode라는 타입을 지정한 것이 옵셔널입니다.
만약 required 변수로 만들고 싶다면 타입 뒤에 !를 붙이면 됩니다.
예를 들어 $episode: Episode!형태입니다.
변수에 기본 값을 할당해 줄 수 있습니다.
아래는 예시입니다.
query HeroNameAndFriends($episode: Episode = JEDI) {
hero(episode: $episode) {
name
friends {
name
}
}
}
쿼리를 할 때 지시어를 작성할 수 있습니다.
지시어는 특정 필드를 포함할지 아니면 포함하지 않을지 설정할 수 있게 만들어 줍니다.
지시어로는
@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"
}
]
}
}
}
앞서 배운 것은 데이터를 가져오기 위한 방법에 대한 것이였습니다.
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!"
}
}
}
쿼리처럼 뮤테이션도 여러 필드를 가질 수 있습니다.
차이점이 있다면 쿼리의 필드는 병행적으로 실행되지만 뮤테이션의 필드는 시리즈 형식으로 하나가 끝난뒤 다음 것을 처리하는 형식으로 이루어집니다.
GraphQL의 스키마는 인터페이스나 유니온 타입을 정의할 수 있습니다.
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
위 쿼리에서 hero 필드는 Character 타입을 리턴합니다.
Character타입은 Droid타입이나 Human타입을 반환할 수 있다고 가정해봅시다.
위 쿼리는 ... on 을 이용해 각 타입에 따라 무엇을 반환할지 설정하고 있습니다.
어떤 상황에서는 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"
}
]
}
}