어서와, 개발은 처음이지?

express-graphql에 mongodb 사용하기 본문

GraphQL

express-graphql에 mongodb 사용하기

오지고지리고알파고포켓몬고 2018. 10. 22. 17:00
반응형


이번 시간에는 GraphQL(express-graphql)과 MongoDB(mongoose)를 연결하여 graphiql로 쿼리를 날려보겠습니다.

이 글에 작성된 내용은 Github에서 확인할 수 있습니다.



1. 프로젝트 구조

.
├── app.js
├── business
│   ├── dao.js
│   ├── join.js
│   └── test.js
├── graphql
│   └── schema.js
├── mongoose
│   ├── model.js
│   └── schema
│       ├── post.js
│       └── user.js
├── package-lock.json
└── package.json

프로젝트 구조는 위와 같은 형태로 잡아봤습니다.

정형화 된 구조가 따로 없으므로 편하신대로 구현하셔도 좋습니다.



2. Mongoose Schema


우선 사용자 정보를 관리할 User 스키마, 게시글을 관리할 Post 스키마가 있다고 가정했습니다.

각 스키마는 아래처럼 간단하게 정의해봤습니다.

module.exports = function(mongoose){
  return new mongoose.Schema({
    email: String,
    pwd: String,
    c_date: { type: Date, default: Date.now }
  });
};

<./mongoose/schema/user.js>

module.exports = function(mongoose){
  return new mongoose.Schema({
    title: String,
    content: String,
    author: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
    c_date: { type: Date, default: Date.now }
  });
};

<./mongoose/schema/post.js>


User 스키마는 이메일, 비밀번호, 생성일을 갖고, Post 스키마는 제목, 내용, 작성자(ObjectId), 작성일을 가지고 있습니다.

추후 GraphQL에서 author.email 형태로 꺼내오는 모습을 보이기 위해 author가 User item의 ObjectId를 참조하게 해두었습니다.


다만, 실제 서비스에서는 로그인 정보(pwd)와 사용자 정보(이름, 닉네임 등)가 분리되어 있는 스키마 구조일 때 보안측면에서 안전하니 예제를 위한 예제임을 감안하고 봐주시면 좋겠습니다.

module.exports = (function(){
  const mongoose = require('mongoose');
  const URI = process.env.MONGO_URI || "localhost"; // your mongodb uri
  const DB = process.env.MONGO_DB || "mongoose"; // your db

  const db = mongoose.connection;
  db.on('error', console.error);
  db.once('open', function(){
      // CONNECTED TO MONGODB SERVER
      console.log("Connected to mongod server");
  });

  mongoose.connect(`mongodb://${URI}/${DB}`, { useNewUrlParser: true });

  const schema = {};
  const model = {};

  schema.Post = require('./schema/post')(mongoose);
  schema.User = require('./schema/user')(mongoose);

  for(let k in schema){
    model[k] = mongoose.model(k, schema[k]);
  }

  return model;
})();

<./mongoose/model.js>


MongoDB 커넥션과 Schema Model 관리는 model.js에서 통합하여 사용할 수 있는 패턴으로 작성해보았습니다.



3. GraphQL Schema


마찬가지로 User와 Post 스키마를 GraphQL에도 정의해줍니다.

const dao = require('../business/dao');
var { buildSchema } = require('graphql');

var schema = buildSchema(`
  type Query {
    users: [User]
    user(email: String!): User
    posts: [Post]
  }

  type Mutation {
    createUser(email: String!, pwd: String!): User
    createPost(title: String!, content: String!, author: String!): Post
  }

  type User{
    id: String
    email: String
    pwd: String
    c_date: String
  }

  type Post{
    id: String
    title: String,
    content: String,
    author: User
    c_date: String
  }
`);
// 맞춤 스칼라 타입 지정은 어떻게?
var resolver = {
  users: async (args, context, info) => {
    return await dao.cm.getAllUsers();
  },
  user: async (args, context, info) => {
    const {email} = args;

    return await dao.cm.getUser(email);
  },
  createUser: async (args, context, info) => {
    const {email, pwd} = args;

    return await dao.cm.joinUser(email, pwd);
  },
  posts: async (args, context, info) => {
    return await dao.post.getAllPosts();
  },
  createPost: async (args, context, info) => {
    return await dao.post.createPost(args);
  },
};

module.exports = {schema: schema, root: resolver};

<./graphql/schema.js>


User와 Post는 Mongoose의 스키마 정의와 동일한 구조입니다.

ObjectId는 세션정보, 인덱싱 등 유용하게 사용할 수 있으므로 id 필드도 추가했습니다.(dao는 4번에서 서술하도록 하겠습니다.)


