어제부터 쭉 GraphQL을 보고 있다.
나름 매력적이라고 느껴지긴 하는데, 음..아직 잘 모르겠다.
실 서비스에 적용해봐야 뭔가 확실히 감이 올 것 같은데 아직은 테스트 케이스로 작성하다보니
확실하게 와닿지가 않는다.
튼, 오늘 공부한 내용을 기록한다.
오늘은 GraphQL을 통해 API구축을 해봤다. 전체 목록 조회/단일 조회/수정/삭제/삽입 총 5가지의 기능을 구현했다.
또한 데이터베이스는 MySQL을 사용했다. 또한 Node.js에서 MySQL과 통신하기 위한 라이브러리는 Sequelize를 사용했다.
이 부분에서 예제가 그리 많지 않아서 살짝 고생했다.
먼저 폴더를 생성하고 yarn init후, 아래의 라이브러리를 설치해준다.
"dependencies": {
"graphql-yoga": "^1.14.10",
"mysql2": "^1.5.3",
"sequelize": "^4.38.0"
},
그리고 index.js 파일을 아래의 내용으로 채워준다.
const { GraphQLServer } = require('graphql-yoga');
const { resolvers } = require('./graphql/resolvers');
const server = new GraphQLServer({
typeDefs: "graphql/schema.graphql",
resolvers: resolvers
});
server.start(() => console.log("GraphQL Server Running"))
이전 포스팅에서 schema와 resolvers에 대해 설명했으므로 짧게 설명하고 넘어간다.
schema는 어떠한 값을 입력으로 받을 지, 그리고 리턴 값은 무엇인지 추상적으로 작성한 파일이며
resolver는 실제로 해당 행위를 할 때 어떻게 작동할지 정의해주는 것이다.
다음으로 graphql 폴더를 하나 생성하고, db.js / resolvers.js / schema.graphql 세개의 파일을 생성한다.
schema와 resolvers는 위에 설명했던 그대로고, db는 실제 데이터베이스와 연결하는 파일이다.
먼저 db.js에 아래의 내용을 채워넣는다.
const Sequelize = require('sequelize');
const sequelize = new Sequelize('graph', 'graph', 'graph', {
host: 'localhost',
dialect: 'mysql'
})
// Table
const MovieModel = sequelize.define('movie', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
allowNull: false
},
score: {
type: Sequelize.INTEGER,
allowNull: false
}
}, {
tableName: 'movie',
timestamps: false
});
// Synchronize table
sequelize.sync({
force: false,
})
// Insert dummy data
.then(() => MovieModel.create({
name: 'Star wars',
score: 4
}))
.then(() => MovieModel.create({
name: 'Avgengers',
score: 5
}))
.then(() => MovieModel.create({
name: 'Cats',
score: 2
}))
.then(() => MovieModel.create({
name: 'Tazza',
score: 3
}))
const Movies = sequelize.models.movie;
module.exports = {
Movies: Movies
}
테이블을 생성하고 서버 실행 시 동기화를 위해 sync()를 해줬고, Movies를 타 파일에서 접근하기 위해 export 해줬다.
다음으로 schema.graphql을 연다.
type Movies {
id: Int!
name: String!
score: Int!
}
type Query {
movies: [Movies]!
movie(id: Int!): Movies
}
type Mutation {
createMovie(name: String!, score: Int!): Movies!
deleteMovie(id: Int!): Movies
updateMovie(id: Int!, name: String, score: Int): Movies
}
먼저 반환시킬 Movies를 정의해줬다.
id, name, score 세가지의 값이 있다는 걸 명시해줬다.
그리고 조회하는 부분인 Query에서 전체 목록을 반환해주는 movies와, id값을 받아서 해당 id에 해당하는 요소만 반환해주는 movie를 정의했다.
Mutation은 수정/삽입/삭제 등 뭔가 실제 데이터베이스와 연동하여 동작하는 일종의 행위를 말한다.
생성/삭제/수정을 해줘야 하므로 위처럼 3가지를 정의했다.
deleteMovie와 updateMovie의 반환값에는 !(required) 값을 주지 않았는데, 요청한 값이 존재하지 않을수도 있기 때문이다.
또한 updateMovie에서 입력값에 id를 제외한 나머지 값들에도 !를 주지 않았는데, 둘 중 하나만 입력값이 올 수 있기 때문이다.
다음으로 resolvers.py를 연다.
const { Movies } = require('./db');
const resolvers = {
Query: {
movies: () => {
return Movies.all();
},
movie: (_, args) => {
return Movies.find({
where: args
})
}
},
Mutation: {
createMovie: (_, args) => {
const movie = Movies.create({
name: args.name,
score: args.score
});
return movie;
},
deleteMovie: (_, args) => {
const movie = Movies.destroy({
where: args
})
return {id: args.id};
},
updateMovie: (_, args) => {
const data = {};
if(args.name === undefined) {
data.score = args.score;
} else if(args.score === undefined) {
data.name = args.name;
} else {
data.name = args.name;
data.score = args.score;
}
const movie = Movies.update(data, {
where: {id: args.id}
})
return {id: args.id};
}
}
}
module.exports = {
resolvers: resolvers
}
별거 없다. 스키마에 정의해준 작업들을 실제 구현해놓은 부분이다.
updateMovie에서는 부분 업데이트를 해주기 위해 값을 검사하고 따로 처리해줬다.
끝이다. 별거없었고 잘 돌아간다.
내일은 Pagination이나 JWT Token을 연동해봐야 겠다.
소스는 https://github.com/teamhide/graphql_mysql