Posts [GraphQL] GraphQL로 API만들기 (2) - GraphQL 쿼리, 뮤테이션, 스키마, 리졸버
Post
Cancel

[GraphQL] GraphQL로 API만들기 (2) - GraphQL 쿼리, 뮤테이션, 스키마, 리졸버

GraphQl로 API 만들기 (2) - 쿼리, 뮤테이션, 스키마, 리졸버


👍 GraphQL의 구조

Graphql 사용 목적 자체가 클라이언트에서 데이터를 서버로부터 간편하고, 효율적으로 가져오는 것입니다.

그리고 이 때 데이터를 가져오기 위해 쿼리(Query)를 사용합니다. 즉, DB 서버로부터 쿼리를 통해 무언가 요청하는 과정을 거칩니다.

여기에서 잠시 GraphQL의 구조를 살펴볼 필요가 있을 것 같습니다.


📙 쿼리, 뮤테이션(Query, Mutation)

쿼리는 단지 정보를 읽어올 때만 사용하는 것으로 이해하시면 됩니다. 예를 들어 다음과 같습니다.

  • 쿼리문(요청) : user의 name을 쿼리로 요청한다.
1
2
3
4
5
query {
	user{
		name
	}
}
  • 응답 : 쿼리문에 대한 응답이 데이터로 반환된다.
1
2
3
4
5
6
7
{
	"data":{
		"user": {
			"name": "abcde"
		}
	}
}

반면 뮤테이션(Mutation)은 받아온 정보를 변형하고자 할 때 사용합니다. 다시 말하자면, 쿼리가 CRUD에서 R 이라면, 뮤테이션은 CUD 부분을 맡습니다.

  • 뮤테이션 요청 예시
1
2
3
4
5
mutation {
	addMovie(name:"Rocks"){
		name
	}
}

addMovie 라는 뮤테이션이 정의되어 있고, 이를 통해 새로운 이름의 영화를 추가(C) 하는 예시


📙 스키마, 리졸버(Schema, Resolver)

그런데 위의 쿼리나 뮤테이션을 그냥 사용할 수는 없습니다. 어딘가에서 정의된 것이 분명할 텐데, GraphQL에서는 스키마에 정의합니다.

schema.graphql 이라는 파일 내에 작성하게 되며, 사용할 쿼리나 뮤테이션, type 등을 정의합니다. 즉, 사용할 데이터들에 대한 설명을 스키마에 작성한다고 생각하면 됩니다.

사용자는 추후에 쿼리문을 작성할 때, 스키마에 정의된 대로 argument 등을 정확히 맞추어 요청해야 합니다.

  • 스키마 파일의 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Movie {
  id: Int!
  title: String!
  rating: Float
  description_intro: String
  language: String
  medium_cover_image: String
  genres: [String]
}

type Query {
  movies(limit: Int, rating: Float): [Movie]!
  movie(id: Int!): Movie
  suggestions(id: Int!): [Movie]!
}
  
type Mutation {
    ...
}

사용할 쿼리나 뮤테이션은 반드시 위와 같이 type Query, type Mutation 내부에 작성해야 하는 규칙을 지켜야 합니다.

그런데 정의만 한다고 해서 데이터의 읽기, 쓰기, 업데이트 등이 알아서 되진 않겠죠? 데이터를 읽어오거나, 데이터를 추가 혹은 삭제 등의 기능을 구현해야 합니다.

그리고 그 기능 구현을 리졸버에 작성합니다. 리졸버(resolver)란, 쉽게 말해 쿼리를 해결하는 것을 말합니다.

스키마에서 정의한 각각의 필드마다, 함수를 하나씩 구현한다고 생각하면 됩니다.

  • 리졸버의 예시
1
2
3
4
5
6
7
8
9
10
11
import { getMovies, getMovie, getSuggestions } from "./db";

const resolvers = {
  Query: {
    movies: (_, { rating, limit }) => getMovies(limit, rating),
    movie: (_, { id }) => getMovie(id),
    suggestions: (_, { id }) => getSuggestions(id)
  }
};

export default resolvers;

get~ 함수들을 모두 구현하고, resolvers.js 파일 내에 위와 같이 작성한 모습입니다. 그러면 graphql이 위의 리졸버를 보고, 쿼리를 어떻게 해결할 지 알 수 있게 됩니다.


