이번 강좌에서는 Mongoose 를 통하여 Node.js 에서 MongoDB와 연동하는것을 배워보겠습니다.
1. 소개
Mongoose는 MongoDB 기반 ODM(Object Data Mapping) Node.JS 전용 라이브러리입니다. ODM은 데이터베이스와 객체지향 프로그래밍 언어 사이 호환되지 않는 데이터를 변환하는 프로그래밍 기법입니다. 즉 MongoDB 에 있는 데이터를 여러분의 Application에서 JavaScript 객체로 사용 할 수 있도록 해줍니다.
Note: 이 강좌는 Node.js 와 MongoDB가 설치되있다는 전제하에 진행됩니다.
또한, MongoDB에 전반적인 지식이 없다면 mongoose 사용이 다소 어려울 수 있습니다.
MongoDB 기초 강좌는 여기서 볼 수 있습니다.
2. 프로젝트 생성 및 패키지 설치
Mongoose 를 배워가면서, 간단한 Express 기반의 RESTful 프로젝트를 만들어보도록 하겠습니다.
2.1 프로젝트 생성
우선 npm init 을 통하여 package.json 을 생성하세요. 엔터를 계속 눌러 설정값은 기본값으로 하시면 됩니다.
$ npm init
2.2 패키지 설치
프로젝트에서 사용 할 패키지를 설치하겠습니다.
- express: 웹프레임워크
- body-parser: 데이터 처리 미들웨어
- mongoose: MongoDB 연동 라이브러리
$ npm install --save express mongoose body-parser
명령어를 입력하시면 자동으로 패키지를 설치하고, package.json 파일에 패키지 리스트를 추가합니다.
3. 서버 설정하기
3.1 디렉토리 구조
먼저 저희가 만들 프로젝트의 디렉토리 구조를 살펴봅시다.
- models/ ----- book.js - node_modules/ - routes ----- index.js - app.js - package.json
이 파일들은 강좌를 진행하면서 만들도록 하겠습니다.
3.2 Express 로 이용한 웹서버 생성
mongoose를 사용하기 위해서 우선 book 데이터를 조회·수정·삭제 하는간단한 RESTful 웹서버를 작성해보겠습니다.
이 서버에 만들 API 목록은 다음과 같습니다.
ROUTE | METHOD | DESCRIPTION |
---|---|---|
/api/books | GET | 모든 book 데이터 조회 |
/api/books/:book_id | GET | _id 값으로 데이터 조회 |
/api/books/author/:author | GET | author 값으로 데이터 조회 |
/api/books | POST | book 데이터 생성 |
/api/books/:book_id | PUT | book 데이터 수정 |
/api/books/:book_id | DELETE | book 데이터 제거 |
우선 서버의 메인 파일인 app.js를 작성하세요.
// app.js // [LOAD PACKAGES] var express = require('express'); var app = express(); var bodyParser = require('body-parser'); var mongoose = require('mongoose'); // [CONFIGURE APP TO USE bodyParser] app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); // [CONFIGURE SERVER PORT] var port = process.env.PORT || 8080; // [CONFIGURE ROUTER] var router = require('./routes')(app) // [RUN SERVER] var server = app.listen(port, function(){ console.log("Express server has started on port " + port) });
line 17 에서 router 모듈을 불러오게 했죠? 이제 router를 작성해봅시다.
routes/index.js 에 다음 코드를 입력하세요.
// routes/index.js module.exports = function(app) { // GET ALL BOOKS app.get('/api/books', function(req,res){ res.end(); }); // GET SINGLE BOOK app.get('/api/books/:book_id', function(req, res){ res.end(); }); // GET BOOK BY AUTHOR app.get('/api/books/author/:author', function(req, res){ res.end(); }); // CREATE BOOK app.post('/api/books', function(req, res){ res.end(); }); // UPDATE THE BOOK app.put('/api/books/:book_id', function(req, res){ res.end(); }); // DELETE BOOK app.delete('/api/books/:book_id', function(req, res){ res.end(); }); }
4. MongoDB 서버 연결
MongoDB 서버에 연결 하는 방법은 다음과 같습니다.
// app.js // ...... var mongoose = require('mongoose'); // ...... // [ CONFIGURE mongoose ] // CONNECT TO MONGODB SERVER var 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://localhost/mongodb_tutorial'); // ......
mongoose.connect() 메소드로 서버에 접속을 할 수 있으며, 따로 설정 할 파라미터가 있다면 다음과 같이 uri를 설정하면 됩니다.
mongoose.connect('mongodb://username:password@host:port/database?options...');
이 강좌에서는 mongodb_tutorial db를 사용하도록 하겠습니다.
5. Schema & Model
5.1 schema
schema는 document의 구조가 어떻게 생겼는지 알려주는 역할을 합니다.
schema를 만드는 방법은 다음과 같습니다. 이 코드를 models/book.js 에 입력하세요.
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var bookSchema = new Schema({ title: String, author: String, published_date: { type: Date, default: Date.now } }); module.exports = mongoose.model('book', bookSchema);
schema에서 사용되는 SchemaType은 총 8종류가 있습니다.
- String
- Number
- Date
- Buffer
- Boolean
- Mixed
- Objectid
- Array
이를 사용하는 예제는 매뉴얼을 참고하세요.
5.2 model
model은 데이터베이스에서 데이터를 읽고, 생성하고, 수정하는프로그래밍 인터페이스를 정의합니다.
// DEFINE MODEL var Book = mongoose.model('book', bookSchema);
첫번째 인자 book’ 은 해당 document가 사용 할 collection의 단수적 표현입니다. 즉, 이 모델에서는 ‘books’ collection 을 사용하게 되겠죠. 이렇게 자동으로 단수적 표현을 복수적(plural) 형태로 변환하여 그걸 collection 이름으로 사용합니다. collection 이름을 plural 형태로 사용하는건 mongodb의 네이밍컨벤션 중 하나입니다.
만약에, collection 이름을 임의로 정하고 싶다면, schema 를 만들 때 따로 설정해야 합니다.
var dataSchema = new Schema({..}, { collection: 'COLLECTION_NAME' });
model을 만들고 나면, model 을 사용하여 다음과 같이 데이터를 데이터베이스에 저장하거나 조회 할 수 있습니다.
var book = new Book({ name: "NodeJS Tutorial", author: "velopert" }); book.save(function(err, book){ if(err) return console.error(err); console.dir(book); });
이와 같이 model을 사용하여 데이터베이스와 연동하는 자세한 방법에 대해서는 다음 섹션에서 다루도록 하겠습니다.
저희는 model을 models/bear.js 를 모듈화해서 만들게 할 것이므로, 다음과 같이 해당파일의 마지막줄에서 model을 모듈화하세요.
// models/book.js var Schema = mongoose.Schema; var bookSchema = new Schema({ title: String, author: String, published_date: { type: Date, default: Date.now } }); module.exports = mongoose.model('book', bookSchema);
app.js에서 이 모듈을 불러와보겠습니다.
// app.js // ... // CONNECT TO MONGODB SERVER // ... // DEFINE MODEL var Book = require('./models/book'); // ...
6. CRUD (Create, Retrieve, Update, Delete)
6.0 시작하기 전에..
3번 섹션에서 만들었던 API 를 직접 구현해가면서 데이터를 생성/조회/수정/제거 하는 방법을 알아보겠습니다.|
라우터에서 Book 모델을 사용해야 하므로, 라우터에 Book을 전달해줘야겠죠?
따라서 /routes/index.js 와 app.js 를 우선 수정해야합니다.
// routes/index.js modules.exports = function(app, Book) { // .... }
// app.js // ... var router = require('./routes')(app, Book); // ...
6.1 Create ( POST /api/books )
book 데이터를 데이터베이스에 저장하는 API 입니다.
app.post('/api/books', function(req, res){ var book = new Book(); book.title = req.body.name; book.author = req.body.author; book.published_date = new Date(req.body.published_date); book.save(function(err){ if(err){ console.error(err); res.json({result: 0}); return; } res.json({result: 1}); }); });
.save 메소드는 데이터를 데이터베이스에 저장합니다. err 을 통하여 오류처리가 가능합니다.
이 API 에서는 데이터 저장에 성공하면 result: 1을, 실패하면 result: 0 을 반환합니다.
(REST API 테스팅에 사용된 어플리케이션은 크롬 확장 프로그램 Insomnia 입니다)
6.2.1 RETRIEVE ( GET /api/books )
모든 book 데이터를 조회하는 API 입니다.
// GET ALL BOOKS app.get('/api/books', function(req,res){ Book.find(function(err, books){ if(err) return res.status(500).send({error: 'database failure'}); res.json(books); }) });
데이터를 조회 할 때는 find() 메소드가 사용됩니다.
query를 파라미터 값으로 전달 할 수 있으며, 파라미터가 없을 시, 모든 데이터를 조회합니다.
데이터베이스에 오류가 발생하면 HTTP Status 500 과 함께 에러를 출력합니다.
6.2.2 RETRIEVE ( GET /api/books/:book_id )
데이터베이스에서 Id 값을 찾아서 book document를 출력합니다.
// GET SINGLE BOOK app.get('/api/books/:book_id', function(req, res){ Book.findOne({_id: req.params.book_id}, function(err, book){ if(err) return res.status(500).json({error: err}); if(!book) return res.status(404).json({error: 'book not found'}); res.json(book); }) });
하나의 데이터만 찾을 것이기 때문에, findOne 메소드가 사용되었습니다.
오류가 발생하면 500, 데이터가 없으면 404 HTTP Status 와 함께 오류를 출력합니다.
6.2.3 RETRIEVE ( GET /api/books/author/:author )
author 값이 매칭되는 데이터를 찾아 출력합니다.
// GET BOOKS BY AUTHOR app.get('/api/books/author/:author', function(req, res){ Book.find({author: req.params.author}, {_id: 0, title: 1, published_date: 1}, function(err, books){ if(err) return res.status(500).json({error: err}); if(books.length === 0) return res.status(404).json({error: 'book not found'}); res.json(books); }) });
find() 메소드에서 첫번째 인자에는 query 를, 두번째는 projection 을 전달해주었습니다.
이를 통하여 author 값으로 찾아서 title 과 published_date 만 출력합니다.
(만약에 projection이 생략되었다면 모든 field 를 출력합니다.)
6.3 UPDATE ( PUT /api/books/:book_id )
book_id 를 찾아서 document를 수정합니다.
// UPDATE THE BOOK app.put('/api/books/:book_id', function(req, res){ Book.findById(req.params.book_id, function(err, book){ if(err) return res.status(500).json({ error: 'database failure' }); if(!book) return res.status(404).json({ error: 'book not found' }); if(req.body.title) book.title = req.body.title; if(req.body.author) book.author = req.body.author; if(req.body.published_date) book.published_date = req.body.published_date; book.save(function(err){ if(err) res.status(500).json({error: 'failed to update'}); res.json({message: 'book updated'}); }); }); });
데이터를 수정 할 땐, 데이터를 먼저 찾은 후, save() 메소드를 통하여 수정하면 됩니다.
update하는 방법은 이 외에도 다른 방법이 있는데요, 만약 어플리케이션에서 기존 document를 굳이 조회 할 필요가없다면
update() 메소드를 통하여 바로 document를 업데이트 할 수 있습니다.
아래 코드는 코드와 같은 동작을 하지만 업데이트하는 과정에서 document를 조회 하지 않습니다.
// UPDATE THE BOOK (ALTERNATIVE) app.put('/api/books/:book_id', function(req, res){ Book.update({ _id: req.params.book_id }, { $set: req.body }, function(err, output){ if(err) res.status(500).json({ error: 'database failure' }); console.log(output); if(!output.n) return res.status(404).json({ error: 'book not found' }); res.json( { message: 'book updated' } ); }) });
여기서 output 은 mongod 에서 출력하는 결과물입니다.
{ ok: 1, nModified: 0, n: 1 }
여기서 nModified는 변경한 document 갯수, n은 select된 document 갯수입니다.
update() 를 실행하였을 떄, 기존 내용이 업데이트 할 내용과 같으면 nModified 는 0 으로 되기 때문에,
n 값을 비교하여 성공여부를 판단합니다.
6.4 DELETE ( /api/books/:book_id )
book_id를 찾아서 document를 제거합니다.
// DELETE BOOK app.delete('/api/books/:book_id', function(req, res){ Book.remove({ _id: req.params.book_id }, function(err, output){ if(err) return res.status(500).json({ error: "database failure" }); /* ( SINCE DELETE OPERATION IS IDEMPOTENT, NO NEED TO SPECIFY ) if(!output.result.n) return res.status(404).json({ error: "book not found" }); res.json({ message: "book deleted" }); */ res.status(204).end(); }) });
document를 제거 할 땐 remove() 메소드가 사용됩니다.
DELETE 는 idempotent(어떤 과정을 몇번이고 반복 수행 하여도 결과가 동일함; 즉 삭제한 데이터를 삭제하더라도, 존재하지 않는 다큐먼트를 제거 시도를하더라도 달라질게 없음) 하므로, 성공하였을떄나 실패하였을때나 결과값이 같습니다. 여기서 204 HTTP status 는 No Content 로서, 요청한 작업을 수행하였고 데이터를 반환 할 필요가 없다는것을 의미합니다.6~9 번줄은 실제로 존재하는 데이터를 삭제하였는지 확인해주는 코드이나, 그럴 필요가 없으므로 주석처리되었습니다.
마치면서..
이 강좌에서 사용된 프로젝트는 깃헙 (/velopert/mongoose_tutorial) 에 업로드되어있습니다. 테스트 하고싶으신 분은 참조하세요.
다음 강좌에서는 gulp를 다뤄보도록하겠습니다.