Tạo Api đơn giản với Restify và MongoDB

Giang Coffee
6 min readAug 20, 2019

--

bài trước chúng ta đã cùng nhau xây dựng một web service đơn giản với Restify, hiểu được cách đặt các url hay còn gọi là route, cách lấy tham số từ route, cách đặt method cho route. Tuy nhiên, ứng dụng của chúng ta là ứng dụng tĩnh, chứa các thông tin tĩnh, quá nghèo nàn so với yêu cầu và sự phát triển của công nghệ hiện nay. Do đó ứng dụng cần phải kết hợp với một cơ sở dữ liệu nhằm cải thiện vấn đề này. Các bạn có thể chọn bất cứ một CSDL nào: MySQl, PostGreSQL … nhưng thường ta sẽ sử dụng MongoDB.

1. Cài đặt gói phần mềm phụ thuộc

Để kết nối đến MongoDB ta trước hết cần cài đặt MongoDB server trên máy mình. Các bạn dùng Ubuntu có thể tham khảo bài viết này còn các bạn dùng Windows có thể tham khảo bài viết này. Để kiểm tra service MongoDb đã được bật hay chưa các bạn có thể gõ lệnh :

$ mongo
MongoDB shell version v3.6.4
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.4

Lệnh mongo không kèm theo tùy chọn nào sẽ kết nối đến địa chỉ mặc định localhost và cổng mặc định 27017. Để kết nối đến MongoDb từ nodejs chúng ta cần một thư viện rất phổ biến đó là Mongoose mà mình đã đề cập một chút trong bài Học Javascript Promise trong 10 phút. Có 2 cách cài đặt mongoose :

npm install --save mongoose
npm install --save mongoose-timestamp

tùy chọn --save sẽ lưu thay đổi này vào file package.json để khi cài đặt ứng dụng ở một máy khác chúng ta chỉ cần phải npm install để cài đặt tất cả các gói phần mềm phụ thuộc. Cách thứ hai chúng ta có thể thêm trực tiếp 2 gói trên vào file package.json ở phần dependencies, sau đó chạy lệnh npm install. Như vậy file package.json sẽ có nội dung như sau:

{
"name": "restify_tutorial_part1",
"version": "1.0.0",
"description": "Restify tutorial part1",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"repository": {
"type": "git",
"url": "restify_tutorial_part1" // điền url git của bạn nếu có
},
"keywords": [
"restify"
],
"author": "GiangCoffee", // tên bạn
"license": "ISC",
"dependencies": {
"mongoose": "^5.2.3",
"mongoose-timestamp": "^0.6.0",
"restify": "^7.2.1"
}
}

2. Cấu trúc thư mục

restify_part2/
|__src/
| |_models/
| |_controllers/
| |_repositories/
| |_routes/
|
|__index.js
|__package.json
  • models chứa định nghĩa các collection trong database, ở trong ví dụ này gồm usertask
  • repositories chứa các lệnh query từ các collection được định nghĩa ở trên
  • controllers là điểm kết nối giữa repositories với các routes bên ngoài
  • routes chứa các định nghĩa về api url

Tạo file todoRouter.js trong thư mục routers với nội dung :

"use strict";

const TodoController = require('../controllers/todoController');

module.exports = function(server){
/**
* POST
*/
server.post('/api/v1/todos', TodoController.create);

/**
* LIST
*/
server.get('/api/v1/todos', TodoController.list);

/**
* GET
*/
server.get('/api/v1/todos/:id', TodoController.one);

/**
* UPDATE
*/
server.put('/api/v1/todos/:id', TodoController.update);

/**
* DELETE
*/
server.del('/api/v1/todos/:id', TodoController.remove);
};

Ở đây ta chỉ định nghĩa 5 phương thức (CRUD) và thêm một phương thức GET. Mỗi khi có một url api match một route nào đó thì hàm controller tương ứng sẽ được gọi. Tạo file todoController.js như đã được import ở trên, trong thư mục controllers với nội dung:

"use strict";

const TodoRepository = require('../repositories/todoRepository');

/**
* @param req
* @param res
* @param next
*/
function create(req, res, next) {
let data = req.body || {};

TodoRepository.save(data)
.then(function (todo) {
res.send(201, todo);
next();
})
.catch(function (error) {
console.error(error);
return next(error);
})
.done();
}

/**
*
* @param {*} req
* @param {*} res
* @param {*} next
*/
function update(req, res, next) {
let data = req.body || {};

if (!data._id) {
data = Object.assign({}, data, {_id: req.params.id});
}

TodoRepository
.update(data._id, data)
.then(function (todo) {
res.send(200, todo);
next();
})
.catch(function (error) {
console.error(error);
return next(error);
})
.done();
}

/**
* @param req
* @param res
* @param next
*/
function list(req, res, next) {
TodoRepository
.getList()
.then(function (todos) {
res.send(todos);
next();
})
.catch(function (error) {
console.error(error);
return next(error);
})
.done();
}