✍ 2. 쿼리와 리졸버 정의하기

이제 GraphQL로 본격적인 API를 만들어 보겠습니다.

그러나 이 포스팅에서 GraphQL로 API를 만든다는 것은, 사실상 REST API로 제공되던 오픈API를 GraphQL로 래핑한다는 의미입니다.

그러므로 이 예시의 목적은 GraphQL을 어떻게 활용하는지 그 언어와 개념을 익히는 예제 정도로 생각하시면 좋겠습니다. (본질적으로 REST API를 전부 대체한 예시가 아닙니다.)

기존에 REST API로 제공되어 fetch api 등을 통해 데이터를 받아야 했던 오픈API를, GraphQL 서버를 만들어서 쿼리로 요청하고 데이터를 받을 수 있게 만드는 것이 목적입니다.


📲 2-1. 오픈API Key 발급받기 (사용한 오픈API : 카트라이더 오픈 API)

많은 오픈API들 중에서 저는 카트라이더가 제공하는 오픈API를 사용해 보기로 하였습니다. 이 글을 보시는 분들은 굳이 이 오픈API가 아니더라도 원하는 다른 오픈API를 선택하셔도 무방합니다. 설명은 카트라이더 API 기준으로 진행하도록 하겠습니다.

API 사용을 위해서는 key를 발급받아야 합니다. 이후 API를 불러올 때 이 key값을 같이 보내야만 응답을 받을 수 있습니다. 로그인 후 새 어플리케이션 등록을 누릅니다.

이후 나오는 페이지에서 정보를 입력하고 완료합니다.

다시 마이페이지에 와 보면 key값이 발급되어 있음을 확인할 수 있습니다!


📲 2-2. 필요한 파일들 생성하기

저번 포스팅에서 grahpqhl-yoga 로 만들었던 graphql 프로젝트 폴더에 들어간 뒤 아래와 같이 폴더와 파일을 새로 생성합니다.

  • 새로 생성할 폴더 : config, graphql
  • 새로 생성할 파일 : config/key.js, graphql/db.js, graphql/resolvers.js, graphql/schema.graphql


📲 2-3. 쿼리, 리졸버, 스키마 등 정의하기

[1] key 값 작성 후 export 해 두기

위 파일들이 github repository에 올라갈 텐데, API Key값이 다 드러난 채로 올라가면 문제가 발생할 수 있습니다. key값은 개인의 비밀번호 정도로 취급해야 합니다.

따라서 key값을 별도의 변수에 담아서 사용할 수 있도록 해야 합니다. 그리고 그 파일은 .gitignore 에 추가시켜 git에서 관리하지 않도록 합니다. 따라서 key 값은 repository에도 올라가지 않습니다!

  • config/key.js 에서 작성 : 카트라이더 API 사이트에서 발급받은 key를 아래와 같이 변수에 저장합니다.
  • export 키워드는 해당 파일 외의 바깥에서도 사용할 수 있도록 해 주는 역할을 합니다.
1
export const API_KEY = 'key값을 여기에 작성';

[2] 스키마 정의하기

2-1에서 발급받은 key 사진 안에 보면 API 바로가기 링크가 있습니다. 그 링크를 누릅니다.

카트라이더 오픈API는 각종 정보를 가져올 때 유저 고유 식별자를 사용했습니다. 그래서 유저 닉네임을 입력하면, 유저 고유 식별자를 반환하는 API를 먼저 만들어 보기로 했습니다.

유저정보 - 라이더명으로 유저 정보 조회에 들어간 뒤 아래 그림과 같이 카트라이더 닉네임을 아무거나 입력하여 테스트하기 버튼을 눌러봅니다.

그러면 아래와 같은 결과를 얻을 수 있습니다.

이 결과값을 토대로 스키마를 작성해 볼 수 있습니다.

이렇게 생각하면 편합니다. 어떤 특정 이름의 쿼리를 작성한 후 서버에 요청하면, 위의 Response Body 처럼 graphql이 응답하여 데이터를 보내 줍니다. 이 때 서버에 요청하기 위한 데이터의 표현을 정확하게 정의하는 것이 스키마입니다.

