클라우드 컴퓨팅 & NoSQL/Vert.x & Node.js

빠르게 훝어 보는 node.js - mongoose ODM 을 이용한 MongoDB 연동

Terry Cho 2016. 3. 25. 13:48

빠르게 훝어 보는 node.js - mongoose ODM 을 이용한 MongoDB 연동


조대협 (http://bcho.tistory.com)



 

Mongoosemongodb 기반의 node.jsODM (Object Data Mapping) 프레임웍이다.

이미 앞에서 monk를 이용한 mongodb 연결 방식에 대해서 알아보았는데, mongoose는 무엇인가? 쉽게 생각하면 monk를 자바에서 JDBC 드라이버로 직접 데이타 베이스에 연결하여 SQL을 실행한 거라면, monggoseHibernate/Mybatis와 같이 OR Mapper의 개념이다. 직접 SQL을 실행하는 것이 아니라 node.js의 자바스크립트 객체를 mongodb의 도큐먼트와 맵핑 시켜 주는 개념이다.

이런ODM의 개념은 웹 프로그램에서 주로 사용되는 MVC (Model View Controller)의 개념을 모델 객체를 실제 데이타 베이스 데이타에 맵핑 시킴으로써 쉽게 구현해주게 한다. 또한 자바스크립트가 가지고 있는 데이타에 대한 모호성을 극복하게 해주는데, mongodb는 특성상 데이타에 대한 스키마가 없기 때문에 자유도가 높아서 유연하게 사용이 가능하지만 반대로 RDBMS에서 정의된 스키마나 명시적인 테이블 구조가 없기 때문에, 어떤 컬럼이 어떤 데이타 타입으로 있는지를 알기가 어렵다. 이러한 문제를 보완하기 위해서 mongoose는 스키마라는 개념을 사용한다.

 

앞에 monk로 만든 예제를 mongoose로 변환한 예제를 살펴보도록 하자.

이 예제는 express 4.X 버전을 기준으로 하니 express generator를 통해서 express 프로젝트를 먼저 생성하자.

 

mongoose 설치

프로젝트 생성이 끝났으면 “npm install mongoose” 명령어를 이용하여, mongoose 모듈을 설치하고, 다른 서버에 배포할 것을 염두하여 package.json에 의존성을 추가한다.

{

  "name": "mongooseExpress",

  "version": "0.0.0",

  "private": true,

  "scripts": {

    "start": "node ./bin/www"

  },

  "dependencies": {

    "body-parser": "~1.13.2",

    "cookie-parser": "~1.3.5",

    "debug": "~2.2.0",

    "express": "~4.13.1",

    "jade": "~1.11.0",

    "mongoose": "~4.4.9",

    "morgan": "~1.6.1",

    "serve-favicon": "~2.3.0"

  }

}

 

 

mongoDB 연결

다음으로 app.js에서, mongoose 모듈을 로딩하고, mongoDB에 연결한다.

var app = express();

 

// view engine setup

app.set('views', path.join(__dirname, 'views'));

app.set('view engine', 'jade');

 

// uncomment after placing your favicon in /public

//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));

app.use(logger('dev'));

app.use(bodyParser.json());

app.use(bodyParser.urlencoded({ extended: false }));

app.use(cookieParser());

app.use(express.static(path.join(__dirname, 'public')));

 

//mongoose configuration

 

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb');

 

var mongo = require('./routes/mongo.js');

app.use('/', mongo)

 

app.js에서 위와 같은 require를 이용하여 mongoose 모듈을 로딩하고

mongoose.connect명령어를 이용하여, 몽고 DB연결한다. 연결 문자열은 monk와 같게

mongodb://{mongodb ip 주소}:{포트}/{DB}

이다. 위의 예제는 localhost 27017 포트의 mongodbmydbDB에 연결하는 문자열이다.

클러스터된 mongodb 연결 및 timeout등 다양한 설정을 지원하기 때문에, 설정에 대해서는 http://mongoosejs.com/docs/connections.html 를 참고하기 바란다.

 

다음으로, /로 들어오는 요청을 /routes/mongo.js 에서 처리할 수 있도록 app.use / 경로를 mongo 모듈로 맵핑 시킨다.

 

스키마 정의

mongoose에는 스키마라는 개념이 존재하는데, 스키마는 mongoDB에 저장되는 도큐먼트의 데이타 형태를 정의한다. 쉽게 말해서 RDBMS에서 일종의 테이블 정의라고 이해 하면된다.

 

이 예제에서는 HTTP 호출을 처리하는 routes/mongo.js에 스키마를 정의하였다. 아래 코드를 보자.

아래 코드는 routes/mongo.js의 첫 부분이다.

 

var express = require('express');

var router = express.Router();

 

 

var mongoose = require('mongoose');

//define scheme

var userSchema = mongoose.Schema({

      userid: String,

      sex : String,

      city : String

});

 

 

require 구문을 이용해서 mongoose 모듈을 로드하였고,

mongoose.Schema 명령을 이용해서 userSchema를 정의하였다.

Schema 정의 부분을 보면, userSchemauserid, sex, city 3개의 필드로 정의되고, 각 필드는 String 데이타 타입을 사용한다. 구체적인 스키마 타입에 대해서는 뒤에서 설명한다.

 

데이타 핸들링

그러면 데이타를 읽고,쓰고,삭제하고,수정하는 방법을 알아보자. 앞의 monk 예제와 같이 /insert /list /update /delete /get에 대한 라우팅 메서드를 구현해보자

 

앞의 코드에 이어서 insert 부분을 먼저 살펴보자

             

 

// create model with mongodb collection & scheme

var User = mongoose.model('users',userSchema);

 

router.post('/insert', function(req, res, next) {

      var userid = req.body.userid;

      var sex = req.body.sex;

      var city = req.body.city;

     

      var user = new User({'userid':userid,'sex':sex,'city':city});

      user.save(function(err,silence){

             if(err){

                console.log(err);

                res.status(500).send('update error');

                return;

             }

             res.status(200).send("Inserted");

            

         });

});

 

 

가장 중요한 부분은 먼저 앞에서 만든 userSchema를 가지고 모델 객체를 생성해야 한다. 모델 객체 생성 방법은

mongoose.model(‘{mongodb에서 Collection }’, {스키마객체명})

이다. 이 포맷에 따라서 다음과 같이 User라는 모델 객체를 생성하였다.

 

var User = mongoose.model('users',userSchema);

 

User라는 모델 객체는 쉽게 생각하면 mongodbCollection을 대변하는 node.js상의 객체라고 생각하면 된다.

새로운 user 정보를 추가하기 위해서 post 메서드에서 User 모델로 새로운 user 객체를 생성한다. 객체를 생성할때는 JSON을 이용해서 스키마 포맷에 맞춰서 저장하고자 하는 키와 값을 다음과 같이 저장해주면된다.

 

var user = new User({'userid':userid,'sex':sex,'city':city});

 

그리고 간단하게 user 객체를 save 메서드를 이용해서 저장해주면 mongodb 저장된다.

 

다음은 삭제 부분을 보자

router.post('/delete', function(req, res, next) {

      var userid = req.body.userid;

     

      var user = User.find({'userid':userid});

      user.remove(function(err){

             if(err){

                console.log(err);

                res.status(500).send('update error');

                return;

             }

             res.status(200).send("Removed");

            

         });

});

 

userid POST 받은 후에 User.find 명령을 이용하여 ‘userid’필드가 POST에서 전달 받은 userid 필드를 검색하여 삭제하고자 하는 도큐먼트를 찾아낸 후에remove 메서드를 이용해서 삭제한다.

 

다음은 업데이트 부분이다. userid 도큐먼트를 조회해서, 내용을 업데이트 하는 로직이다.

router.post('/update', function(req, res, next) {

      var userid = req.body.userid;

      var sex = req.body.sex;

      var city = req.body.city;

      User.findOne({'userid':userid},function(err,user){

 

           if(err){

               console.log(err);

               res.status(500).send('update error');

               return;

          }

           user.sex = sex;

           user.city = city;

           user.save(function(err,silence){

                  if(err){

                     console.log(err);

                     res.status(500).send('update error');

                     return;

                  }

                  res.status(200).send("Updated");

                 

              });

      });

});

 

 

User.findOne 이용해서 userid 도큐먼트를 조회한 후에, 콜백으로 리턴되는 user 객체의 sex,city 값을 변경한후에

user.save 이용하여 저장하였다.

 

다음은 전체 리스트를 조회하는 부분이다.

router.get('/list', function(req, res, next) {

      User.find({},function(err,docs){

           if(err) console.log('err');

           res.send(docs);

      });

});

 

 find 메서드를 사용하면 되는데, find 메서드의 인자로 검색 조건을 넘기면 된다. 전체 검색이기 때문에 별도의 조건없이 {} 사용하였다.

 

다음은 userid로 도큐먼트를 조회하는 부분이다.

전체리스트와 유사하지만 findOne 메서드를 이용하여 userid에 해당하는 사용자 도큐먼트를 조회하여 출력한다.

router.get('/get', function(req, res, next) {

      db = req.db;

      var userid = req.query.userid

      User.findOne({'userid':userid},function(err,doc){

           if(err) console.log('err');

            res.send(doc);

      });

});

 

module.exports = router;

 

 

route 처리 로직에 요청을 보내는 html 파일을 만들자. 이 파일은 앞서 monk 예제에서 만든 파일과 동일하다.

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>Insert title here</title>

</head>

<body>

 

<h1> Mongoose Test Example</h1>

<form method='post' action='/insert' name='mongoform' >

      user <input type='text' size='10' name='userid'>

      <input type='submit' value='delete' onclick='this.form.action="/delete"' >

      <input type='button' value='get' onclick='location.href="/get?userid="+document.mongoform.userid.value' >

      <p>

      city <input type='text' size='10' name='city' >

      sex <input type='radio' name='sex' value='male'>male

      <input type='radio' name='sex' value='female'>female

      <p>

      <input type='submit' value='insert' onclick='this.form.action="/insert"' >

      <input type='submit' value='update' onclick='this.form.action="/update"' >

      <input type='button' value='list'  onclick='location.href="/list"' >

     

</form>

</body>

</html>

 

/public/mongoose.html이라는 파일로 저장을 하자.

모든 코드 작성이 끝났다. 이를 실행해보면 다음과 같이 데이타를 넣을 수 있는 입력창이 나온다.

 



Figure 1 mongoose 예제에서 데이타를 입력하는 화면

 

데이타를 입력하고, insert를 이용하여 데이타를 저장한후 다시 get 버튼을 눌러서 데이타를 조회해보면 다음과 같이 useriddavid인 도큐먼트가 조회되는 것을 확인할 수 있다.



Figure 2 mongoose 예제에서 useriddavid인 도큐먼트를 조회한 결과

 

쿼리

 

간단한 삽입,삭제,수정,조회만 앞에서 살펴보았는데, 이외에도 다양한 조회 기능을 제공한다. 자세한 쿼리 사용 방법은 http://mongoosejs.com/docs/documents.html 를 참고하면 된다.

몇 가지 쿼리들을 살펴보도록 하자. 다음 예제를 살펴보자

 

 

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb');

var userSchema = mongoose.Schema({

      userid: String,

      sex : String,

      city : String,

      age : Number

});

 

var User = mongoose.model('users',userSchema);

 

// select city from users where userid='terry'

User.findOne({'userid':'terry'}).select('city').exec(function(err,user){

      console.log("q1");

      console.log(user+"\n");

      return;

});

 

 

// select * from users where city='seoul' order by userid limit 5

User.find({'city':'seoul'}).sort({'userid':1}).limit(5).exec(function(err,users){

      console.log("q2");

      console.log(users+"\n");

      return;

});

 

// using JSON doc query

// select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({'city':'seoul', 'age':{$gt:10 , $lt:29}})

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q3");

           console.log(users+"\n");

           return;

});

 

