본문 바로가기
Dev/database

웹 앱 API 개발을 위한 GraphQL (study)

by Luigi.yoon 2023. 4. 25.

웹 앱 API 개발을 위한 GraphQL

 

1, 2 장 설명 등은 생략하고 쿼리 중심으로 학습합니다. 

 

3장 GraphQL 쿼리어

GraphQL 은 인터넷용 쿼리 언어입니다.

쿼리는 단순한 문자열로, POST 요청 본문에 담겨 GraphQL 엔드포인트로 보내집니다. GraphQL 은 다음과 같이 생겼습니다.

{
    allLifts {
        name
    }
}
 

 

cURL 을 사용해 GraphQL 엔드포인트로 쿼리를 보내려면 다음과 같이 하면 됩니다.

curl 'http://snowtooth.herokuapp.com/'
  -H 'Content-Type: application/json'
  --data '{"query":"{allLifts {name }}"}'
 

 

 

데이터를 수정하려면 뮤테이션(mutation) 을 사용하면 됩니다. 다음처럼 뮤테이션과 함께 데이터를 넣어 보내면 됩니다.

mutation {
    setLiftStatus(id: "panorama" status: OPEN) {
        name
        status
    }
}

 

cURL 을 사용해 GraphQL 서버에 mutation 요청을 보낼 수 있습니다.

 
curl 'http://snowtooth.herokuapp.com/'
  -H 'Content-Type: application/json'
  --data '{"query":"mutation {setLiftStatus(id: \"panorama\" status: OPEN) {name status}}"}'

3.1 GraphQL API 툴

3.1.1 GraphiQL

3.1.2 GraphQL playground

3.1.3 공용 GraphQL API


 

3.2 GraphQL Query

쿼리 작업으로 API 에 데이터를 요청할 수 있습니다. 쿼리 안에는 GraphQL 서버에서 받고 싶은 데이터를 써넣습니다. GraphQL 쿼리어에서 필드는 scala 타입과 object 타입 둘 중 하나에 속합니다.

  • scala 타입 : Int, Float, String, Boolean, ID(유일한 문자열)
  • object 타입 : 스키마에 정의한 필드를 그룹으로 묶어 둔 것 입니다.

쿼리를 보낼 때는 요청 데이터를 필드로 적어 넣습니다. 아래 응답에는 allLifts 배열과 각각의 리프트에 대한 name 과 status 가 들어 있습니다.

query {
    allLifts {
        name
        status
    }
}

에러 처리

정상적인 쿼리를 보내면 data 키가 들어있는 JSON 문서가 응답으로 돌아옵니다.

비정상적인 쿼리에는 응답으로 error 키가 들어 있는 JSON 문서가 돌아옵니다.

이 키의 값으로는 에러테 대한 세부적인 내용이 들어갑니다. data 와 error 키가 응답 객체에 동시에 포함된 경우도 있습니다.


 

쿼리 작업 두개를 같은 문서에 쓸 수 있습니다.

query liftsAndTrails {
    liftCount(status: OPEN)
    allLifts {
        name
        status
    }
    allTrails {
        name
        difficulty
    }
}


 

Query

  • GraphQL 루트 타입. 쿼리 문서의 루트를 의미

Selection set

  • 쿼리 안에 중괄호로 묶인 블록
  • 셀렉션 세트는 서로 중첩시킬 수 있습니다.

 

응답 객체의 필드명에 별칭을 부여할 수 있습니다.

query liftsAndTrails {
    open: liftCount(status: OPEN)
    chairlifts: allLifts {
        liftName: name
        status
    }
    skiSlopes: allTrails {
        name
        difficulty
    }
}

 

쿼리 인자(query arguments) 를 넘기면 필터링이나 데이터를 선택할 수 있습니다.

query jazzCatStatus {
    Lift(id: "jazz-cat") {
        name
        status
        night
        elevationGain
    }
}

 

GraphQL 에서는 JSON 필드처럼 객체를 중첩하여 연결 관계에 대한 쿼리를 작성할 수 있습니다.

query liftToAccessTrail {
    Trail(id:"dance-fight") {
        groomed
        accessedByLifts {
            name
            capacity
        }
    }
}

 

fragment (프래그먼트)

  • GraphQL 쿼리 안에는 각종 작업에 대한 정의와 프래그먼트에 대한 정의가 들어갈 수 있습니다.
  • 프래그먼트는 셀력션 세트의 일종이며, 여러번 재사용할 수 있습니다.
  • fragment 식별자를 사용하여 만듭니다.
  • 어떤 타입에 대한 프래그먼트인지 정의에 꼭 명시해야 합니다. (명시한 타입에서만 사용 가능)
  • 프래그먼트 이름 앞에 점 세 개를 찍어서 사용합니다.

 

