GraphQL은 api 통신 방식 중 REST에서 주로 사용하는 방식입니다.
버그바운티를 공부하면서 알게되었고, 정리하면서 기억하기 위해 요약해보겠습니다.

GraphQL

REST API의 대안으로 만들어진 API 쿼리 언어 입니다.

REST API의 경우 여러 정보를 받으려면 각각의 다른 URL에 요청을 보내어 서버가 정해놓은 구조 그대로 데이터를 여러번 받아와야합니다.

이 과정에서 불필요한 데이터도 같이 딸려오고요.

GraphQL은 다르게 원하는 데이터만 하나의 엔드포인트에 직접 명시하여 요청합니다.

요청 과정을 줄이고 원하는 데이터만 가져와 불필요한 요청도 확연히 줄어드는 특징이 있습니다.

query {
  user(id: 1) {
    name
    email
    posts {
      title
      createdAt
    }
  }
}

 

이렇게 하면 user의 name, email, 그리고 각 게시글의 title과 createdAt만 가져오게됩니다.

GraphQL의 동작

1. Query 

 

query는 서버에서 데이터를 조회할 때 사용합니다.
REST의 GET 요청 처럼 데이터를 가져오는 역할을 합니다.

query {
  book(id: "123") {
    title
    price
    author {
      name
    }
  }
}

 

예로 이런 요청을 보낸다면, book에서 id가 123인 데이터의 title과 price, author의 name에 대한 데이터를 줘 라는 요청이 됩니다.

그러면 서버는 클라이언트가 요청한 데이터만 그대로 반환하게 됩니다.

{
  "data": {
    "book": {
      "title": "클린 코드",
      "price": 33000,
      "author": {
        "name": "로버트 C. 마틴"
      }
    }
  }
}

 

book에 출판사나 출판일, 페이지 수 같은 다른 필드가 있었어도 클라이언트가 요청하지 않았기에 서버는 불필요한 데이터를 같이 보내지 않습니다.

 

2. Mutation - 데이터 변경

 

뮤테이션은 데이터에 대한 수정, 삭제, 생성을 할 때 사용합니다

REST의 POST, PUT, PATCH, DELETE에 해당하는 역할입니다.

mutation {
  createReview(input: { #서버의 정의된 createReview 라는 뮤테이션 호출 (함수 호출)
    bookId: "123"
    rating: 5
    comment: "정말 유익한 책입니다"
  }) { #리뷰를 생성한 다음, 아래의 데이터를 돌려줘 라는 요청
    id
    rating
    comment
    createdAt
    book {
      title
      averageRating
    }
  }
}

 

위의 요청을 별점과 리뷰를 작성하는 요청입니다.

보면 뮤테이션을 호출해서 별점과 리뷰를 작성하고, 응답으로 데이터를 받을 때 클라이언트가 원하는 데이터만 받는 형식입니다.

REST API 였다면 한 엔드포인트로 리뷰를 생성하고, 응답으로 리뷰 데이터를 받을 때 서버가 정해놓은 필드 전부가 돌아옵니다.

후에 내가 작성한 별점이 반영된 새 별점 평균을 알려고자 한다면 엔드포인트에 다시 한번 요청하여 새 별점 평균을 받아와야합니다.

 

이처럼 두 번의 요청과 불필요한 데이터를 응답받는 것을 GraphQL에서는 한 번의 요청과 필요한 데이터만을 응답받아 별도의 추가 요청이 필요없게 됩니다.

 

3. Subscription - 실시간 데이터

 

서브스크립션은 서버에서 특정한 이벤트가 발생할 때 클라이언트에게 자동으로 데이터를 푸시하는 방식입니다.

재고의 변동이나 배송 현황 같은 기능들을 서비스 할 때 주로 사용합니다.

subscription {
  bookStockChanged(bookId: "123") { #bookStockChanged라는 서브스크립션에 대해 요청 (함수 요청)
    bookId
    title
    stock
    status
  }
}

 

위와 같은 서브스크립션을 등록해두면, 누군가 책을 구매해서 재고가 변할 때 마다 저 데이터들을 서버가 자동으로 알려줍니다.

 

// 첫 번째 이벤트 (1권 구매)
{
  "data": {
    "bookStockChanged": {
      "bookId": "123",
      "title": "클린 코드",
      "stock": 4,
      "status": "IN_STOCK"
    }
  }
}

// 두 번째 이벤트 (마지막 구매, 재고 소진 알림)
{
  "data": {
    "bookStockChanged": {
      "bookId": "123",
      "title": "클린 코드",
      "stock": 0,
      "status": "OUT_OF_STOCK"
    }
  }
}

 

이런식으로 실시간 데이터와 상태를 받아야 할 때 서브스크립션이 사용됩니다.