//using querybuilder

//select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({})

      .where('city').equals('seoul')

      .where('age').gt(10).lt(29)

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q4");

           console.log(users+"\n");

           return;

});

 

 

코드 첫 부분에서는

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb');

var userSchema = mongoose.Schema({

      userid: String,

      sex : String,

      city : String,

      age : Number

});

 

var User = mongoose.model('users',userSchema);

 

mongoose 모듈을 로딩하고, mongodb에 연결한다. 다음 스키마를 정의 한다. userid, sex, city를 문자열로 가지고, age라는 필드를 숫자로 갖는다. 스키마 정의가 끝났으면 이 스키마를 이용해서 users 컬렉션에 대해서 User 모델 객체를 생성한다.

 

첫번째 쿼리를 살펴보자

// select city from users where userid='terry'

User.findOne({'userid':'terry'}).select('city').exec(function(err,user){

      console.log("q1");

      console.log(user+"\n");

      return;

});

 

첫번째 쿼리는 useridterry인 도큐먼트를 쿼리해서 그중에서 city 라는 이름의 필드만 리턴하는 쿼리이다.

실행 결과는 다음과 같다.

 

q1

{ city: 'seoul', _id: 56e62f2c1a2762d26afa6053 }

 

두번째 쿼리를 살펴보자