liftInfo 프래그먼트 필드를 다른 셀렉션 세트에 추가하려면 프래그먼트 이름 앞에 점 세 개를 찍어 주면 됩니다.

 
fragment liftInfo on Lift {
    name
    status
    capacity
    night
    elevationGain
}


query {
    Lift(id: "jazz-cat") {
        ...liftInfo
        trailAccess {
            name
            difficulty
        }
    }
    Trail(id: "river-run") {
        name
        difficulty
        accessedByLifts {
            ...liftInfo
        }
    }
}
# 위 liftInfo 프래그먼트는 Lift 타입에 속하는 필드만 사용할 수 있기 때문에 Trail 같은 다른 타입 안에 넣는 것은 불가능합니다.

 

Union type (유니언 타입)

  • 타입 여러 개를 한 번에 리스트에 담아 반환할 때 사용합니다.
  • 여러 가지 타입을 하나의 집합으로 묶는 것입니다.

 

아래와 같이 AgendaItem 타입이 Workout 또는 StudyGroup 타입을 반환하도록 union type 설정할 수 있습니다.

 
union AgendaItem = Workout | StudyGroup

 

아래 1, 2 와 같이 union type 을 사용할 때 특정 타입에 따라 특정 필드만 선택되도록 만들 수 있습니다.

 

1. Inline fragment 를 사용하여 타입에 따라 특정 필드만 선택되도록 만들 수 있습니다.

# 타입별 필드 설정
query schedule {
    agenda {
        ...on Workout {
            name
            reps
        }
        ...on StudyGroup {
            name
            subject
            students
        }
    }
}

# 특정 타입일 때 필드 추가
query schedule {
    agenda {
        name
        start
        end
        ...on Workout {
            reps
        }
    }
}

 

2. 일반 fragment 를 사용하여 타입에 따라 특정 필드만 선택되도록 만들 수 있습니다.

query today {
    agenda {
        ...workout
        ...study
    }
}

fragment workout on Workout {
    name reps
}

fragment study on StudyGroup {
    name
    subject
    students
}

 

Interface (인터페이스)

  • 필드 하나로 객체 타입을 여러 개 반환할 때 사용합니다.
  • 추상적인 타입이며, 유사한 객체 타입을 만들 때 구현해야 하는 필드 리스트를 모아둔 것입니다.
  • 인터페이스를 가지고 타입을 구현할 때는 인터페이스에 정의된 필드는 모두 넣어야 합니다.

 

interface ScheduleItem {
    name: String!
    start: Int
    end: Int
}
type StudyGroup implements ScheduleItem {
    name: String!
    start: Int
    end: Int
    subject: String!
    students: Int!
}
type Workout implements ScheduleItem {
    name: String!
    start: Int
    end: Int
    reps: Int!
}


 

3.3 Mutation

GraphQL 에서 데이터를 수정하려면 mutation 을 사용해야 합니다.

 

1. 새로운 데이터를 생성하는 예제입니다.

mutation createSong {
    addSong(title:"No Scrubs", numberOne: true, performerName:"TLC") {
        id
        title
        numberOne
    }
}

 

2. 기존 데이터를 변경하는 예제입니다.

mutation closeLift {
    setLiftStatus(id: "jazz-cat", status: CLOSED) {
        name
        status
    }
}

 

3. 데이터를 모두 지워버리는 예제입니다.

mutation burnItDown {
    deleteAllData
}

 

3.3.1 쿼리 변수 사용하기

쿼리에 있는 정적(static) 값을 변수로 대체하여 동적인(dynamic) 값을 넣을 수도 있습니다.
GraphQL 은 변수명 앞에 $ 문자가 붙습니다.

mutation createSong($title:String! $numberOne:Int $by:String!) {
    addSong(title:$title, numberOne:$numberOne, performerName:$by) {
        id
        title
        numberOne
    }
}

 

변수 데이터를 아래와 같이 JSON 객체 형식으로 보내면 됩니다.

{
    "title": "No Scrubs",
    "numberOne": true,
    "by": "TLC"
}

 

변수와 cURL 사용 예제입니다.

curl \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{ "query": "mutation($user: UserInput!) { createUser(user: $user) { name location id } }" ,
        "variables": {
          "user": {
            "name": "Nader Dabit",
            "location": "United States",
            "id": "001"
          }
        }
      }' \
  http://some.api.com/graphql


 