/**
* @param req
* @param res
* @param next
*/
function one(req, res, next) {
TodoRepository
.findById(req.params.id)
.then(function (todo) {
res.send(todo);
next();
})
.catch(function (error) {
console.log(error);
return next(error);
})
.done();
}

/**
* @param {*} req
* @param {*} res
* @param {*} next
*/
function remove(req, res, next) {
TodoRepository
.remove(req.params.id)
.then(function (deleted) {
res.send(204);
next();
})
.catch(function (error) {
console.error(error);
return next(error);
})
.done();
}

module.exports = {
one: one,
list: list,
create: create,
remove: remove,
update: update
};

trong file controller này có import file repository, tạo file todoRepository.js trong thư mục models với nội dung :

"use strict";

const Todo = require('../models/todo');
const Q = require("q");
const errors = require('restify-errors');

/**
* @param {*} data
*/
function save(data) {
const deferred = Q.defer();
let todo = new Todo(data);

todo.save(function (error, result) {
if (error) {
console.error(error);
deferred.reject(new errors.InvalidContentError(error.message));
} else {
deferred.resolve(result);
}
});

return deferred.promise;
}

/**
* @param id
* @param data
* @returns {*|promise}
*/
function update(id, data) {
const deferred = Q.defer();

Todo.update({ _id: id }, data, function (error, updated) {
if (error) {
deferred.reject(new errors.InvalidContentError(error.message));
} else {
deferred.resolve(updated);
}
});

return deferred.promise;
}

/**
*
* @param params
* @returns {*|promise}
*/
function getList(params) {
const deferred = Q.defer();

Todo.find({})
.populate('user')
.exec(function (error, todos) {
if (error) {
console.error(error);
deferred.reject(
new errors.InvalidContentError(error.message)
);
} else {
deferred.resolve(todos);
}
});

return deferred.promise;
}

/**
*
* @param id
* @returns {*|promise}
*/
function findById(id) {
const deferred = Q.defer();

Todo.findOne({ _id: id })
.populate('user')
.exec(function (error, todo) {
if (error) {
console.error(error);
deferred.reject(new errors.InvalidContentError(error.message));
} else if (!todo) {
deferred.reject(new errors.ResourceNotFoundError(
'The resource you requested could not be found.'
));
} else {
deferred.resolve(todo);
}
});

return deferred.promise;
}

/**
* @param id
* @returns {*|promise}
*/
function remove(id) {
const deferred = Q.defer();

Todo.remove({ _id: id }, function(error) {
if (error) {
console.error(error);
deferred.reject(
new errors.InvalidContentError(error.message)
);
} else {
deferred.resolve(true);
}
});

return deferred.promise;
}

module.exports = {
save: save,
update: update,
remove: remove,
findById: findById,
getList: getList
};

để ý cách gọi populate('user') sẽ tìm kiếm user theo ObjectId được lưu và trả lại một User object thay vì chỉ là ObjectId sau đó trả về cho callback. Tất cả các hàm đều trả về Promise

Tiếp theo phải định nghĩa các trường trong collection Todo, Tạo file todo.js trong thư mục models với nội dung :

"use strict";

const mongoose = require('mongoose');
const timestamps = require('mongoose-timestamp');
const ObjectId = mongoose.Schema.Types.ObjectId;

const TodoSchema = new mongoose.Schema(
{
user: {
type: ObjectId,
required: true,
ref: "User"
},
name: {
type: String,
required: true
},
description: {
type: String,
required: true
}
},
{ minimize: false }
);

TodoSchema.plugin(timestamps);

const Todo = mongoose.model('Todo', TodoSchema);
module.exports = Todo;

mongoose-timestamp plugin giúp tự thêm các trường createdAt khi tạo mới, và updatedAt khi cập nhật. Property ref tạo liên kết giữa collection Todo với collection User sẽ tạo ở bước sau.

Tương tự tạo các file userRouter.js, userController.js, userRepository.jsuser.js. Source code được để tại đây

Việc cuối cùng là móc nối các route định nghĩa ở trên vào server đang listen. Tạo file index.js với nội dung như sau :

"use strict";

const restify = require('restify');
const mongoose = require('mongoose');
const UserRouter = require('./routes/userRouter.js');
const TodoRouter = require('./routes/todoRouter.js');


const server = restify.createServer();

server.use(restify.plugins.bodyParser());
server.use(restify.plugins.acceptParser(server.acceptable));
server.use(restify.plugins.queryParser({ mapParams: true }));
server.use(restify.plugins.fullResponse());

server.listen(8080, function() {
// establish connection to mongodb
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/todos');

const db = mongoose.connection;
db.on('error', function (err) {
process.exit(1);
});

db.once('open', function () {
UserRouter(server);
TodoRouter(server);
});

console.log('%s listening at %s', server.name, server.url);
});

Khởi động server

node index.js

Tạo User bằng Advanced Rest Client

Hình 1: Tạo user bằng Rest Client
Hình 2: Lấy danh sách User

--

--