Cette fois, je vais vous présenter Nodejs pour utiliser Mongoose pour créer des modèles et des API. Quelles sont les précautions à prendre pour utiliser Mongoose pour créer des modèles et des API dans Nodejs ? Ce qui suit est un cas pratique, jetons un coup d'œil.
MongoDB est une base de données de stockage de documents distribuée, différente de la base de données relationnelle habituellement utilisée. Elle stocke le format BSON (binaire JSON), qui se caractérise par des performances élevées et une simplicité d'utilisation. déploiement, facile à utiliser... (voir l'encyclopédie pour plus de détails). Le point principal est qu'il prend en charge la lecture et l'écriture de JavaScript. La plus grande caractéristique du développement de la pile MEAN est que le front-end et le back-end, y compris la base de données, sont tous écrits. en JavaScript. Mongoose peut être comparé à EntityFramwork (ORM-Object Relational Mapping), et sa fonction principale est de permettre aux développeurs d'exploiter la base de données plus facilement. Avant de commencer, pensez à installer mongodb en premier.
1. Préparation de MongoDB
Si "l'ordinateur rejette activement" apparaît, vous devez définir le dbpath, qui est le chemin. aux données spécifiées.
mongod --dbpath=c:\mongodb\db
Après succès, vous pouvez visiter http://localhost:28017/ et vous pouvez voir la page suivante :
indiquant que tout est prêt.
D:\mongodb\bin>mongod --dbpath=D:\mongodb --logpath=D:\mongodb\log.txt --install
Remplacez le chemin ci-dessus par votre propre chemin, puis entrez services.msc。设置服务的时候要确保mongo在运行状态,不然设置不会成功。
@echo off start "" "D:\mongodb-win32-i386-2.0.6\bin\mongo.exe"
show dbs
switched to db dbname 或者: use dbname
show collections
db.collections.find()
var mongoose = require('mongoose');
mongodb不需要在连接它之前创建数据库,当第一次连接的时候,会根据链接自动创建。Mongoose在连接MongoDB的时候会创建一个有五个可重用的连接的连接池,连接数是可以配置的。这么做的原因是因为连接数据库是比较耗时的操作,特别是分布式的数据库。连接字符串的规则如下:
这里用户名、密码、端口都是可以省略的,那么在本地的时候我们的连接字符串就如下:
var dbURI = 'mongodb://localhost/RClub'; mongoose.connect(dbURI);
命名我们的数据库为RClub。Mongoose会基于连接的状态发布不同的事件:
mongoose.connection.on('connected', function () { console.log('Mongoose connected to ' + dbURI); }); mongoose.connection.on('error', function (err) { console.log('Mongoose connection error: ' + err); }); mongoose.connection.on('disconnected', function () { console.log('Mongoose disconnected'); });
如此我们可以监听连接的状态。另外应用终止需要监听nodejs的进程的SIGINT事件。而如果是nodemon重启,需要监听的是SIGUSR2事件,以便关闭连接。
// 当应用重启或终止的时候 关闭连接Shutdown = function (msg, callback) { mongoose.connection.close(function () { console.log('Mongoose disconnected through ' + msg); callback(); }); };// nodemon 重启 process.once('SIGUSR2', function () { Shutdown('nodemon restart', function () { process.kill(process.pid, 'SIGUSR2'); }); });// 应用终止process.on('SIGINT', function () { Shutdown('app termination', function () { process.exit(0); }); });
View Code
运行nodemon:
显示连接成功!
Mongoose封装了mongodb的api使之更便于调用,但作为MongoDB的对象-文档建模器,有着更强大的功能。在mongodb中,每一个条目就是一个document,相当于关系型数据库中的行,而document的集合称为collection,相当于是关系型数据库中的table。在Mongoose中定义一个document的对象称为schema,相当于用EF定义一个Model,而定义schema中每一条数据的规则称为path(约束)。path的规则和jquery.validate.js几乎是一样的。
path支持的数据类型如下:
String
Number
Date
Boolean
Buffer 二进制,比如图形
Mixed 任何类型
Array 可以为数组,或者内嵌的 子文档集
ObjectId 唯一id
接下来演示下如何用Mongoose建一个数据模型,在models文件夹下创建一个books.js。然后引用Mongoose。如下,更具上一节book对象创建一个bookSchema 。
{ id: 0"深入浅出Node.js""朴灵 / 人民邮电出版社 / 2013-12-1 / CNY 69.00"5"https://img3.doubanio.com/mpic/s27269296.jpg""node", "深入浅出"'本书从不同的视角介绍了 Node 内在的特点和结构。.....9787115335500
id不用创建,mongo会为每个document生成一个唯一的id。
var mongoose = require('mongoose');var bookSchema = new mongoose.Schema({ title: String, rating: { type: Number, required: true, min: 0, max: 5 }, info: String, img: String, tags: [String], brief: String, ISBN: String });
这种语法是不是和jquery.validate.js很像,相信不用解释就能看明白,不满足path的条件时是不能保存或者更改的。每个path还可以设置默认值,比如时间:
createdOn: { type: Date, default: Date.now },
一个Schema可以包含另外的Schema或数组,在关系型数据库中这种需求用的是外键或者是关系映射表,而Mongoose这样看起来直观多了。
var userSchema = new mongoose.Schema({ userName: String, email: String, ...});var commentSchema = new mongoose.Schema({ user: userSchema, ... content: String });var topicSchema = new mongoose.Schema({ .... visitedCount: { type: Number, default: 0 }, ... comments: [commentSchema], deleted: { type: Boolean, default: false }, top: { type: Boolean, default: false }, // 置顶帖 ... });
这个时候的shema 还不具备数据库的操作能力,还需要注册下。
mongoose.model('Book', bookSchema); mongoose.model('Topic', topicSchema);
全部模型:
var mongoose = require('mongoose');var bookSchema = new mongoose.Schema({ title: String, rating: { type: Number, required: true, min: 0, max: 5 }, info: String, img: String, tags: [String], brief: String, ISBN: String });var userSchema = new mongoose.Schema({ userName: String, email: String, password: String, createdOn: { type: Date, default: Date.now }, img: String, ip: String, mobile: String });var commentSchema = new mongoose.Schema({ user: userSchema, createdOn: { type: Date, default: Date.now }, content: String });var topicSchema = new mongoose.Schema({ title: String, type: String, visitedCount: { type: Number, default: 0 }, commentCount: { type: Number, default: 0 }, createdOn: { type: Date, default: Date.now }, img: String, author: String, content: String, comments: [commentSchema], deleted: { type: Boolean, default: false }, top: { type: Boolean, default: false }, // 置顶帖 good: { type: Boolean, default: false }, // 精华帖}); mongoose.model('Book', bookSchema); mongoose.model('Topic', topicSchema);
View Code
接下来就是改造controllers文件夹的中的home.js了,首先要引用db.js和Mongoose。
var db = require('../models/db.js');var mongoose = require('mongoose');
1).初始化数据,查询与新建数据。
这里把上一节创建的数据books和topics存入数据库中:
module.exports.init = function (req, res) { mongoose.model('Book').find().exec(function (err, objs) { if (err) { res.render('error', { message: err.message, error: err }); return; } if (objs.length) { for (var i = 0; i < books.length; i++) { var taget = new Bookmodel(books[i]);//创建一个模型 taget.save(function (err) {//保存。 console.log(err); }); } for (var i = 0; i < topics.length; i++) { var taget = new topicmodel(topics[i]); console.log('topic create'); taget.save(function (err) { console.log(err); }); } res.send('初始化完成...'); } res.render('books', { title: 'Books', books: objs }); });
这段代码里面有查询和新建保存,mongoose.model("Book") 相当于EF中的db.Books。而这里的find()方法并没有立即执行,这里有点像Linq的感觉,可以链式查询:
Book.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec(callback);//例子,和上面的book没有关系
这里Book相当于mongoose.model("Book") ,而{ size: 'small' }是一个条件。最终到exec方法时才正在执行。回调函数默认有两个参数,一个是err,一个是结果对象。这里偷了懒只判断了books的查询结果,集合为空的话就加上原来的数据。因为mongoose.model("Book")使用的比较多,所以干脆提出来:
var Bookmodel = mongoose.model('Book');var Topicmodel = mongoose.model('Topic');
find方法是查询一个集合,常用的还有findone:
var query = Person.findOne({ 'name.last': 'Ghost' });// 选择name和occupation 字段query.select('name occupation');// 执行查询query.exec(function (err, person) { if (err) return handleError(err); console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation) })
保存数据,new一个Model,传入初始化的对象,save即可。
var taget = new Bookmodel(books[i]); taget.save(function (err) { console.log(err); });
配置下路由:
router.get('/init', homeController.init);
运行:
因此数据添加成功!
三、app_api
为方便后面复用,接下来将数据调用换成api的方式。在工程里面新建一个app_api文件夹,并将原来app_server目录下的models文件移过去。并增加controllers和routes文件夹。也就是说,把数据调用分离出来。
1)在app.js中增加以下代码:
var routesApi = require('./app_api/routes/index');... app.use('/', routes);app.use('/api', routesApi);
也就是说,所有以/api/xx开始的请求将由routes/index.js来路由。
2)在routes文件下新增index.js 文件,用来配置api的路由:
var express = require('express');var router = express.Router();var bookCtrl = require('../controllers/book');var topicCtrl = require('../controllers/topic'); router.get('/books', bookCtrl.books); router.post('/book', bookCtrl.bookCreate); router.get('/book/:bookid', bookCtrl.bookReadOne); router.put('/books/:bookid', bookCtrl.bookUpdateOne); router.delete('/books/:bookid', bookCtrl.bookDeleteOne);//topicsrouter.get('/topics', topicCtrl.topics); module.exports = router;
路由覆盖了增删改查,暂时先实现这部分的路由。后面根据需求再增加。
3)controller实现
一个成功的Restful API应该包含返回的数据和http的状态码,因此我们定义了一个公用方法:
var sendJSONresponse = function (res, status, content) { res.status(status); res.json(content); };
比如定义一个bookReadOne方法:
module.exports.bookReadOne = function (req, res) { var bookid = req.params.bookid; if (!bookid) { sendJSONresponse(res, 404, { "message": "Not found, bookid is required" }); return; } BookModel.findById(bookid).exec(function (err, book) { if (!book) { sendJSONresponse(res, 404, { "message": "bookid not found" }); return; } else if (err) { sendJSONresponse(res, 400, err); return; } console.log(book); sendJSONresponse(res, 200, book); }); }
请求成功发送状态码200和结果,请求失败则发送404或者500及错误。
books.js:
var mongoose = require('mongoose');var BookModel = mongoose.model('Book');var sendJSONresponse = function (res, status, content) { res.status(status); res.json(content); }; module.exports.books = function (req, res) { BookModel.find().exec(function (err, books) { if (err) { console.log(err); sendJSONresponse(res, 400, err); return; } sendJSONresponse(res, 200, books); }); } module.exports.bookCreate = function (req, res) { BookModel.create({ title: req.body.title, info: req.body.info, img: req.body.img, tags: req.body.tags, brief: req.body.brief, ISBN: req.body.ISBN }, function(err, book) { if (err) { console.log(err); sendJSONresponse(res, 400, err); } else { console.log(book); sendJSONresponse(res, 201, book); } }); } module.exports.bookReadOne = function (req, res) { var bookid = req.params.bookid; if (!bookid) { sendJSONresponse(res, 404, { "message": "Not found, bookid is required" }); return; } BookModel.findById(bookid).exec(function (err, book) { if (!book) { sendJSONresponse(res, 404, { "message": "bookid not found" }); return; } else if (err) { sendJSONresponse(res, 400, err); return; } console.log(book); sendJSONresponse(res, 200, book); }); } module.exports.bookUpdateOne = function (req, res) { var bookid = req.params.bookid; if (!bookid) { sendJSONresponse(res, 404, { "message": "Not found, bookid is required" }); return; } BookModel.findById(bookid).exec(function (err, book) { if (!book) { sendJSONresponse(res, 404, { "message": "bookid not found" }); return; } else if (err) { sendJSONresponse(res, 400, err); return; } book.title = req.body.title; book.rating = req.body.rating; book.info = req.body.info; book.img = req.body.img; book.tags = req.body.tags; book.brief = req.body.brief; book.ISBN = req.body.ISBN; book.save(function (err, book) { if (err) { sendJSONresponse(res, 404, err); } else { sendJSONresponse(res, 200, book); } }); }); } module.exports.bookDeleteOne = function (req, res) { var bookid = req.params.bookid; if (bookid) { BookModel.findByIdAndRemove(bookid) .exec(function (err) { if (err) { console.log(err); sendJSONresponse(res, 404, err); return; } console.log("book id :" + bookid + "deleted"); sendJSONresponse(res, 204, null); }); } else { sendJSONresponse(res, 404, { message: "No bookid" }); } }
View Code
这样注意一点是,find()、findbyid和findByIdAndRemove不是立即执行,是在exec方法中执行,但是create,save是立即执行的。
5)、调用api
接下来就是在app_server下的controller中调用上面定义的api。在这里需要安装一个request模块,request让http请求变的更加简单。
在home.js中引用request:
var request = require('request');
request的调用如下:
request(options, callback)
包含options和回调两部分,options结构如下,包含url,method,json和qs四部分。
var requestOptions = { url : "http://yourapi.com/api/path", method : "GET", json : {}, //请求体的JavaScript对象qs : { offset : 20 //查询字符串} };
而默认的回调格式如下:
function(err, response, body) {if (err) { console.log(err); } else if (response.statusCode === 200) { console.log(body); } else { console.log(response.statusCode); } }
修改index方法,先定义一个apiOptions,只想默认路径,记得在发布的时候修改为带域名的地址。
var apiOptions = { server : "http://localhost:3000"}; module.exports.index = function (req, res) { var requestOptions, path; path = "/api/topics"; requestOptions= { url: apiOptions.server + path, method: "GET", json:{}, } request(requestOptions, function (err, response, body) { if (response.statusCode == 200) { res.render('index', { title: 'Index', topics: body }); } else { res.render('error', { message: err.message, error: err }); } }); };
这样就进行了分离,home.js不在需要引用Mongoose部分。
home.js:
var request = require('request');var apiOptions = { server : "http://localhost:3000"};//if (process.env.NODE_ENV === 'production') {// apiOptions.server = "https://stoneniqiu-mean.herokuapp.com/ ";//}module.exports.index = function (req, res) { var requestOptions, path; path = "/api/topics"; requestOptions= { url: apiOptions.server + path, method: "GET", json:{}, } request(requestOptions, function (err, response, body) { if (response.statusCode == 200) { res.render('index', { title: 'Index', topics: body }); } else { res.render('error', { message: err.message, error: err }); } }); }; module.exports.books = function (req, res) { var requestOptions, path; path = "/api/books"; requestOptions = { url: apiOptions.server + path, method: "GET", json: {}, } request(requestOptions, function (err, response, body) { if (response.statusCode == 200) { res.render('books', { title: 'Books', books: body }); } else { res.render('error', { message: err.message, error: err }); } }); }; module.exports.detail = function (req, res) { var requestOptions, path; path = "/api/book/" + req.params.id; requestOptions = { url: apiOptions.server+path , method: "GET", json: {}, } request(requestOptions, function (err, response, body) { if (response.statusCode == 200) { res.render('detail', { title: body.title, book: body }); } else { res.render('info', err); } }); }; module.exports.about = function (req, res) { res.render('about', { title: 'About' }); };
View Code
更多查询可以移步:http://mongoosejs.com/docs/queries.html
在首页,需要处理一下时间显示的问题,增加了一个mixin
mixin formatDate(dateString) -var date = new Date(dateString); -var d = date.getDate(); -var monthNames = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" ]; -var m = monthNames[date.getMonth()]; -var y = date.getFullYear(); -var output = y + '/' + m + '/' + d; =output
调用:
span.pull-right +formatDate(topic.createdOn)
页面如下:
初步改造完成。之前还写了一个基于回调的service方式的controller和service,类似于C#中的分离。后来觉得还是api的方式更好,留在这里可以当一个参考。
home.js:
var bookservice = require('../services/homeService.js'); module.exports.index = function (req, res) { bookservice.allTopics(function (result) { var obj = result.content; console.log(obj.status); if (result.status == 200) { res.render('index', { title: 'Index', topics: obj }); } res.render('info', obj); }); }; module.exports.books = function (req, res) { bookservice.allBooks(function(result) { var obj = result.content; console.log(obj.status); if (result.status == 200) { res.render('books', { title: 'Books', books: obj }); } res.render('info', obj); }); }; module.exports.detail = function (req, res) { bookservice.bookReadOne(req.params.id, function (result) { var obj = result.content; if (result.status == 200) { res.render('detail', { title: obj.title, book: obj }); } res.render('info', obj); }); }; module.exports.about = function (req, res) { res.render('about', { title: 'About' }); };
View Code
homeService.js:
var mongoose = require('mongoose');var db = require('../models/db.js');var Bookmodel = mongoose.model('Book');var Topicmodel = mongoose.model('Topic');var jsonResult = function (status, content) { return { status: status, content: content }; }; module.exports.bookReadOne = function (id,callback) { console.log('Finding book details', id); if (!id) { console.log('No bookid specified'); return jsonResult(404, { "message": "No bookid specified" }); } Bookmodel.findById(id).exec(function (err, book) { if (err) { callback(jsonResult(404, err)); return; } if (!book) { callback(jsonResult(404, { "message": "book not found" })); return; } callback(jsonResult(200, book)); }); }; module.exports.allBooks=function(callback) { Bookmodel.find().exec(function (err, books) { if (err) { callback(jsonResult(404, err)); return; } if (!books.length) { callback(jsonResult(404, { "message": "books not found" })); return; } callback(jsonResult(200, books)); }); } module.exports.createBook = function (book, callback) { var t = new Bookmodel(book); t.save(function (err) { callback(err); }); } module.exports.allTopics=function(callback) { Topicmodel.find().exec(function (err, topics) { if (err) { callback(jsonResult(404, err)); return; } if (!topics.length) { callback(jsonResult(404, { "message": "topics not found" })); return; } callback(jsonResult(200, topics)); }); } module.exports.createTopic=function(topic, callback) { var t = new Topicmodel(topic); t.save(function(err) { callback(err); }); }var books = [ { id: 0, title: "深入浅出Node.js", info: "朴灵 / 人民邮电出版社 / 2013-12-1 / CNY 69.00", rating: 5, img: "https://img3.doubanio.com/mpic/s27269296.jpg", tags: ["node", "深入浅出"], brief: '本书从不同的视角介绍了 Node 内在的特点和结构。由首章Node 介绍为索引,涉及Node 的各个方面,主要内容包含模块机制的揭示、异步I/O 实现原理的展现、异步编程的探讨、内存控制的介绍、二进制数据Buffer 的细节、Node 中的网络编程基础、Node 中的Web 开发、进程间的消息传递、Node 测试以及通过Node 构建产品需要的注意事项。最后的附录介绍了Node 的安装、调试、编码规范和NPM 仓库等事宜。本书适合想深入了解 Node 的人员阅读。' ,ISBN: 9787115335500 }, { id: 1, title: "程序员修炼之道 : 从小工到专家", info: "Andrew Hunt、David Thomas / 马维达 / 电子工业出版社 / 2005-1 / 48.00元", rating: 5, img: "https://img3.doubanio.com/mpic/s3957863.jpg", tags: ["程序人生", "软件开发"], brief: '《程序员修炼之道》由一系列的独立的部分组成,涵盖的主题从个人责任、职业发展,直到用于使代码保持灵活、并且易于改编和复用的各种架构技术。利用许多富有娱乐性的奇闻轶事、有思想性的例子以及有趣的类比,全面阐释了软件开发的许多不同方面的最佳实践和重大陷阱。无论你是初学者,是有经验的程序员,还是软件项目经理,本书都适合你阅读。' ,ISBN: 9787505397194 }, { id: 2, title: "Getting MEAN with Mongo, Express, Angular, and Node", info: "Simon Holmes / Manning Publications / 2015-11-26 / USD 44.99", rating: 4, img: "https://img3.doubanio.com/mpic/s27676844.jpg", tags: ["node", "web开发", "编程"], brief: 'MEAN栈开发,比较详尽的的应用开发书籍' , ISBN: 9781617292033 } ];var topics = [ { title: "书山有路第十一期:程序员修炼之道-第二章-注重实效的途径--第五天", type: "读书", visitedCount: 80, commentCount: 2, createdOn: '2016/5/15 21:32', author: 'stoneniqiu', img: 'http://upload.jianshu.io/users/upload_avatars/133630/d5370e672fd4.png?imageMogr/thumbnail/90x90/quality/100' }, { title: "《明朝那些事儿》之闲言散语", type: "书评", visitedCount: 180, commentCount: 20, createdOn: '2016/5/15 21:32', author: '卡卡卡萨布兰卡 ', img: 'http://upload.jianshu.io/users/upload_avatars/1675188/2d0810ccc03d.jpg?imageMogr/thumbnail/90x90/quality/100' }, { title: "有《程序员修炼之道》高清版吗?", type: "求书", visitedCount: 90, commentCount: 1, createdOn: '2016/5/15 21:32', author: '吾不知 ', img: 'http://upload.jianshu.io/users/upload_avatars/1125491/3910f3825f73.jpg?imageMogr/thumbnail/90x90/quality/100', }, { title: "《国富论》-读书笔记", type: "书评", visitedCount: 180, commentCount: 20, createdOn: '2016/5/15 21:32', author: '寻海 ' ,img: 'http://upload.jianshu.io/users/upload_avatars/133630/d5370e672fd4.png?imageMogr/thumbnail/90x90/quality/100' }, { title: "《高效人士的七个习惯》读书笔记", type: "书评", visitedCount: 180, commentCount: 20, createdOn: '2016/5/15 21:32', author: '书虫纪庆 ', img: 'http://upload.jianshu.io/users/upload_avatars/1429280/454c495361f9.jpg?imageMogr/thumbnail/90x90/quality/100' }, { title: "《css揭秘》这本书如何", type: "求索", visitedCount: 58, commentCount: 3, createdOn: '2016/5/15 21:32', author: 'Watery_D_Lotus ', img: 'http://upload.jianshu.io/users/upload_avatars/1449533/a2d98762484a.jpg?imageMogr/thumbnail/90x90/quality/100' } ];//module.exports.bookCreate = function (req, res) {// var book = {// title: "test",// info: "Simon Holmes / Manning Publications / 2015-11-26 / USD 44.99",// rating: 4,// img: "https://img3.doubanio.com/mpic/s27676844.jpg",// tags: ["node", "web开发", "编程"],// brief: 'MEAN栈开发,比较详尽的的应用开发书籍',// ISBN: 9781617292033// };// var b = new Bookmodel(book);// b.save(function (err) {// if (err) {// console.log(err);// res.render('error', {// message: err.message,// error: err// });// }// res.render('info', { message: "添加成功!", success: true });// });//};//module.exports.bookDelete = function (req, res) {// Bookmodel.findOneAndRemove({ title: "test" }, function (err) {// if (err) {// console.log(err);// res.render('error', {// message: err.message,// error: err// });// }// res.render('info', { message: "删除成功!", success: true });// });//};
View Code
至此,这三个页面的数据全部都是通过api从数据库中获取。那么现在我们要发布,本地数据库显然是不行。那怎么办呢?
四、mlab,500M MongoDB免费空间!
mlab是一个提供部署在云端的mongodb数据库服务平台,可以对每个用户免费提供500M空间。
1. 先注册一个账号:https://mlab.com/,并验证邮箱:
2.点击Create new创建数据库:这里选择亚马逊的单用户sanbox会有免费的500M空间。
输入数据库名称,并创建
3.创建成功之后,给数据库创建一个用户
4.连接字符串:
第一个字符串是用cmd连接,第二个用于我们的程序。修改db.js 连接成功:
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!