GraphQL

Introduction

  • API를 위해 Facebook에서 만든 쿼리 언어 (Query Language)
    • Facebook이 2012년에 개발하여 2015년에 공개적으로 발표
    • “write one, run anywhere”

배경

  • 모바일 사용 증가로 효율적인 데이터 로드에 대한 필요성
  • 다양한 프론트 엔드 프레임워크 및 플랫폼 대응
  • 빠른 개발

특징

  • Specification

    • API 디자인에 대한 스펙/방법론 (ex. REST)
    • 클라이언트가 서버에서 데이터를 로드하는 방법을 정의
    • 스펙이 스키마의 유효성 판별
    • 스카마가 클라이언트 호출의 유효성 판별
  • Application Layer

    • 정의된 스키마 기반으로 데이터 탐색 및 반환
    • 데이터 저장 방식과 무관
  • Graph: 스키마에 정의된 구조

    • 그래프는 노드(Node)와 에지(Edge)로 구성
    • GraphQL은 객체와 그 관계로 구성

REST와의 가장 큰 차이점

  • flexibillity and efficiency
  • 단 하나의 Endpoint
  • 요청 시 사용한 쿼리에 따라 각기 다른 응답

예제 상황

  • 블로그 앱에서 특정 사용자의 글 제목과 그 사용자의 최근 3명 follower 이름을 보여주려고 할 때

    • REST 경우

      REST

    • GraphQL 경우

      GraphQL

이로 인한 장점

  • HTTP 요청 횟수 감소
  • HTTP 응답 사이즈 감소
  • 서버 측 추가 작업 없이 클라이언트 수정 가능

대표 사용처

Learn

동작 방식

  • 요청 받은 쿼리에 대해 정의된 타입과 필드를 검사한 다음, 함수를 실행하여 결과를 생성하여 응답

  • 타입과 필드를 정의하고, 각 타입의 필드에 대한 함수로 구현

타입과 필드

1
2
3
4
5
6
7
8
type Query {
me: User
}

type User {
id: ID
name: String
}

쿼리

1
2
3
4
5
{
me {
name
}
}

응답 (JSON)

1
2
3
4
5
{
"me": {
"name": "Luke Skywalker"
}
}

Schema - a collection of GraphQL types

  • SDL (Schema Definition Language)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    type Query {
    allPersons(last: Int): [Person!]!
    }

    type Mutation {
    createPerson(name: String!, age: Int!): Person!
    }

    type Subscription {
    newPerson: Person!
    }

    type Person {
    name: String!
    age: Int!
    posts: [Post!]!
    }

    type Post {
    title: String!
    author: Person!
    }

Type

  • Query - fetch

    1
    2
    3
    type Query {
    allPersons(last: Int): [Person!]!
    }
  • Mutation - create / update / delete

    1
    2
    3
    type Mutation {
    createPerson(name: String!, age: Int!): Person!
    }
  • Subscription - realtime connection

    1
    2
    3
    type Subscription {
    newPerson: Person!
    }
  • 스칼라

    • 하위 필드가 없는 쿼리의 끝 부분
    • Int: 부호가 있는 32비트 정수
    • Float: 부호가 있는 부동소수점 값
    • String: UTF=8 문자열
    • Boolean: true 또는 false
    • ID: 고유 식별자를 나타내며 사람이 읽을 수 있도록 하는 의도가 아니라는 것을 의미
  • 열거형 (Enums)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    enum Weekday {
    MONDAY
    TUESDAY
    WEDNESDAY
    THURSDAY
    FRIDAY
    SATURDAY
    SUNDAY
    }
  • 리스트와 Non-Null

    1
    2
    3
    4
    5
    6
    myField: [String!]

    myField: null // valid
    myField: [] // valid
    myField: ['a', 'b'] // valid
    myField: ['a', null, 'b'] // error
    1
    2
    3
    4
    5
    6
    myField: [String]!

    myField: null // error
    myField: [] // valid
    myField: ['a', 'b'] // valid
    myField: ['a', null, 'b'] // valid
  • 인터페이스

    1
    2
    3
    interface Node {
    id: ID!
    }
    • Node구현한(implements) 모든 타입은 이러한 인자와 리턴 타입을 가져야 한다는 것을 의미

      1
      2
      3
      4
      5
      type User implements Node {
      id: ID!
      name: String!
      age: Int!
      }
  • 유니온

    1
    union Person = Adult | Child
  • 입력

    1
    2
    3
    4
    input ReviewInput {
    stars: Int!
    commentary: String
    }
    1
    2
    3
    4
    5
    6
    mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
    createReview(episode: $ep, review: $review) {
    stars
    commentary
    }
    }
    1
    2
    3
    4
    5
    6
    7
    {
    "ep": "JEDI",
    "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "data": {
    "createReview": {
    "stars": 5,
    "commentary": "This is a great movie!"
    }
    }
    }