3.4 Subscription

Subscription 은 GraphQL 에서 수행할 수 있는 세 번째 작업 타입입니다.
Subscription 을 사용하면 실시간 데이터 변경 내용을 받을 수 있습니다.

 

어떤 리프트의 상태 변경 내용을 구독하는 작업을문 예제입니다.

subscription {
    liftStatusChange {
        name
        capacity
        status
    }
}


 

3.5 Introspection

Introspection 을 사용하면 API 스키마의 세부 사항에 관한 쿼리를 작성할 수 있습니다.
Introspection 을 사용하면 API 스키마를 통해 어떤 데이터를 반환받을 수 있는지 조사할 수 있습니다.

 

어떤 GraphQL API 에서 사용할 수 있는 모든 타입을 알고 싶을때 예제입니다.
(루트 타입, 커스텀 타입, 스칼라 타입까지 나옵니다.)

query {
    __schema {
        types {
            name
            description
        }
    }
}

 

특정 타입에 관한 세부 사항만 보고 싶을때 예제입니다.

query liftDetails {
    __type(name:"Lift") {
        name
        fields {
            name

        }
    }
}

 

루트 타입에서 사용할 수 있는 필드 등 자세하게 보고 싶을때 예제입니다.

query {    
    __schema {      
        queryType { name }      
        mutationType { name }      
        subscriptionType { name }      
        types {         ...FullType       }      
        directives {        
            name        
            description        
            locations        
            args {  ...InputValue  }      
        }    
    }  
}   

fragment FullType on __Type {
    kind    
    name    
    description    
    fields(includeDeprecated: true) {      
        name      
        description      
        args {         ...InputValue       }      
        type {         ...TypeRef       }      
        isDeprecated      
        deprecationReason    
    }    
     
    inputFields {       ...InputValue     }    
    interfaces {       ...TypeRef     }    
    enumValues(includeDeprecated: true) {      
        name      
        description      
        isDeprecated      
        deprecationReason    
    }    
     
    possibleTypes {       ...TypeRef     }  
}
     
fragment InputValue on __InputValue {    
    name    
    description    
    type { ...TypeRef }    
    defaultValue  
}   

fragment TypeRef on __Type {    
    kind    
    name    
    ofType {      
        kind      
        name      
        ofType {        
            kind        
            name        
            ofType {          
                kind          
                name        
            }      
        }    
    }  
}


 

3.6 Abstract syntax tree

GraphQL API 로 쿼리를 보낼 때, 문자열은 Abstract syntax tree(추상 구문 트리) 로 파싱되어 실행 전에 유효성 검사를 거칩니다.
Abstract syntax tree(추상 구문 트리) , 줄여서 AST는 계층 구조를 지닌 객체로 쿼리를 표현하는 데 사용합니다.
쿼리는 어휘화(lexing 또는 어휘 분석(lexical analysis)) 과정 후 AST 형태로 가공됩니다.
쿼리를 AST 로 만들면 수정과 유효성 검사가 쉬워집니다.


 

4장 스키마 설계하기

데이터 타입의 집합을 스키마라고 부릅니다.
디자인 방법론의 일종인 스키마 우선주의(Schema first) 를 통해, 백엔드-프론트엔드 모두 같은 선상에서 이해할 수 있습니다.
GraphQL은 스키마 정의를 위해 SDL(Schema Definition Language, 스키마 정의 언어)를 지원합니다.
SDL 로 정의한 타입은 GraphQL 요청에 대한 유효성 검사에도 사용됩니다.


 

4.1 타입 정의하기


 

4.1.1 타입

타입(Type) 은 GraphQL의 커스텀 객체이며 핵심 단위입니다.
타입에는 객체의 데이터와 관련있는 필드(Field) 가 들어갑니다.
각각의 필드는 정수나 문자열 등 특정 종류의 데이터를 반환합니다.
필드 오른쪽에 느낌표가 붙으면 'non-nullable' 을, 없으면 'nullable' 을 뜻합니다.

 

GraphQL 객체 타입 정의 예제입니다.

type Photo {
    id: ID!
    name: String!
    url: String!
    description: String
}


 

4.1.2 스칼라 타입

내장 스칼라 타입 Int, Float, String, Boolean, ID(유일한 문자열) 5가지 밖에 없습니다.
graphql-custom-type npm 패키지, om.graphql-java:graphql-java-extended-scalars gradle(maven) 패키지 등을 사용하면 미리 정의된 커스텀 스칼라 타입을 사용할 수 있습니다.
커스텀 스칼라 타입을 사용하려면 상단에 'scalar Long' 이런식으로 정의해줘야 합니다.

 