특이한점은 Mutation인데, Query가 RESTful API의 Get역할을 한다면, Mutation이 Get을 제외한 행위를 담당한다고 볼 수 있습니다.

사실상 Query와 Mutation의 필드만이 resolver 함수를 갖습니다.(resolver 함수에 async await을 사용할 수 있습니다.)


예제에서는 모든 유저 정보(query.users), 단일 유저 정보(query.user), 유저 정보 생성(mutation.createUser), 모든 글(query.posts), 글 생성(mutation.createPost)의 5가지 기능을 수행합니다.




4. Business Logic(DAOs)


GraphQL에서 DB에 접근하는 과정은 MVC 패턴의 DAO를 모사했습니다.

기능 혹은 페이지 단위로 나눌 수 있기에 사용자 관련 로직을 담당하는 customer_manage.js와 글 관련 로직을 담당하는 post.js로 나누고 dao.js에서 통합하여 관리하도록 했습니다.

module.exports = (function(){
  const model = require('../mongoose/model');

  // https://mongoosejs.com/docs/guide.html#id
  async function getUser(email){
    return await model.User.findOne({email: email}); // 없을땐 null
  }

  async function getAllUsers(){
    return await model.User.find();
  }

  async function joinUser(email, pwd){
    if(await getUser(email)) throw "email is existed";

    const newUser = new model.User({email: email, pwd: pwd});
    const result = await newUser.save();

    return result;
  }

  return {
    getUser: getUser,
    getAllUsers: getAllUsers,
    joinUser: joinUser,
  };

})();

<./business/customer_manage.js>

module.exports = (function(){
  const model = require('../mongoose/model');

  async function createPost({title, content, author}){
    const newPost = new model.Post({title: title, content: content, author: author});

    const result = await newPost.save();
    return await result.populate('author').execPopulate();
  }

  async function getAllPosts(){
    return await model.Post.find().populate('author');
  }

  return {
    createPost: createPost,
    getAllPosts: getAllPosts,
  };

})();

<./business/post.js>


각 DAO는 스키마와 모델을 통합시켜둔 mongoose/model.js를 사용하여 기능을 수행하도록 했습니다.

module.exports = {
  cm: require('./customer_manage'),
  post: require('./post'),
};

<./business/dao.js>


DAO들도 GraphQL 단에서 호출하기 편하도록 dao.js 통합하였습니다.



5. API 실행


app.js를 아래와 같이 작성하여 API 서버를 실행하겠습니다.

var express = require('express');
var graphqlHTTP = require('express-graphql');
//var { buildSchema } = require('graphql');

var app = express();

const {schema, root} = require('./graphql/schema');

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'));

<./app.js>


이제 graphiql(localhost:4000/graphql)에 접속하여 쿼리를 테스트 해봅니다.


우선 유저 생성입니다.

<user 생성>


mutation의 createUser로 유저를 생성하는 모습입니다.


GraphQL은 $parameter_name: type 꼴로 외부 값을 사용할 수 있습니다. type뒤에 느낌표를 붙이면 해당 인자를 필수적으로 입력해야 합니다.

graphiql 좌측 하단에 QUERY VARIABLES 탭을 활성화 하여 JSON 형태로 값을 주입할 수 있습니다.


문법에 대한 내용은 다음 글에서 정리하도록 하겠습니다.

<중복된 email>


throw "message" 처리한 부분은 다음과 같은 형태로 노출되게 됩니다.


유저 정보 역시 스키마에서 정의한 users와 user를 사용하여 가져올 수 있습니다.

<모든 유저 정보>

<단일 유저 정보>


유저 생성과 마찬가지로 글 생성도 테스트해봅니다.

<글 생성>


정상적으로 실행되었다면 Post 스키마에 해당하는 내용을 확인할 수 있습니다.

작성한 Post를 가져오는 쿼리는 직접 작성해보면 좋을 것 같습니다.



6. 마치며


이번 시간에는 MongoDB와 GraphQL를 연동하여 사용하는 것을 실습해봤습니다.

갑자기 mutation이나 $ 같은 새로운 것들이 튀어나와 조금 혼란스러울 수 있지만 실제 DB와 연동하면 이런식으로 쓰이겠구나 정도로 봐주시면 좋을 것 같습니다.


다음 글에서는 GraphQL의 몇가지 문법을 정리해보도록 하겠습니다.




'GraphQL' 카테고리의 다른 글

GraphQL Schema의 기본 문법  (1) 2018.10.24
express-graphql 시작하기(Hello World Guide)  (4) 2018.10.14
Comments