query
는 REST API의 GET
역할(데이터 조회)을 한다.name
은 String 타입을 반환한다. 아래의 경우 hero
의 name
이 “R2-D2”임을 알 수 있다. 보이는 것처럼 쿼리와 결과가 정확하게 같은 모양을 하고 있음을 확인할 수 있는데, 이 부분은 GraphQL에 있어서 필수적이라고 볼 수 있다. GraphQL은 서버에 요청했을 때 예상했던 대로 돌려받고, 서버는 GraphQL을 통해 클라이언트가 요구하는 필드를 정확히 알기 때문이다.// 요청
// hero의 name을 쿼리
{
hero {
name
}
}
// 응답
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
freinds
필드는 배열을 반환한다. GraphQL 쿼리는 관련 객체 및 필드를 순회할 수 있기 때문에 고전적인 REST API에서 그러했듯 다양한 endpoint를 만들어 각기 요청을 보내는 대신 클라이언트가 하나의 요청을 보냄으로써 관련 데이터를 가져올 수 있다.// 요청
// 히어로의 이름과 히어로의 친구 이름을 같이 쿼리
{
hero {
name
# 이런 식으로 GraphQL 내에서 주석도 작성할 수 있습니다.
friends {
name
}
}
}
// 응답
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
필드에 인수를 전달하는 부분을 추가하게 되면 쿼리의 필드 및 중첩된 객체들에 전달하여 원하는 데이터만 받아올 수 있다.
아래와 같이 id가 1000인 human의 이름과 키를 쿼리해 올 수 있다. REST와 같은 시스템에서는 단일 인수 집합(요청의 쿼리 매개변수 및 URL 세그먼트)만 전달할 수 있다.
예를 들어 REST API를 이용한다면 ?id=1000
이거나 /1000(/:id)
일 때와 같은 목적으로 쿼리할 수 있다.
// 요청
// id가 1000인 human의 name과 height를 쿼리
{
human(id: "1000") {
name
height
}
}
// 응답
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 1.72
}
}
}
// 요청
// 다른 이름으로 별명을 지정하면 한 번의 요청으로 두 개의 결과를 모두 응답 받을 수 있다.
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
// 응답
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
위에서는 query
키워드와 query
이름을 모두 생략 한 단축 문법을 사용했지만, 실제 애플리케이션에서는 코드를 덜 헷갈리게 작성하는 것이 좋다.
앞의 query
는 오퍼레이션 타입이다. 오퍼레이션 타입에는 query
뿐만 아니라 mutation
, subscription
, describes
등이 있다.
쿼리를 약식으로 작성하지 않는 한 이런 오퍼레이션 타입은 반드시 필요하다. 오퍼레이션 네임을 작성할 때는 오퍼레이션 타입에 맞는 이름으로 작성하는 것이 가독성에 좋다.
// 요청
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
// 응답
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
변수는 인수들을 동적으로 받고 싶을 때 사용한다.
오퍼레이션 네임 옆에 변수를 $변수 이름: 타입 형태
로 정의한다. 아래의 예시처럼 $episode: Episode
일 때, 뒤에 !
가 붙는다면 episode는 반드시 Episode여야 한다는 뜻이며, !
는 옵셔널한 사항이다.
// 요청
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
mutation
은 REST API의 POST
, PUT or PATCH
, DELETE
역할(데이터 생성, 수정, 삭제)을 한다.
GraphQL은 대개 데이터를 가져오는 데에 중점을 두고 있지만 서버측 데이터를 수정하기도 한다.
REST API에서 GET
요청을 사용하여 데이터를 수정하지 않고, POST
혹은 PUT
요청을 사용하는 것처럼 GraphQL도 유사하다. GraphQL은 mutation
이라는 키워드를 사용하여 서버 측 데이터를 수정한다.
// 요청
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
type Character {
name: String!
appearsIn: [Episode!]!
}
Character
는 GraphQL 객체 타입이며, 필드가 있는 타입임을 의미한다. 스키마에 있는 대부분의 타입은 객체 타입이다.
name
과 appearIn
은 Character
타입의 필드다. 즉 name
과 appearIn
은 GraphQL 쿼리의 Character
타입 어디서든 사용할 수 있는 필드다.
String
은 내장된 스칼라 타입 중 하나다. 이는 단일 스칼라 객체로 확인되는 유형이며 쿼리에서 하위 선택을 가질 수 없다. 스칼라 타입에는 ID, Int도 있다.
!
가 붙는다면 이 필드는 nullable하지 않고 반드시 값이 들어온다는 의미이다. 이것을 붙여 쿼리한다면 반드시 값을 받을 수 있을 것이란 예상을 할 수 있다.
[ ]
는 배열을 의미하며 배열에도 !
가 붙을 수 있다. 여기서는 !
이 뒤에 붙어 있어 null 값을 허용하지 않으므로 항상 0개 이상의 요소를 포함한 배열을 기대할 수 있게 된다.
요청에 대한 응답을 결정해주는 함수로써 GraphQL의 여러 가지 타입 중 Query, Mutation, Subscription과 같은 타입의 실제 일하는 방식 즉 로직을 작성한다.
즉, 위와 같이 스키마를 정의하면 그 스키마 필드에 사용되는 함수의 실제 행동을 Resolver에서 정의한다. 또한 이러한 함수들이 모여 있기 때문에 보통 Resolvers라고 부른다.
GraphQL에서는 데이터를 가져오는 구체적인 과정을 직접 구현해야 하는데 이와 같은 작업(e.g. 데이터베이스 쿼리, 원격 API 요청)을 Resolver가 담당하게 된다.
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 () => {
...
}
}
};