DateTime 커스텀 스칼라 타입 필드 사용 예제입니다.

scalar DateTime

type Photo {
    id: ID!
    name: String!
    url: String!
    description: String
    created: DateTime!
}


 

4.1.3 열거 타입

열거 타입(enumeration type)은 스칼라 타입에 속하며, 필드에서 반환하는 문자열 세트로 미리 지정해 둘 수 있습니다.
미리 정의한 세트에 속하는 값만 필드에서 반환하도록 만들 때 열거 타입(enum)을 사용하면 됩니다.

 

enum 타입 선언 예제입니다.

enum PhotoCategory {
    SELFIE
    PORTRAIT
    ACTION
    LANDSCAPE
    GRAPHIC
}

 

enum 타입으로 필드 생성하는 예제입니다.

type Photo {
    id: ID!
    name: String!
    url: String!
    description: String
    created: DateTime!
    category: PhotoCategory!
}


 

4.2 연결과 리스트

리스트 타입은 GraphQL 타입을 대괄호로 감싸서 만듭니다.
[String] 은 문자열 리스트를 뜻합니다. 

리스트 null 적용 규칙 표

리스트 선언 정의
[Int] 리스트 안에 담긴 정수 값은 null이 될 수 있다.
[Int!] 리스트 안에 담긴 정수 값은 null이 될 수 없다.
[Int]! 리스트 안의 정수 값은 null이 될 수 있으나, 리스트 자체는 null이 될 수 없다.
[Int!]! 리스트 안의 정수 값은 null이 될 수 없고, 리스트 자체도 null이 될 수 없다.

 


 

4.2.1 일대일 연결

 

type User {
    githubLogin: ID!
    name: String
    avatar: String
}

type Photo {
    id: ID!
    name: String!
    url: String!
    description: String
    created: DateTime!
    category: PhotoCategory!
    postedBy: User!
}

 

4.2.2 일대다 연결

 

type User {
    githubLogin: ID!
    name: String
    avatar: String
    postedPhotos: [Photo!]!
}

 


4.2.3 다대다 연결

 

 
type User {
...
inPhotos: [Photo!]!
}

type Photo {
...
taggedUsers: [User!]!
}

 

4.2.4. 여러타입을 담는 리스트

3장에 나온 union type 을 리스트로 사용하는 예제입니다.

union AgendaItem = StudyGroup | Workout
type Query {
    agenda: [AgendaItem!]!
}

 


3장에 나온 인터페이스(interface) 를 리스트로 사용하는 예제입니다.

interface AgendaItem {
    name: String!
    strt: DateTime!
    end: DateTime!
}

type StudyGroup implements AgendaItem {
    name: String!
    start: DateTime!
    end: DateTime!
    participants: [User!]!
    topic: String!
}

type Workout implements AgendaItem {
    name: String!
    start: DateTime!
    end: DateTime!
    reps: Int!
}

type Query {
    agenda: [AgendaItem!]!
}

유니언 타입 리스트와 인터페이스 리스트는 여러타입을 담는 리스트로 사용됩니다.
객체에 따라 필드가 완전히 달라질 때는 유니언 타입을 사용하고,
특정 필드가 반드시 들어있어야 한다면 인터페이스를 사용합니다.


 

4.3 인자

특정 정보를 얻으려면 인자를 넣어 주어야 합니다.
인자가 필수일 때 non-nullable 필드로 정의합니다. (모든 인자가 non-nullable 일 필요는 없습니다.)
쿼리 요청 시 필수 인자 값을 넣어주지 않으면 GraphQL 파서가 에러를 반환합니다.


 

4.3.1 데이터 필터링

# 인자가 없을 때 모든 데이터를 반환합니다.
query {
    allPhotos {
        name
    }
}

# 인자를 사용해 분류된 데이터를 반환할 수 있습니다.
query {
    allPhotos(category: "SELFIE") {
        name
    }
}

 

 

Data paging

쿼리에 인자를 전달해 반환 데이터의 양을 조절할 때 데이터 페이징(Data paging) 이라 합니다.


데이터 페이징 기능을 추가하려면 옵션 인자를 2개 더 써야 합니다.

  • first 인자 : 데이터 페이지 한 장 당 들어가는 레코드 수
  • start 인자 : 첫 번째 레코드가 시작되는 인덱스, 시작 위치
