Queries and Mutations
이 페이지에서는 GraphQL 서버를 쿼리하는 방법에 대해 자세히 알아볼 것입니다.1)
Fields
가장 간단하게 GraphQL은 객체의 특정 필드를 요청하는 것입니다. 매우 간단한 쿼리와 실행했을 때 얻은 결과부터 살펴보겠습니다.2)
{ hero { name } }
{ "data": { "hero": { "name": "R2-D2" } } }
쿼리가 결과와 정확히 같은 모양을 하고 있음을 즉시 확인할 수 있습니다. 이것은 GraphQL에 필수적입니다. 왜냐하면 항상 예상한 대로 돌려받고 서버는 클라이언트가 요구하는 필드를 정확히 알고 있기 때문입니다.3)
필드 name
은 String
유형을 반환합니다. 이 경우 Star Wars의 주요 영웅인 "R2-D2"
의 이름입니다.4)
아, 한 가지 더 - 위의 쿼리는 대화형입니다. 즉, 원하는 대로 변경하고 새 결과를 볼 수 있습니다. 쿼리의 영웅 개체에 appearsIn
필드를 추가하고 새 결과를 확인합니다.5)
이전 예에서 우리는 문자열을 반환한 영웅의 이름을 요청했지만 필드는 객체를 참조할 수도 있습니다. 이 경우 해당 개체에 대한 필드의 하위 선택을 만들 수 있습니다. GraphQL 쿼리는 관련 개체 및 해당 필드를 순회할 수 있으므로 클라이언트가 고전적인 REST 아키텍처에서 필요로 하는 여러 왕복을 만드는 대신 한 번의 요청으로 많은 관련 데이터를 가져올 수 있습니다.6)
{ hero { name # Queries can have comments! friends { name } } }
{ "data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } }
이 예에서 friends
필드는 항목 배열을 반환합니다. GraphQL 쿼리는 단일 항목 또는 항목 목록 모두에 대해 동일하게 보이지만 스키마에 표시된 내용을 기반으로 예상되는 항목을 알고 있습니다.7)
Arguments
우리가 할 수 있는 유일한 것이 객체와 그 필드를 순회하는 것이라면 GraphQL은 이미 데이터 가져오기에 매우 유용한 언어가 될 것입니다. 그러나 필드에 인수를 전달하는 기능을 추가하면 상황이 훨씬 더 흥미로워집니다.8)
{ human(id: "1000") { name height } }
{ "data": { "human": { "name": "Luke Skywalker", "height": 1.72 } } }
REST와 같은 시스템에서는 단일 인수 집합(요청의 쿼리 매개변수 및 URL 세그먼트)만 전달할 수 있습니다. 그러나 GraphQL에서는 모든 필드와 중첩 객체가 고유한 인수 집합을 얻을 수 있으므로 GraphQL을 여러 API 가져오기를 완전히 대체할 수 있습니다. 모든 클라이언트에서 개별적으로가 아니라 서버에서 한 번만 데이터 변환을 구현하기 위해 인수를 스칼라 필드에 전달할 수도 있습니다.9)
{ human(id: "1000") { name height(unit: FOOT) } }
{ "data": { "human": { "name": "Luke Skywalker", "height": 5.6430448 } } }
인수는 다양한 유형이 될 수 있습니다. 위의 예에서는 유한한 옵션 세트(이 경우 길이 단위, METER
또는 FOOT
) 중 하나를 나타내는 열거형을 사용했습니다. GraphQL은 기본 유형 세트와 함께 제공되지만 GraphQL 서버는 전송 형식으로 직렬화할 수 있는 한 자체 사용자 정의 유형을 선언할 수도 있습니다.10)
Aliases
예리한 눈을 가진 경우 결과 개체 필드가 쿼리의 필드 이름과 일치하지만 인수가 포함되지 않기 때문에 다른 인수를 사용하여 동일한 필드를 직접 쿼리할 수 없다는 것을 알았을 것입니다. 이것이 별칭이 필요한 이유입니다. 별칭을 사용하면 필드 결과의 이름을 원하는 이름으로 바꿀 수 있습니다.11)
{ empireHero: hero(episode: EMPIRE) { name } jediHero: hero(episode: JEDI) { name } }
{ "data": { "empireHero": { "name": "Luke Skywalker" }, "jediHero": { "name": "R2-D2" } } }
위의 예에서 두 hero
필드는 충돌했지만 다른 이름으로 별칭을 지정할 수 있으므로 한 번의 요청으로 두 결과를 모두 얻을 수 있습니다.12)
Fragments
앱에 상대적으로 복잡한 페이지가 있어서 두 명의 영웅을 친구와 함께 나란히 볼 수 있다고 가정해 보겠습니다. 이러한 쿼리가 빠르게 복잡해질 수 있다고 상상할 수 있습니다. 비교의 각 측면에 대해 필드를 한 번 이상 반복해야 하기 때문입니다.13)
이것이 GraphQL에 조각이라는 재사용 가능한 단위가 포함된 이유입니다. 조각을 사용하면 필드 집합을 구성한 다음 필요한 쿼리에 포함할 수 있습니다. 다음은 프래그먼트를 사용하여 위의 상황을 해결할 수 있는 방법의 예입니다.14)
{ 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" } ] } } }
필드가 반복되면 위의 쿼리가 얼마나 반복되는지 알 수 있습니다. 프래그먼트의 개념은 복잡한 애플리케이션 데이터 요구 사항을 더 작은 청크로 분할하는 데 자주 사용됩니다. 특히 여러 UI 구성 요소를 서로 다른 프래그먼트를 사용하여 하나의 초기 데이터 가져오기로 결합해야 할 때 그렇습니다.15)
Using variables inside fragments
조각이 쿼리 또는 변형에 선언된 변수에 액세스할 수 있습니다. Variables를 참조하십시오. 16)
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" } } ] } } } }
Operation Name
지금까지 쿼리 키워드와 쿼리 이름을 모두 생략하는 축약형 구문을 사용했지만 프로덕션 앱에서는 코드를 덜 모호하게 만드는 데 사용하는 것이 유용합니다.17)
다음은 작업 유형으로 키워드 query
를 포함하고 작업 이름으로 HeroNameAndFriends
를 포함하는 예입니다.18)
query HeroNameAndFriends { hero { name friends { name } } }
{ "data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } }
작업 유형은 쿼리, 변형 또는 구독이며 수행하려는 작업 유형을 설명합니다. 쿼리 약식 구문을 사용하지 않는 한 작업 유형이 필요합니다. 이 경우 작업에 대한 이름이나 변수 정의를 제공할 수 없습니다.19)
작업 이름은 작업에 대한 의미 있고 명시적인 이름입니다. 다중 작업 문서에서만 필요하지만 디버깅 및 서버 측 로깅에 매우 유용하므로 사용을 권장합니다. 문제가 발생하면(네트워크 로그 또는 GraphQL 서버 로그에 오류가 표시됨) 내용을 해독하는 대신 이름으로 코드베이스의 쿼리를 식별하는 것이 더 쉽습니다. 이것을 좋아하는 프로그래밍 언어의 함수 이름처럼 생각하십시오. 예를 들어 JavaScript에서는 익명의 함수로만 쉽게 작업할 수 있지만 함수에 이름을 지정하면 함수를 추적하고 코드를 디버그하고 호출될 때 기록하기가 더 쉽습니다. 마찬가지로 GraphQL 쿼리 및 변형 이름은 프래그먼트 이름과 함께 서버 측에서 다양한 GraphQL 요청을 식별하는 데 유용한 디버깅 도구가 될 수 있습니다.20)
Variables
지금까지 쿼리 문자열 안에 모든 인수를 작성했습니다. 그러나 대부분의 응용 프로그램에서 필드에 대한 인수는 동적입니다. 예를 들어 관심 있는 스타워즈 에피소드, 검색 필드 또는 필터 집합을 선택할 수 있는 드롭다운이 있을 수 있습니다.21)
이러한 동적 인수를 쿼리 문자열에 직접 전달하는 것은 좋은 생각이 아닙니다. 그러면 클라이언트 측 코드가 런타임에 쿼리 문자열을 동적으로 조작하고 GraphQL 관련 형식으로 직렬화해야 하기 때문입니다. 대신 GraphQL에는 쿼리에서 동적 값을 인수분해하고 별도의 사전으로 전달하는 일급 방법이 있습니다. 이러한 값을 변수라고 합니다. 22)
변수 작업을 시작할 때 세 가지 작업을 수행해야 합니다.23)
- 쿼리의 정적 값을
$variableName
으로 바꿉니다. - 쿼리에서 허용하는 변수 중 하나로
$variableName
을 선언합니다.
query HeroNameAndFriends($episode: Episode) { hero(episode: $episode) { name friends { name } } }
VARIABLES
{ "episode": "JEDI" }
{ "data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } }
이제 클라이언트 코드에서 완전히 새로운 쿼리를 생성할 필요 없이 단순히 다른 변수를 전달할 수 있습니다. 이것은 또한 일반적으로 쿼리의 어떤 인수가 동적일 것으로 예상되는지를 나타내는 좋은 방법입니다. 사용자가 제공한 값에서 쿼리를 구성하기 위해 문자열 보간을 수행해서는 안 됩니다.25)
Variable definitions
변수 정의는 위 쿼리에서 ($episode: Episode)
처럼 보이는 부분입니다. 유형이 지정된 언어의 함수에 대한 인수 정의처럼 작동합니다. 여기에는 $
가 접두사로 붙은 모든 변수가 나열되고, 그 뒤에 해당 유형(이 경우에는 Episode
)이 표시됩니다.26)
선언된 모든 변수는 스칼라, 열거형 또는 입력 개체 유형이어야 합니다. 따라서 복잡한 개체를 필드에 전달하려면 서버에서 일치하는 입력 유형을 알아야 합니다. 스키마 페이지에서 입력 개체 유형에 대해 자세히 알아보세요.27)
변수 정의는 선택 사항이거나 필수 사항일 수 있습니다. 위의 경우에는 !
가 에피소드 유형 옆에 없으므로 선택 사항입니다. 그러나 변수를 전달하는 필드에 null이 아닌 인수가 필요한 경우에는 변수가 필수 입니다.28)
이러한 변수 정의의 구문에 대해 자세히 알아보려면 GraphQL 스키마 언어를 배우는 것이 좋습니다. 스키마 언어는 스키마 페이지에 자세히 설명되어 있습니다.29)
Default variables
유형 선언 뒤에 기본값을 추가하여 쿼리의 변수에 기본값을 할당할 수도 있습니다.30)
query HeroNameAndFriends($episode: Episode = JEDI) { hero(episode: $episode) { name friends { name } } }
모든 변수에 대해 기본값이 제공되면 변수를 전달하지 않고 쿼리를 호출할 수 있습니다. 변수 사전의 일부로 변수가 전달되면 기본값을 무시합니다.31)
Directives
위에서 변수를 사용하여 동적 쿼리를 구성하기 위해 수동 문자열 보간을 수행하지 않도록 하는 방법에 대해 논의했습니다. 인수에 변수를 전달하면 이러한 문제의 꽤 큰 부류가 해결되지만 변수를 사용하여 쿼리의 구조와 모양을 동적으로 변경하는 방법도 필요할 수 있습니다. 예를 들어 요약 및 상세 보기가 있는 UI 구성요소를 상상할 수 있습니다. 여기서 하나는 다른 것보다 더 많은 필드를 포함합니다.32)
query Hero($episode: Episode, $withFriends: Boolean!) { hero(episode: $episode) { name friends @include(if: $withFriends) { name } } }
VARIABLES
{ "episode": "JEDI", "withFriends": false }
{ "data": { "hero": { "name": "R2-D2" } } }
위의 변수를 편집하여 대신 withFriends
에 대해 true
를 전달하고 결과가 어떻게 변경되는지 보십시오.33)
지시문이라고 하는 GraphQL의 새로운 기능을 사용해야 했습니다. 지시문은 필드 또는 조각 포함에 첨부될 수 있으며 서버가 원하는 방식으로 쿼리 실행에 영향을 줄 수 있습니다. 핵심 GraphQL 사양에는 사양을 준수하는 GraphQL 서버 구현에서 지원해야 하는 정확히 두 개의 지시문이 포함됩니다.34)
지시문은 쿼리에서 필드를 추가 및 제거하기 위해 문자열 조작을 수행해야 하는 상황에서 벗어나는 데 유용할 수 있습니다. 서버 구현은 완전히 새로운 지시문을 정의하여 실험적 기능을 추가할 수도 있습니다.37)
Mutations
GraphQL에 대한 대부분의 논의는 데이터 가져오기에 중점을 두고 있지만 완전한 데이터 플랫폼은 서버 측 데이터도 수정할 수 있는 방법이 필요합니다.38)
REST에서 모든 요청은 결국 서버에 일부 부작용을 일으킬 수 있지만 일반적으로 GET 요청을 사용하여 데이터를 수정하지 않는 것이 좋습니다. GraphQL도 유사합니다. 기술적으로 모든 쿼리를 구현하여 데이터 쓰기를 수행할 수 있습니다. 그러나 쓰기를 유발하는 모든 작업은 mutation을 통해 명시적으로 보내야 한다는 규칙을 설정하는 것이 유용합니다.39)
쿼리와 마찬가지로 변형 필드가 객체 유형을 반환하면 중첩 필드를 요청할 수 있습니다. 이것은 업데이트 후 개체의 새 상태를 가져오는 데 유용할 수 있습니다. 간단한 mutation 예를 살펴보겠습니다.40)
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } }
VARIABLES
{ "ep": "JEDI", "review": { "stars": 5, "commentary": "This is a great movie!" } }
{ "data": { "createReview": { "stars": 5, "commentary": "This is a great movie!" } } }
createReview
필드가 새로 생성된 리뷰의 stars
및 commentary
필드를 반환하는 방법에 유의하십시오. 이것은 한 번의 요청으로 필드의 새 값을 변경하고 쿼리할 수 있기 때문에, 예를 들어 필드를 증가시킬 때 기존 데이터를 변경할 때 특히 유용합니다.41)
또한 이 예에서 전달한 review
변수가 스칼라가 아님을 알 수 있습니다. 이것은 입력 객체 유형이며 인수로 전달할 수 있는 특별한 유형의 객체 유형입니다. 스키마 페이지에서 입력 유형에 대해 자세히 알아보세요.42)
Multiple fields in mutations
mutation은 쿼리와 마찬가지로 여러 필드를 포함할 수 있습니다. 쿼리와 mutation 사이에는 이름 외에 한 가지 중요한 차이점이 있습니다. 43)
쿼리 필드가 병렬로 실행되지만 동안 mutation 필드는 차례로 차례로 실행됩니다.44)
즉, 한 요청에서 두 개의 mutation을 보내면 incrementCredits
첫 번째 mutation이 두 번째 시작 전에 완료되도록 보장되어 우리 자신과의 경쟁 조건으로 끝나지 않도록 합니다.45)
Inline Fragments
다른 많은 유형 시스템과 마찬가지로 GraphQL 스키마에는 인터페이스 및 공용체 유형을 정의하는 기능이 포함되어 있습니다. 스키마 가이드에서 이에 대해 알아보세요.46)
인터페이스 또는 공용체 유형을 반환하는 필드를 쿼리하는 경우 인라인 조각 을 사용하여 기본 구체 유형의 데이터에 액세스해야 합니다. 예를 들어 보면 가장 쉽게 볼 수 있습니다.47)
query HeroForEpisode($ep: Episode!) { hero(episode: $ep) { name ... on Droid { primaryFunction } ... on Human { height } } }
VARIABLES
{ "ep": "JEDI" }
{ "data": { "hero": { "name": "R2-D2", "primaryFunction": "Astromech" } } }
이 쿼리에서 hero
필드는 에피소드 인수에 따라 Human
또는 Droid
가 될 수 있는 Character
유형을 반환합니다. 직접 선택에서는 name
과 같이 Character 인터페이스에 존재하는 필드만 요청할 수 있습니다.48)
구체적인 유형에 대한 필드를 요청하려면 유형 조건이 있는 Inline Fragments을 사용해야 합니다. 첫 번째 조각은 Droid
에서 ...
로 레이블이 지정되어 있기 때문에 primaryFunction
필드는 Hero에서 반환된 Character가 Droid 유형인 경우에만 실행됩니다. Human
유형의 height
필드와 유사합니다.49)
명명된 프래그먼트에는 항상 유형이 첨부되어 있으므로 명명된 프래그먼트도 같은 방식으로 사용할 수 있습니다.50)
Meta fields
GraphQL 서비스에서 어떤 유형을 반환할지 모르는 상황이 있다는 점을 감안할 때 클라이언트에서 해당 데이터를 처리하는 방법을 결정할 방법이 필요합니다. GraphQL은 메타 필드 __typename
을 요청하여 쿼리의 어느 지점에서든 해당 지점의 개체 유형 이름을 가져올 수 있습니다.51)
{ 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" } ] } }
위 search
쿼리에서는 세 가지 옵션 중 하나가 될 수 있는 공용체 유형을 반환합니다. __typename
필드 없이 클라이언트와 다른 유형을 구별하는 것은 불가능합니다.52)
GraphQL 서비스는 몇 가지 메타 필드를 제공하며 나머지는 Introspection 시스템을 노출하는 데 사용됩니다.53)
Continue Reading
관련 문서
METER
or FOOT
). GraphQL comes with a default set of types, but a GraphQL server can also declare its own custom types, as long as they can be serialized into your transport format.$variableName
2. Declare
$variableName
as one of the variables accepted by the query3. Pass
variableName: value
in the separate, transport-specific (usually JSON) variables dictionary($episode: Episode)
in the query above. It works just like the argument definitions for a function in a typed language. It lists all of the variables, prefixed by $
, followed by their type, in this case Episode.incrementCredits
mutations in one request, the first is guaranteed to finish before the second begins, ensuring that we don't end up with a race condition with ourselves.__typename
, a meta field, at any point in a query to get the name of the object type at that point.