// select * from users where city='seoul' order by userid limit 5

User.find({'city':'seoul'}).sort({'userid':1}).limit(5).exec(function(err,users){

      console.log("q2");

      console.log(users+"\n");

      return;

});

 

다음은 실행결과이다.

 

q2

{ age: 18,

  city: 'seoul',

  sex: 'female',

  userid: 'cath',

  _id: 56e62f351a2762d26afa6054 },{ age: 23,

  city: 'seoul',

  sex: 'female',

  userid: 'stella',

  _id: 56e62f3c1a2762d26afa6055 },{ age: 29,

  city: 'seoul',

  sex: 'male',

  userid: 'terry',

  _id: 56e62f2c1a2762d26afa6053 },{ age: 27,

  city: 'seoul',

  sex: 'female',

  userid: 'yuna',

  _id: 56e62f411a2762d26afa6056 }

 

users 컬렉션에서 city 필드가 seoul인 도큐먼트를 조회한후 결과를 sort({‘userid’:1}) 을 이용하여 userid에 따라 오름차순으로 정렬한 후에, limit(5)를 이용하여 결과중 5개만 리턴한다.

 

세번째 쿼리를 보면

// using JSON doc query

// select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({'city':'seoul', 'age':{$gt:10 , $lt:29}})

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q3");

           console.log(users+"\n");

           return;

});                                 

 