서버 측 스키마 정의

위와 같은 세가지 동작을 위해서는, 서버에 미리 이런 기능들을 정의해둬야합니다. 

그걸 스키마 정의라고 합니다.

서버가 클라이언트에게 이러이러한 기능들을 제공할께 라고 선언하는 부분입니다.

이 스키마가 존재함으로써 어떠한 기능들이 있는지 한 눈에 알아볼 수 있습니다.

또한 타입을 미리 정의함으로써 타입 안정성 역시 보장되는 장점이 있습니다.

 

위에서 예시 든 동작들에 대해서 아래와 같이 선언하겠습니다.

type Query {
  book(id: ID!): Book
  books(category: String, limit: Int): [Book]
  user(id: ID!): User
}

type Mutation {
  createReview(input: ReviewInput!): Review
  updateCartItem(itemId: ID!, quantity: Int!): CartItem
  deleteReview(id: ID!): DeleteResult
}

type Subscription {
  bookStockChanged(bookId: ID!): StockUpdate
  orderStatusUpdated(orderId: ID!): OrderStatus
}

 

 

각각 쿼리 타입, 뮤테이션 타입, 서브스크립션 타입에 대한 정의입니다.

쿼리 타입부터 보겠습니다.

type Query {
  book(id: ID!): Book
  books(category: String, limit: Int): [Book]
  user(id: ID!): User
}

 

클라이언트가 조회할 수 있는 것들의 목록입니다.

 

1. book(id: ID!): Book 

id를 필수로 받아서 Book 하나에 대한 데이터를 반환한다는 뜻입니다.

이때 요청받는 값에 !가 붙어있다면 필수로 받아야하는 값이라는 뜻이고, 아니라면 선택적으로 받아야하는 값입니다.

 

2. books(category: String, limit: Int): [Book]

category와 limit에 대해 선택적으로 값을 받고, Book의 배열을 반환한다는 뜻입니다.

카테고리나 리미트가 없으면 Book 전체를 돌려줄 것이고, 카테고리가 있다면 그 카테고리의 Book만, 리미트가 있다면 리미트 만큼만 돌려줄 것 입니다.

 

3. user(id: ID!): User

id를 필수로 받아서 그 id에 맞는 유저를 반환한다는 뜻입니다.

 

다음은 뮤테이션 타입입니다.

type Mutation {
  createReview(input: ReviewInput!): Review
  updateCartItem(itemId: ID!, quantity: Int!): CartItem
  deleteReview(id: ID!): DeleteResult
}

 

이건 클라이언트가 변경할 수 있는 것들의 목록입니다.

 

1. createReview(input: ReviewInput!): Review

리뷰 작성 기능입니다.

ReviewInput의 경우 별도로 정의된 입력타입이고, Review의 경우 서버가 돌려주는 데이터의 형식으로 별도로 선언된것 입니다.

아래와 같다고 가정하겠습니다.

input ReviewInput {
  bookId: ID!
  rating: Int!
  comment: String
}

type Review {
  id: ID!
  rating: Int!
  comment: String
  createdAt: String!
  book: Book!
}

 

즉 클라이언트가 bookID와 별점인 rating, 댓글인 comment를 변경하고자 요청하면

서버는 id와 별점, 댓글과 작성된 시간, 그리고 그 책에 대한 정보를 반환해주는 형태입니다.

 

2. updateCartItem(itemId: ID!, quantity: Int!): CartItem

카트에 들어있는 아이템의 갯수를 변경하는 기능합니다.

바꾸고자 하는 아이템의 id와 갯수를 필수로 받고 카트의 아이템들의 상태를 돌려줍니다.

 

3. deleteReview(id: ID!): DeleteResult

삭제할 리뷰에 대한 기능입니다.

삭제할 리뷰의 id를 필수로 받고, 삭제 후 응답을 돌려줍니다.

 

마지막으로 서브스크립션 타입입니다.

type Subscription {
  bookStockChanged(bookId: ID!): StockUpdate
  orderStatusUpdated(orderId: ID!): OrderStatus
}

 

클라이언트가 실시간으로 받을 수 있는 이벤트 목록입니다.

 

1. bookStockChanged(bookId: ID!): StockUpdate

특정 책에 대한 제고를 보여주는 기능입니다.

특정 책에 대한 id를 필수로 받고, 그 책의 재고가 변할 때 마다 업데이트를 받겠다는 의미입니다.

 

2. orderStatusUpdated(orderId: ID!): OrderStatus

특정 주문의 상태가 바뀔 때 마다 업데이트 해주는 기능입니다.

주문의 id를 필수로 받고 주문의 상태가 바뀌면 알림을 준다는 기능입니다.

+ Recent posts