type Query {
    ...
    allUsers(first: Int=50 start: Int=0): [User!]!
    allPhotos(first: Int=25 start: Int=0): [Photo!]!
}

 

 

Sorting

데이터 리스트의 Sorting(정렬) 방식을 쿼리에서 인자를 사용해 지정할 수 있습니다.

 

enum SortDirection {
    ASC
    DESC
}
enum SortablePhotoField {
    name
    description
    category
    created
}

Query {
    allPhotos(
        sort: SortDirection = DESC
        sortBY: SortablePhotoField = created
    ): [Photo!]!
}

 

4.4 Mutation

mutation 스키마 선언 예제 입니다.

type Mutation {
    postPhoto(
        name: String!
        description: String
        category: PhotoCategory = PORTRAIT
    ): Photo!
}

schema {
    query: Query
    mutation: Mutation
}

 

변수를 사용해서 mutation 호출 예제입니다.

# variables
{
    "name": "No name",
    "description": "any text",
    "category": ACTION
}


# 변수를 사용해서 mutation 호출 예제입니다.
mutation postPhoto(
    $name: String!
    $description: String
    $category: PhotoCategory
) {
    postPhoto(name: $name, description: $description, category: $category) {
        id
        name
        email
    }
}

 

 

4.5 Input type

Input type을 사용하면 인자 관리를 조검 더 체계적으로 할 수 있습니다.
Input type은 객체 타입과 비슷하나, 인자에서만 쓰입니다.

 

PhotoFilter 라는 Input type 스키마 예제입니다.

input PhotoFilter {
    category: PhotoCategory
    createdBetween: DateRange
    taggedUsers: [ID!]
    searchText: String
}

input DateRange {
    start: DateTime!
    end: DateTime!
}

input DataPage {
    first: Int = 25
    start: Int = 0
}

input DataSort {
    sort: SortDirection = DESCENDING
    sortBy: SortablePhotoField = created
}

type User {
    ...
    postedPhotos(
        filter: PhotoFilter
        paging: DataPage
        sorting: DataSort
    ): [Photo!]!
    inPhotos(filter: PhotoFilter, paging: DataPage, sorting: DataSort): [Photo!]!
}

type Photo {
    ...
    taggedUsers(sorting: DataSort): [User!]!
}

type Query {
    ...
    allUsers(paging: DataPage, sorting: DataSort): [User!]!
    allPhotos(filter: PhotoFilter, paging: DataPage, sorting: DataSort): [Photo!]!
}
  • 인풋 타입을 인자로 재사용할 수 있습니다.
  • 인자를 필터 옵션으로 활용해 필요한 데이터만 받을 수 있습니다.


 

4.6 Return type

응답에 추가적으로 데이터가 필요할 때 커스텀 객체 타입을 사용합니다.

 

인증할 때 성공한 정보화 함께 토큰을 커스텀 객체 타입에 반환하는 예제입니다.

type AuthPayload {
    user: User!
    token: String!
}

type Mutation {
    ...
    githubAuth(code: String!): AuthPayload!
}

 

 

4.7 Subscription

# schema
type Subscription {
    newPhoto(category: PhotoCategory): Photo!
    newUser: User!
}

# 원하는 내용만 걸러서 보는 subscription 예제입니다.
subscription {
    newPhoto(category: "Action") {
        id
        name
        url
        postedBy {
            name
        }
    }
}

 

4.8 Schema 문서화

GraphQL 스키마를 작성할 때는 옵션으로 각 필드에 대한 설명을 적을 수 있습니다.
설명을 잘 적어두면 API 사용자들이 스키마를 이해하는 데 도움이 됩니다.

 

"""
postPhoto 뮤테이션과 함께 전송되는 인풋 값
"""
input PostPhotoInput {
    "신규 사진명"
    name: String!
    "(옵션) 사진에 대한 간략한 설명"
    description: String
    "(옵션) 사진 카테고리"
    category: PhotoCategory=PORTRAIT
}

postPhoto(
    "인풋: 신규 사진 이름, 설명, 카테고리"
    input: PostPhotoInput!
): Photo!

 

'Dev > database' 카테고리의 다른 글

PACELC Theorem(패슬씨 정리)  (2) 2025.06.12
CAP Theorem(CAP 정리)  (1) 2025.06.12
콘솔 mysql 접속 방법  (0) 2023.11.02
Database Index 종류  (0) 2023.08.09
Redis Hash 에서 key pattern 을 이용한 삭제  (0) 2019.04.05