users 컬렉션에서 cityseoul이고, age 10보다 크고, 29보다 작은 도큐먼트를 조회한 후 sort를 이용하여 내림 차순으로 정렬을 한 후, select를 이용하여 useridage 필드만 리턴한다

다음은 쿼리 실행결과이다.

 

q3

{ age: 23, userid: 'stella', _id: 56e62f3c1a2762d26afa6055 },{ age: 18, userid: 'cath', _id: 56e62f351a2762d26afa6054 }

 

마지막 네번째 쿼리를 보면

//using querybuilder

//select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({})

      .where('city').equals('seoul')

      .where('age').gt(10).lt(29)

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q4");

           console.log(users+"\n");

           return;

});

 

세번째 쿼리와 내용을 같으나, 검색 조건을 find() 안에서 json으로 정의하지 않고, 밖에 where문으로 빼서 정의했다. 이를 쿼리 빌더라고 하는데, mongoosewhere문에 따라서 자동으로 쿼리를 생성해준다.

.where('city').equals('seoul') city필드가 seoul 도큐먼트를 조회하고

.where('age').gt(10).lt(29) age 10보다 크고, 29보다 작은 도큐먼트를 조회하도록 한다.

다음은 쿼리 수행 결과이다.

q4

{ age: 23, userid: 'stella', _id: 56e62f3c1a2762d26afa6055 },{ age: 18, userid: 'cath', _id: 56e62f351a2762d26afa6054 }