GraphQL의 중첩된 필드를 그래프의 계층 구조로 표현하면 트리 구조로 표현할 수 있게 됩니다. 즉, GraphQL은 트리 구조로 쿼리 결과를 받기 위해 그래프를 탐색하는 쿼리 언어라고 볼 수 있습니다.
REST API에서는 여러 Resource에 접근하고자 할 때 여러 번의 요청이 필요하지만, GraphQL에서는 한번의 요청에서 여러 Resource에 접근할 수 있습니다.
서버로부터 데이터를 조회(Read)하는 경우, REST API에선 GET 요청이 있었다면 GraphQL에서는 Query를 이용해 원하는 데이터를 요청할 수 있습니다. 또한 Create, Delete와 같이 저장된 데이터를 수정하는 경우에는 Mutation을 이용해 이를 수행할 수 있습니다.
더 나아가 GraphQL에서는 구독(Subscription)이라는 개념을 제공하며 이를 이용해 실시간 업데이트를 구현할 수 있습니다.
Subscription는 전통적인 Client(요청)-Server(응답) 모델을 따르는 Query 또는 Mutation과 달리, 발행/구독(pub/sub) 모델을 따릅니다. 클라이언트가 어떤 이벤트를 구독하면, 클라이언트는 서버와 WebSocket을 기반으로 지속적인 연결을 형성하고 유지하게 됩니다. 그 후 특정 이벤트가 발생하면, 서버는 대응하는 데이터를 클라이언트에 푸시해줍니다.
Query: 저장된 데이터 가져오기 (REST의 GET과 비슷합니다.)
Mutation: 저장된 데이터 수정하기
Create: 새로운 데이터 생성
Update: 기존의 데이터 수정
Delete: 기존의 데이터 삭제
Subscription: 특정 이벤트가 발생 시 서버가 대응하는 데이터를 실시간으로 클라이언트에게 전송
매우 간단한 query를 실행했을 때 결과를 보자.
{
hero{
name
}
}
{
"data":{
"hero":{
"name": "R2-D2"
}
}
}
다른 예시를 보자)
히어로의 이름과 친구 이름을 같이 쿼리하는 중
{
hero{
name
#이런 식으로 graphql 안에 주석 작성 가능
friends{
name
}
}
}
결과
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
=> 이처럼 필드를 중첩하여 쿼리 가능
필드에 인수를 전달하는 부분을 추가하게 되면, 쿼리의 필드 및 중첩된 객체들에 전달하여 원하는 데이터만 받아올 수 있다.
//id가 1000인 human의 name과 height를 쿼리
{
human(id: "1000"){
name
height
}
}
// 쿼리 결과
{
"data":{
"human":{
"name":"이유정",
"height": "168"
}
}
}
필드 이름을 중복해서 사용할 수 없으므로, 필드 이름을 중복으로 사용해서 쿼리를 해야 할 때는 별명을 붙여서 쿼리를 한다.
// 중복해 쿼리할 수 없다.
hero(episode: EMPIRE) {
name
}
hero(episode: JEDI) {
name
}
}
//앞에 알아볼 수 있는 별명을 붙여주면 쿼리할 수 있다.
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
//쿼리 결과
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
여태껏 쿼리와 쿼리 네임을 모두 생략하는 축약형 구문을 사용했습니다만, 실제 앱에서는 코드를 모호하지 않게 작성해야 한다.
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
//결과
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
여태껏 고정된 인수를 받아 쿼리했지만, 실제 앱을 사용할 때는 고정된 인수를 받는 것보다는 동적으로 인수를 받아 쿼리하는 경우가 많다. 변수는 그런 인수들을 동적으로 받고 싶을 때 사용한다.
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
GraphQL은 보통 데이터를 가져오는 데에 중점을 두지만, 서버측 데이터를 수정하기도 한다.
REST API에서 GET 요청을 사용하여 데이터를 수정하지 않고, POST 혹은 PUT 요청을 사용하는 것처럼 GraphQL도 유사하다. GraphQL은 mutation이라는 키워드를 사용하여 서버 측 데이터를 수정한다.
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
GraphQL 스키마의 가장 기본적인 구성 요소는 서비스에서 가져올 수 잇는 객체의 종류와 포함하는 필드를 나타내는 객체 유형이다.
type Character {
name: String!
appearsIn: [Episode!]!
}
요청에 대한 응답을 결정해주는 함수로써 GraphQL의 여러 가지 타입 중 Query, Mutation, Subscription과 같은 타입의 실제 일하는 방식 즉 로직을 작성한다.
다시 말해 위와 같이 스키마를 정의하면 그 스키마 필드에 사용되는 함수의 실제 행동을 Resolver에서 정의한다.
이러한 함수들이 모여 있기 때문에 보통 Resolvers라 부른다.
const db = require("./../db")
const resolvers = {
Query: { // **Query :** 저장된 데이터 가져오기 (REST 에 GET 과 비슷합니다.)
getUser: async (_, { email, pw }) => {
db.findOne({
where: { email, pw }
}) ... // 실제 디비에서 데이터를 가져오는 로직을 작성합니다.
...
}
},
Mutation: { // **Mutation :** 저장된 데이터 수정하기 ( Create , Update , Delete )
createUser: async (_, { email, pw, name }) => {
...
}
}
Subscription: { // **Subscription :** 실시간 업데이트
newUser: async () => {
...
}
}
};
GraphQL에서는 데이터를 가져오는 구체적인 과정을 직접 구현해야 하는데 이와 같은 작업(e.g. 데이터베이스 쿼리, 원격 API 요청)을 Resolver가 담당한다.