graphQL은 API를 위한 쿼리 언어이며 타입 시스템을 사용하여 쿼리를 실행하는 서버사이드 런타임이다. 특정 데이터베이스나 특정한 스토리지 엔진과 관계되어 있지 않으며 기존 코드와 데이터에 의해 대체된다.
graphQL
에서graph
가 붙은 이유는 데이터 구조를graph
구조의 관점으로 보고 있어서이다. 예를 들어 아래와 같이 데이터가 있다고 했을 때{ person: { name: 'gildong' gender: 'man', job: 'thief' } }
각 필드는 정점(node)을 나타내고 중괄호는 해당 필드의 간선을 나타내는 역할을 한다.
그렇기 때문에 person이라는 필드는 각각 name, gender, job이라는 필드와 연결된 형태의 graph 구조가 된다. 필드들의 관계(1:1, 1:n, n:m)를 나타내는 것은 책을 통해 자세히 보길 바란다.
graphQL
은 REST API
를 대체하기 위해 처음 나온게 아니라 REST API
의 단점을 보완하기 위해 등장했다고 한다. REST API
가 처음 나왔을 때 굉장히 유용했지만, 사용하다 보니 특정 상황들에서 불편함이 커져갔다고 한다.
REST API
를 통해 데이터 요청을 하다보면 필요 이상의 데이터를 가져오는 경우가 생기기 시작했다.
기존 API에 필요한 데이터를 추가해서 받고 싶은 경우 API를 추가로 요청해야 하는 경우가 잦아졌다.
위 1, 2번 경우가 자주 생겨 클라이언트 요구사항이 들어오면 매번 엔트리 포인트를 추가하고 관리해주어야 한다.
위와 같은 단점들 때문에 이를 해소하기 위해 graphQL
이 등장하게 되었고 불편함을 해소하기 위해 나왔던 API는 이제 완전히 REST API
를 대체하게 되었다.
graphQL
의 작업 타입을 통해 현재 쿼리의 역할을 지정할 수 있다. 각 작업 타입들은 스키마 작성시 root 타입을 정의할 수 있다.
스키마 작성과 관련된 내용은 다음 포스트에서 다루도록 하겠다.
제공되는 작업 타입들은 아래와 같다.
데이터를 가져오기 위한 전반적인 행위를 뜻한다. 이론적으로는 query
타입을 통해서도 데이터를 변경할 수 있지만 사용하지 않는 것이 좋다.
mutation
은 사용자가 어플리케이션에서 데이터를 조작할 수 있는 모든 행위를 뜻한다. 해당 타입은 백엔드 데이터에 직접적으로 영향을 줄 수 있기 때문에 반드시 그것을 인지하고 작성해야 한다.
다중필드
mutation
은query
와 마찬가지로 여러 필드를 포함한다. 다만 주의해야 할 점은query
필드는 병렬로 실행 되지만mutation
필드는 하나씩 차례대로 실행된다는 것 이다.
하나의 요청에 두 개의mutation
을 보내면 첫 번째는 반드시 두 번째 실행전에 완료되는 것이 보장된다.
데이터 추가, 변경을 구독하기 위한 행위이다. 실시간 데이터 반영을 해야 할 때 유용하게 쓸 수 있다. subscription
이 시작되면 웹소켓이 열리고 서버와 통신하게 된다. query
, mutation
과 다르게 subscription
은 계속 열려 있기 때문에 필요하지 않은 시점에 해지 해주어야 한다.
graphQL
은 쿼리를 작성하여 원하는 필드를 가져올 수 있다. 각 필드들은 하위 필드를 작성할 수 있으며 하위 필드로 객체 타입
, 스칼라 타입
이 올 수있다. 또한 연관된 객체와 필드를 가져올 수 있기 때문에 여러번 서버에 요청할 필요가 없이 필요한 데이터를 손쉽게 가져올 수 있다.
이러한 특징 때문에 쿼리 작성시 단일 아이템 또는 배열에 상관없이 동일한 형태로 작성이 가능하다. 필드는 스키마를 기반으로 작성되기 때문에 필드를 보고 반환받을 데이터의 구조를 예상하기 쉽다.
graphQL
은 필드에 인자를 전달할 수 있다. REST API
는 단일 인자들만 전달가능하지만 graphQL
은 모든 필드와 중첩된 객체에 인자 전달이 가능하다. 또한 인자들은 다양한 타입으로 넘겨주는 것이 가능하다.
{
human(id: "1000") {
name
height
}
}
쿼리를 작성하다 보면 종종 동일 필드를 각각 가져와야 할 때가 있는데, 이 때 유용한 기능이 별칭 지정이다. 각 필드의 별칭을 지정하여 원하는 명칭으로 가져오는 것이 가능하다.
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
필요한 필드셋을 정의하여 재사용 할 수 있는 단위를 프래그먼트라고 한다. 복잡한 필드셋들이 반복 된다면 프래그먼트로 정의하여 재사용하는 것이 좋다.
복잡한 응용 프로그램의 데이터 요구사항을 작은 단위로 분할하는데 사용하며, 특히 청크가 다른 ui 구성 요소를 하나의 초기 데이터 fetch로 통합하는 경우 많이 사용한다고 한다. 프래그먼트 안에서도 변수 사용이 가능하다.
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
graphQL
은 클라이언트 단에서 동적 값을 전달하기 위해 변수 기능 제공한다.
$variable
로 작성$variable
을 쿼리에서 받는 변수로 선언variableName: value
을 전달주의할 점은 사용자가 제공한 값으로 문자열 보간을 사용해서는 안된다는 것이다. !
키워드를 통해 필수여부를 지정할 수 있다. 또한 =
키워드를 통해 기본 값 적용도 가능하다.
필수값 설정
$variable: !Variable
기본값 적용
$variable: Variable = ‘DEFAULT’
변수를 사용하여 쿼리의 구조나 형태를 동적으로 변경하는데 사용하며, 필드나 프래그먼트 안에 삽입될 수 있다. 코어 graphQL
에는 두가지 지시어 사양을 제공하고 있다.
인자가 true인 경우에만 필드를 결과에 포함한다.
@include(if: Boolean)
인자가 true이면 해당 필드를 건너 뛴다.
@skip(if: Boolean)
필드를 추가, 제거하기 위해 문자열(쿼리)을 조작해야 하는 상황에서 유용하게 쓰인다. 지시어는 서버에 새로운 형태를 정의하여 실험적인 기능을 추가할 수도 있다.
graphQL
스키마에는 인터페이스나 유니온 타입을 정의하기 위해 인라인 프래그먼트를 제공한다. 특정 조건에 따라 반환받을 타입이 달라질 경우 인라인 프래그먼트를 사용할 수 있다. …on
키워드를 통해 특정 타입 조건을 걸 수도 있다.
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
쿼리를 통해 API 세부 스키마 정보를 조회하기 위해 제공된다. 특정 메타 필드를 통해 원하는 스키마 정보를 가져올 수 있다.
__schema
필드를 통해 API에 존재하는 모든 스키마 정보를 확인할 수 있다.
query {
__schema {
types {
name
description
}
}
}
__type
필드를 통해 넘겨받은 인수에 해당하는 필드 스키마 정보를 확인할 수 있다.
query lifetDetails {
__type(name: "Lift") {
name
fields {
name
description
type {
name
}
}
}
}
__typename
필드를 통해 현재 시점에 요청한 필드의 타입명을 알 수 있다.
{
search(text: "an") {
__typename
... on Human {
name
}
... on Droid {
name
}
... on Starship {
name
}
}
}