Query & Mutation

쿼리 필드는 병렬로 실행되지만 뮤테이션 필드는 하나씩 차례대로 실행

  • 기본

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    allPersons {
    name
    age
    posts {
    title
    }
    }
    }
  • 인자

    1
    2
    3
    4
    5
    {
    allPersons(last: 2) {
    name
    }
    }
  • 별칭 (alias) - 필드의 결과를 원하는 이름으로

    1
    2
    3
    4
    5
    6
    7
    8
    {
    first: User(id: "1") {
    name
    }
    second: User(id: "2") {
    name
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "first": {
    "name": "Alice"
    },
    "second": {
    "name": "Sarah"
    }
    }
    • 별칭이 없으면 두 개 결과 모두 “User”라는 필드명으로 반환되므로
  • 프래그먼트 - a collection of fields on a specific type

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    allUsers {
    name
    age
    email
    street
    zipcode
    city
    }
    }
    1
    2
    3
    4
    5
    6
    fragment addressDetails on User {
    name
    street
    zipcode
    city
    }
    1
    2
    3
    4
    5
    6
    7
    {
    allUsers {
    age
    email
    ... addressDetails
    }
    }
    • 유니온과 활용하는 예제

      1
      2
      3
      4
      5
      6
      7
      8
      9
      type Adult {
      name: String!
      work: String!
      }

      type Child {
      name: String!
      school: String!
      }
      1
      union Person = Adult | Child
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      {
      allPersons {
      name # works for `Adult` and `Child`
      ... on Child {
      school
      }
      ... on Adult {
      work
      }
      }
      }
  • 작업 이름

    1
    2
    3
    4
    5
    6
    7
    8
    query HeroNameAndFriends {
    hero {
    name
    friends {
    name
    }
    }
    }
  • 변수

    1
    2
    3
    type Query {
    allUsers(olderThan: Int = -1): [User!]!
    }
    1
    2
    3
    4
    5
    6
    {
    allUsers(olderThan: 30) {
    name
    age
    }
    }
  • 지시어

    1
    2
    3
    4
    5
    6
    7
    8
    query Hero($episode: Episode, $withFriends: Boolean!) {
    hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
    name
    }
    }
    }
    • @include: 인자가 true인 경우에만 이 필드를 결과에 포함
    • @skip: 인자가 true인 경우 이 필드를 무시
    1
    2
    3
    4
    {
    "episode": "JEDI",
    "withFriends": false
    }
    1
    2
    3
    4
    5
    6
    7
    {
    "data": {
    "hero": {
    "name": "R2-D2"
    }
    }
    }
  • 뮤테이션 (Mutation)

    1
    2
    3
    4
    5
    6
    mutation {
    createPerson(name: "Bob", age: 36) {
    name
    age
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "data": {
    "createPerson": {
    "name": "Bob",
    "age": 36
    }
    }
    }

Resolver

Code & Playground

참고 링크

jhgu