1. Set Up
- yarn init
- yarn add graphql-yoga
2.Why GraphQL?
2-1. Over-fetching
- 기존의 RestAPI의 GET방식으로 데이터를 요청할 경우, 영화이름만 필요한 경우에도 영화 이름 뿐만아닌
모든 데이터를 같이 불러오는 현상이 발생함 - 사용하지 않을 데이터들 까지도 불러오면서 데이터량으 늘어남에 따라 비효율적인 현상 발생
2-2.Under-fetching
- 어떤 하나를 완성하기 위해서 다른 요청까지 해야되는 현상 발생
GraphQL은 내가 원하고 불러오고 싶은 데이터의 정보들만 불러올 수 있다.
기존의 Rest-API에서 발생한 문제점을 해결할 수있다.
3.Create GraphQL Server
graphQL에서 필요한 것은 두가지 이다.
- schema작성(데이터 구조)
- url로 호출할 수 있도록 작성
이번에는 간단한 express 서버를 이용해 쿼리가 들어오면 데이터를 리턴해주는 graphQL을 작성해보겠다.
프로젝트 생성
먼저 프로젝트 폴더를 생성한뒤 package.json에 넣어주고, graphQL을 설치해준다.
yarn init -y
yarn init 을이용해 package.json을 만들어준다 -y옵션은 모든 답변을 기본값(공백)으로 설정할 수 있다.
Apollo Server 및 GraphQL 설치
yarn add apollo-server graphql
Apollo Server는 GraphQL이 적용된 서버를 생성할 수 있는 클래스를 제공한다. 비유하자면 react에 CRA와 비슷
개별 도구 설치(선택)
> yarn add nodemon @babel/core @babel/node @babel/preset-env --dev
- nodemon
실행 중인 Javascript 파일 변경을 감지하고 파일이 변경되면 Node를 재실행해 변경 사항이 자동으로 반영되게
도와준다. 많이 쓰는 패키지 라서 yarn global add nodemon으로 설치하면 좋다. - babel
우리가 Javscript 최신 문법을 사용하면 자도으로 각 브라우저가 지원하는 Javascript버전에 맞게 문법을 바꿔줌
.babelsrc 파일 생성 (선택 사항)
{
"presets": ["@babel/preset-env"]
}
babel 패키지 설정 파일인 .babelrc파일을 프로젝트 폴더에 새로 생성한다.
package.json 파일 수정
{
...
"scripts": {
"start": "nodemon --exec babel-node src/index.js"
}
}
package.json에 scripts항목에 추가한다. babel과 nodemon을 설치하지 않으면 위 스크리트 대신
"start": "node index.js"를 추가한다.
데이터 베이스 생성
const movies = [
{
id: 1,
name: "백두산",
rating: 7
},
{
id:2,
name: "히트맨",
rating: 7
},
{
id:3,
name: "남산의 부장들",
rating: 9
},
{
id: 4,
name: "겨울 왕국2",
rating: 7
}
];
export default movies;
나중에 만들 resolver.js 파일 안에서 데이터베이스를 처리해도 되지만 명확히 구분하기 위해 따로 폴더를 만들어 json파일 형태로 간단한 데이터 베이스를 만들었다.
데이베이스는 외부에서 가져와도 되고, MySQL, mongoDB등 여러가지 형태로 직접 생성해도 된다. GraphQL은
서버와 클라이언트 사이에서 오고 가는 쿼리 언어이기 때문에 데이터베이스 형태에 제약이 없다.
스키마와 리졸버 설정
스키마
import { gql } from 'apollo-server';
const typeDefs = gql`
type Movie {
id: Int!
name: String!
rating: Int!
}
type Query {
movies: [Movie]!
movie(id: Int!): Movie
}
type Mutation {
addMovie(name: String!, rating: Int!): Movie!
}
`;
export default typeDefs;
스키마는 서버에 어떠한 데이터를 요청할지 정의한 파일이다. 요청 시 어떤 데이터를 얼마나 요청할지, 각각의 데이터의
자료형이 무엇이고, 어떤 데이터를 필수로 요청할지에 대한 정보가 담긴다. 즉 , 사용자는 반드시 스키마에 정의된
형태로 서버에 요청해야한다.
- Query: 데이터베이스에서 데이터를 읽는 요청
- Mutation: 데이터베이스를 수정하는 요청
스키마엔 Query, Mutation 같이 2가지 요청이 있다. 이렇게 요청을 구분하는 데이터 베이스 읽기 요청은 무한정으로
동시에 수행될 수 있지만, 데이터베이스 수정 요청은 순차적으로 수행되야 하기 떄문이다. 성능 향상을 위해 편의상
구분한 것이라고 생각하지만 정확히는 모른다.
스키마에 Movie의 구조와 저료형도 정의해야한다. 그래야 GraphQL 서버에서 데이터베이스 구조를 읽고 처리할 수 있다.
- !: Not Nullable. 데이터가 꼭 있어야 한다.
- []: 배열
리졸버
import movies from '../database/movie';
const resolvers = {
Query: {
movies: () => movies,
movie: (_, { id }) => {
return movies.filter(movie => movie.id === id)[0];
}
},
Mutation: {
addMovie: (_, { name, rating}) => {
// 영화제목 중복 검사
if (movies.find(movie => movie.name === name)) return null;
// 데이터 베이스 추가
const newMovie = {
id: movies.length - 1;
name,
rating
};
movies.push(newMovie);
return newMovie
}
}
};
export default resolvers;
리졸버는 사용자가 쿼리를 요청할때 이를 서버가 어떻게 처리할지 정의한 파일이다.
리졸버는 요청에 대한 단순히 데이터를 반환할 수 도 있지만, 직접 데이터 베이스를 찾거나, 메모리에 접근하거나,
다른 API에 요청해서 데이터를 가져올 수 있다.
위 파일은 다음을 의미한다. 데이터베이스를 읽는 요청(Query) 중 movies가 요청되면 ..database/Movies.js에 있는
movies데이터를 반환해준다. 데이터 베이스를 수정하는 요청 (Mutation) 중 name과 rating을 파라미터로 가진
addMovie가 요청되면, 데이터베이스에 영화를 추가한다.
각 리졸버 항목(movies, movie, addMovie)의 매게변수 4개까지 있는데 다음과 같다.
1. parent: 부모 타입 리졸버에서 반환된 결과를 가진 객체
2. args: 쿼리 요청시 전달된 파라미터를 가진 객체
3.context: GraphQL의 모든 리졸버가 공유하는 객체로서 로그인 인증, 데이터베이스 접근 권한 등에 사용한다.
4. info: 명령 실행 상태 정보를 가진 객체
프로젝트가 커지다보면 나중에는 리졸버 구현이 복잡해지는데, 이를 해결하기 위해 리졸버 단계에
prisma나 TypeORM등 데이터베이스 ORM을 사용하기도 한다.
서버 생성
import { ApolloServer } from 'apollo-server';
import resolvers from './graphql/resolvers';
import typeDefs from './graphql/typeDefs';
// AppolloServer는 스키마와 리졸버 가 반드시 필요
const server = new ApolloServer({
typeDefs,
resolvers
})
// listen 함수로 웹서버 실행
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
})
프로젝트 src폴더 index.js파일을 생성한다.
GraphQL API 테스트
Query
query {
movies {
name
}
}
위와 같이 입력하고 실행하면 모든 영화의 이름이 반환되는 것을 볼 수 있다. 여기서 query키워드는 생략할 수 있다.
Mutation
mutation {
addMovie(name: "인셉션", rating: 8) {
name
}
}
위와 같이 새로운 영화를 추가하는 요청을 보낼 수도 있다.