위의 API의 경우 유저 정보를 보여주기 때문에 저는 쿼리명을 user라고 할 것이고, 닉네임을 파라미터로 넘기면 accessId와 name으로 이루어진 type을 반환한다고 정의하겠습니다. 또한 반환된 값을 보면 여러 데이터가 같이 나오는 배열 형태가 아닌 것에도 유의해야 합니다.

이름설명리턴값
type : UserGraphql의 객체 타입이며 필드로 accessId, name을 가진다. 
query : user파라미터로 usrName: String! 을 가진다. 리턴값이 [User]! 가 아닌데, 닉네임을 파라미터로 넘기면 API 사이트에서 나온 결과에서 볼 수 있듯이 accessId와 name 값 하나만 반환되므로 객체의 배열 형태로 쓰지 않았다.User!

String! 과 같이 느낌표가 붙은 것의 의미는 이 값이 non-nullable 하다고 표시하는 것입니다. 즉 null 값을 허용하지 않는 것입니다. 쿼리할 때 이 값이 null이면 곧바로 오류를 뱉도록 하여 안전한 코드 작성이 가능해집니다. 이것이 graphql의 또다른 장점이기도 합니다. 만약 느낌표가 없다면 null을 허용하는 옵셔널이 됩니다.

같은 의미로 리턴값에 User! 라는 것은 null 이 아닌 User 값을 반환하겠다고 명시한 것입니다.

이제 아래와 같이 schema.graphql 파일에 작성합니다.

📁 graphql/schema.graphql

1
2
3
4
5
6
7
8
type User{
  accessId: String!
  name: String!
}

type Query{
  user(usrName: String!): User!
}

[3] 리졸버 작성하기

그리고 이제 위 쿼리를 해결하는 리졸버를 작성해야 합니다.

우선, 인자로 유저 이름을 주면, accssId와 name을 응답으로 받을 수 있게 하기 위한 함수가 필요합니다. 즉 스키마에서 정의한 쿼리를 해결하는 함수를 작성해 줄텐데, db.js 파일에 이 함수를 작성하겠습니다.

  • 함수 작성 전, REST API로부터 fetch할 수 있도록 node-fetch를 설치합니다. 루트 디렉토리 터미널에서 아래 명령어를 입력하여 설치한 후 db.js에 코드를 작성합니다.
1
$ yarn add node-fetch

📁 graphql/db.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import fetch from "node-fetch";
import { API_KEY } from "../config/key"

const API_URL = "https://api.nexon.co.kr/kart/v1.0/users/nickname/";

export const getUserId = (usrName) => {
  let REQUEST_URL = API_URL + encodeURI(usrName);
  return fetch(REQUEST_URL,{
    method: 'get',
    headers: {
      'Authorization': API_KEY
    }
  })
  .then(res => res.json())
  .then(json => json)
};
  • 1, 2행 - node-fetch를 import하고, 1번에서 API_KEY를 export 한 것을 불러왔습니다.
  • 4행 - API정보에서 확인한 API URL값을 저장합니다.
    • 위 링크에 들어가면, API의 요청 정보와 응답 정보 등을 확인할 수 있으며, 무엇보다도 데이터의 타입 등을 상세히 명세해 놓았기 때문에 이러한 API 문서를 잘 확인하는 것이 중요합니다!
  • 6행~ - 닉네임을 인자로 넘기면, accessId와 name을 받을 수 있도록 함수를 작성한 것입니다.
    • REQUEST_URL은 https://api.nexon.co.kr/kart/v1.0/users/nickname/myNickname 과 같이 주소 맨 뒤에 사용자가 입력한 닉네임을 붙인 것입니다. 이 때 한글로 닉네임을 넘길 경우 인코딩 문제가 발생하여 제대로 응답을 받을 수 없습니다. 그래서 자바스크립트의 encodeURI 함수를 사용하였습니다.
    • 요청을 보내고, 응답받는 과정을 그 밑에서 작성하였습니다. 여기에 Header로 Key 값을 전송해야 응답을 받을 수 있는데요, 그에 대한 내용은 카트라이더 API FAQ 에 보시면 나와 있습니다. 이러한 준수 사항을 지켜야 하므로 headers 에 KEY 값을 넣은 것입니다. Type이나 KeyName(Authorization) 등 자세한 상세는 카트라이더 API FAQ 에 적혀 있으며 이를 참고한 것입니다.
  • 위에서 fetch가 끝나고 데이터를 받아온 후 .then을 이용하여 처리 결과값을 받고, 그 값을 json 형태로 받아온 것입니다.
  • 사실 위와 같은 fetch를 이용한 코드 작성법은 node-fetch github에 가면 케이스별로 상세히 알려 줍니다. 오픈소스는 이렇게 사용법 등이 자세히 나와 있고 커뮤니티도 활발해서 큰 장점으로 생각됩니다. 이 코드 작성 또한 https://github.com/node-fetch/node-fetch 를 참조하였습니다.

