GraphQL을 공부하기로 하였다.
예전부터 많이 궁금했던 영역이기도 하고, 서버에서 REST API가 아닌 방식으로 어떻게 데이터를 가져올 수 있다는거지? 에 대한 궁금증을 해결하고 싶었다.
Graph QL을 풀어보면 아래와 같다.
Graph Query Language
그래프가 모든 노드가 연결 되어있듯,
모두 연결되어있는 데이터들 중 가져오는 쿼리 언어 라는 의미로 지어진 것 같다.
연결되어있는 데이터들 중 내가 원하는 데이터만 가져오겠다!
굉장히 재미있는 이론같다.
지금까지 내가 아는 서버 / 클라이언트의 소통 방식은
서버 개발자는 REST API를 만들고,
명세서를 통해 클라이언트에게 사용방법을 제공하여,
클라이언트는 명세서에 작성된 대로 사용하여 데이터를 제공 받을 수 있었다.
내가 원하는 데이터만 골라 받을 수 없고,
내가 원하는 데이터를 받을 수 없으면 서버 개발자에게 만들어 달라고 요청하는것과 같은
불편함도 존재했지만, 당연하다고 생각했었다.
Graph QL은 위 불편함을 해소시켜 준다고 한다.
React.js 를 개발한 Facebook 에서 2015년에 공개적으로 발표된 쿼리 언어 이다.
SQL의 목적
GQL의 목적
위에서의 불편한 점만 생각해도 바로 장점이 떠오를 수 있겠지만,
그럼에도 정리해보자면 아래와 같다.
위 이미지에서도 볼수 있듯 충격적인 사실은 GQL에서 EndPoint가 단 하나다!
쿼리는 읽는데(R) 사용한다.
뮤테이션은 데이터를 변조(CUD) 하는데 사용한다.
쿼리와 뮤테이션은 개념적인 규약을 정해 놓은 것일 뿐,
내부적으로 들어감녀 사실상 별 차이가 없다.
query getStudentInfomation($studentId: ID){
personalInfo(studentId: $studentId) {
name
address1
address2
major
}
classInfo(year: 2021, studentId: $studentId) {
classCode
className
teacher {
name
major
}
classRoom {
id
maintainer {
name
}
}
}
SATInfo(schoolCode: 0412, studentId: $studentId) {
totalScore
dueDate
}
}
오퍼레이션 네임 쿼리는 쿼리용 함수 라고 생각하면 편하다.
위의 예시에서, studentId라는 인자를 받을 수 있도록 getStudentInfomation함수를 선언하였는데, 이를 오퍼레이션 네임 쿼리라고 부른다.
이를 통해 REST API와는 다르게, 한번의 네트워크를 통신하여 원하는 모든 데이터를 가져올 수 있도록 해준다.
type Character {
name: String!
appearsIn: [Episode!]!
}
데이터를 가져오기 위한 과정을 담당한다.
클라이언트 개발자는 본인이 가져오기 위한 데이터를 직접 작성해야하는 불편함이 있지만,
리졸버를 통해 데이터를 데이터베이스에서 가져 올 수 있고, 일반 파일에서 가져 올 수 있고, 심지어 http, SOAP와 같은 네트워크 프로토콜을 활용해서 원격 데이터를 가져올 수 있다.
우리가 가져오고 싶은 필드마나 함수가 하나씩 존재한다고 생각하면 된다.
이 함수는 다음 타입을 반환하고, 필드가 스칼라값(String 이나 Int 와 같은 primitive 타입)인 경우 실행이 종료된다.
이 함수를 GQL에서는 리졸브라고 한다.
기존 REST API 방식에서는 서버/클라이언트 개발자가 소통하고 협업하기 위해 명세서 라는 것이 반드시 필요했지만,
명세서의 존재는 작업의 복잡성과 효율성을 증가시킨다.
(서버 개발을 할때마다 느끼지만 정말 중요함을 알면서도 갱신이 귀찮 쉽지 않다..)
GQL에서는 명세서 공유와 같은 문제를 해결해 주도록 인트로스펙션을 제공한다.
인트로스펙션은 서버 자체에서 현재 서버에 정의된 스키마를 실시간으로 공유 할 수 있도록 해준다.
이 스키마 정보만 알고있으면? 클라이언트에서는 명세서가 필요 없다 이말이지~
대부분의 GQL 라이브러리에서는 쿼리용 IDE를 제공하기 때문에, 클라이언트 개발자도 쉽게 확인이 가능하다.
잠시 후, 실습때 보겠지만, 아래 화면은 Apollo server라는 GQL 라이브러리에서 제공하는 웹 IDE 예시이다.
Node.js에서 Apollo 라이브러리를 이용해 GraphGL 서버를 구축해보자!
나는 IntelliJ IDEA를 사용하기에 IntelliJ 기준으로 설명하겠다!
새 프로젝트를 누르고, Javascript -> Node.js를 선택 후 Next
이름 지어주고 finish
성공적으로 생성했으면 터미널을 통해 GraghQL, Apollo Server, nodemon을 설치하자.
npm install graphql apollo-server nodemon
프로젝트 폴더 아래에 바로 src 폴더를 하나 추가하자.
그안에는 index.js 파일을 추가하자.
"type": "module"
"start": "nodemon src/index.js"
임시로 데이터베이스를 하나의 파일로 생성하자.
여기서는 연습이라 따로 생성하기 위함이지만,
외부의 데이터 베이스를 사용하거나 Maria DB, MySQL 등 여러 형태로 생성해도 상관 없다.
src/database/users.js
// src/database/users.js
const users = [
{
id: 1,
nickname: "Ted",
age: 28
},
{
id:2,
nickname : "Bob",
age: 23
}
];
export default users;
서버에 어떻게 데이터를 요청할지 정의한 스키마를 정의하자.
여기서는 User라는 데이터를 주고 받기 위해 User의 구조와 자료형,
User를 읽기 위한 Query users, user(id: Int)
User를 추가하기 위한 addUser 를 선언하였다.
src/graphql/typeDefs.js
// src/graphql/typeDefs.js
import { gql } from 'apollo-server';
const typeDefs = gql`
type User {
id: Int!
nickname: String!
age: Int!
}
type Query {
users: [User!]!
user(id: Int!) : User
}
type Mutation {
addUser(nickname: String!, age: Int!): User!
}
`;
export default typeDefs
스키마에서 정의했던 users, user, addUser가 어떻게 작동하는지 설정하기 위해
리졸버에 정의하자.
단순히 데이터를 반환하거나,
직접 데이터베이스를 찾거나,
메모리에 접근하거나,
다른 API에 요청하여 데이터를 가져오는 등을 처리할 수 있다.
우리는
users 요청 시 users(데이터베이스)를 반환하고,
user(id) 요청 시 users(데이터베이스)에서 해당 id값을 가진 유저를 찾아 return 하고,
addUser 요청 시 닉네임 중복 검사 후 해당 유저를 추가한 다음 추가한 유저를 return 해주는 리졸버를 선언하였다.
프로젝트가 커지다보면 나중에는 리졸버 구현이 복잡해질텐데, 이를 해결하기 위해 리졸버 단계에 prisma나 TypeORM 등 데이터베이스 ORM를 사용하기도 한다고 한다.
src/graphql/resolver.js
// src/graphql/resolver.js
import users from "../database/users.js";
const resolvers = {
Query: {
users: () => users,
user: (_, { id }) => {
return users.filter(user => user.id === id)[0];
}
},
Mutation: {
addUser: (_, {nickname, age}) => {
// 닉네임 중복 검사
if (users.find(nickname => users.nickname === nickname)) return null;
const newUser = {
id: users.length + 1,
nickname,
age
};
users.push(newUser);
return newUser;
}
}
};
export default resolvers
이제 다시 처음에 생성해 두었던 index.js에 돌아와서
서버를 생성해주자.
import {ApolloServer} from "apollo-server";
import resolvers from "./graphql/resolvers.js";
import typeDefs from "./graphql/typeDefs.js";
const server = new ApolloServer({
typeDefs,
resolvers
});
server.listen().then(({url}) => {
console.log(`🚀 Server ready at ${url}`);
});
터미널에서 npm start 를 선언해보자.
로켓과 함께 서버가 준비되었다는 콘솔이 찍혔으니 실행된 서버로 접속해보자.
localhost:4000으로 접속하니 아까전에 이야기 했던 인트로스펙션 기능을 통해 아폴로에서 제공하는 웹 IDE가 열리는 것을 볼 수 있다!
query {
users {
id
nickname
age
}
}
위 코드를 가운데에 작성하고 Run을 클릭해보자.
우리가 만들었던 데이터베이스의 데이터가 조회된다... 싱기방기쓰..
이번엔 1명의 유저만 검색해보자.
query {
user(id: 1) {
id
nickname
age
}
}
우리가 만들어 두었던 쿼리들이 정상적으로 작동하는 것들을 볼 수 있다.
이번엔 미리 선언해 두었던 addMutation을 실행해보자.
mutation {
addUser(nickname: "Dory", age: 26) {
id
nickname
age
}
}
추가된 데이터의 어떤 항목도 우리는 마음대로 조회할 수 있다.
다시 전체 유저를 조회해보면
방금 추가한 유저가 들어가 있음을 볼 수 있다.
이렇게 간단하게 Node.js에서 Apollo를 통해 GraphQL를 구현해 보았다.
기초중의 기초로, 호다닥 만들어 보았는데도 불구하고 재미있다.
너무 새로운 방식이고, 너무너무 좋은 방식이라고 생각된다.
다음에 토이프로젝트 할일이 생긴다면 GraphQL 방식을 꼭 사용해봐야지.
이상!