필요한 함수를 모두 작성하였으므로, 이제 리졸버 파일을 수정합니다.

📁 graphql/resolvers.js

1
2
3
4
5
6
7
8
9
import { getUser } from "./db";

const resolvers = {
  Query:{
    user: (_, {usrName}) => getUserId(usrName)
  }
}

export default resolvers;

위처럼 리졸버를 작성해 두면, graphql은 user 쿼리에 맞는 리졸버를 찾고, 이를 실행합니다. 여기에선 미리 작성한 함수가 실행되는 것입니다. 이 때 (_, {usrName}) 부분에서 앞의 _ 는 Object에 관한 것인데 지금은 사용하지 않는다는 의미로 _ 를 표시한 것이고, 그 다음 두 번째 인자가 argument 자리입니다.

여기까지의 과정을 간단하게 정리하면 다음과 같습니다.

  1. 스키마 정의하기

    1. 스키마 내에 쿼리 및 type 정의
  2. 리졸버 작성하기 - 쿼리 해결의 역할

    1. 필드를 해결하기 위한 함수를 작성하고, 리졸버에 연결!

이제 index.js 에 graphql 서버를 만들고, 실행해 보면 됩니다!

📁 index.js

1
2
3
4
5
6
7
8
9
import { GraphQLServer } from "graphql-yoga";
import resolvers from "./graphql/resolvers"

const server = new GraphQLServer({
  typeDefs: "graphql/schema.graphql",
  resolvers
});

server.start(() => console.log("Graphql Server Running"));

포스팅 (1) 에서 설치했던 graphql-yoga를 여기서 사용합니다. graphql-yoga는 Graphql 서버를 바로 쓸 수 있도록 준비해 주는 역할을 합니다. (추후에 다루겠지만 graphql-yoga가 바로 쓸 수 있는 백엔드 부분이라면, apollo는 바로 쓸 수 있는 클라이언트 부분이라고 생각하면 편합니다.)

위의 코드 작성법 및 함수 설명도 모두 graphql-yoga git에 나와 있습니다! (https://github.com/prisma-labs/graphql-yoga)


✍ 3. GrqphQL 서버 실행하기

  • 터미널에서 yarn start를 입력합니다.
    • 정상적으로 실행되면, Graphql Server Running 문구가 터미널에 뜹니다.
  • 이제 웹 브라우저에서 localhost:4000 에 들어갑니다. grahql-yoga가 준비한 플레이그라운드가 뜰 것입니다. DB를 테스트하게 해 주는 좋은 툴로 편하게 개발할 수 있게 도와 줍니다. 직접 정의한 스키마 내용들과 타입 등을 플레이그라운드 페이지 내부에서도 확인이 가능합니다.

  • 위처럼 쿼리를 작성하고 가운데 플레이 버튼을 눌러봅니다. 그러면 아래와 같이 결과가 나옵니다.!!

여기까지 하면, REST API로 제공되던 것을 Graphql로 래핑하여 또 하나의 Graphql API를 직접 만들게 된 것입니다.

이런 식으로 만들어 두면 추후에 계속 API에 대한 fetch 등을 통한 작업 없이 graphql 에 쉽게 요청하는 것만으로도 데이터를 받아올 수 있어 편해질 것입니다.

카트라이더 API 서비스에 가 보면 더욱 다양한 API를 제공합니다. 그들을 지금까지 했던 과정처럼 모두 작성해 둔 다음, 이 API를 이용하여 웹 페이지를 만들어 보면 더욱 좋을 것 같습니다.

이렇게 만들어 둔 graphql 서버를 기반으로, react, apollo를 활용하여 웹 페이지를 만드는 포스팅도 이 다음 게시물에서 다루도록 하겠습니다.


[GraphQL] GraphQL로 API만들기 (1) - 프로젝트 셋업

알고리즘 - c++의 priority_queue 사용법