这是 MDN 上面的 node框架express 的教程,实现一个本地图书馆管理项目。
课程采用了服务端渲染,渲染模板引擎是pug, 数据库是mongoDB, 实现了增删改查。
我先把教程整体看过一遍,大体理解了,不过为了熟悉一点,逼着自己一步一步跟着做出来了。
这是我做的项目仓库地址
node Version: v12.18.3 npm Version: 6.14.8 mongoDB Version: v4.4.1
数据库用的是本地的mongoDB,所以如果你要跑起来看看效果,要先安装mongoDB 并启动它。
这个教程的编写是2016年,我练习时间是2020年,以下记录我在跟着教程走时遇到的问题
-
课程在Express 教程 5: 呈现图书馆数据 > 使用 async 进行非同步流控制 处理异步事件的时候,用的是 async, 但是现在基本可以使用原生的promise + async 了, 方便后面继续跟着教程走,就不做修改了。
-
课程在Express 教程 6: 使用表单 使用 express-validator 的时候,用的引入方式被废弃了,不建议使用。
课程引入方式如下:
const { body,validationResult } = require('express-validator/check'); const { sanitizeBody } = require('express-validator/filter');
根据提示,现在建议使用的引入方式如下:
const { body,validationResult, sanitizeBody } = require("express-validator")
-
旧的版本 express-validator 不支持在验证的时候转义请求的body数据,但是现在可以了,不用再分开 body 和 sannitizeBody 两部分走了
-
在 Express 教程 6: 使用表单 > 创建书本表单 这一节中,代码并没有处理 “用户不选择书本种类的情况”,导致写入数据库报错。(更新书本表单也是)
原教程代码如下(/controllers/bookController.js):
exports.book_create_post = [ // Convert the genre to an array. (req, res, next) => { if(!(req.body.genre instanceof Array)){ if(typeof req.body.genre==='undefined') req.body.genre=[]; else req.body.genre=new Array(req.body.genre); } next(); }, // Validate fields. body('title', 'Title must not be empty.').isLength({ min: 1 }).trim(), body('author', 'Author must not be empty.').isLength({ min: 1 }).trim(), body('summary', 'Summary must not be empty.').isLength({ min: 1 }).trim(), body('isbn', 'ISBN must not be empty').isLength({ min: 1 }).trim(), // Sanitize fields (using wildcard). sanitizeBody('*').trim().escape(), sanitizeBody('genre.*').escape(), // Process request after validation and sanitization. (req, res, next) => { // Extract the validation errors from a request. const errors = validationResult(req); // Create a Book object with escaped and trimmed data. var book = new Book( { title: req.body.title, author: req.body.author, summary: req.body.summary, isbn: req.body.isbn, genre: req.body.genre }); if (!errors.isEmpty()) { // There are errors. Render form again with sanitized values/error messages. // Get all authors and genres for form. async.parallel({ authors: function(callback) { Author.find(callback); }, genres: function(callback) { Genre.find(callback); }, }, function(err, results) { if (err) { return next(err); } // Mark our selected genres as checked. for (let i = 0; i < results.genres.length; i++) { if (book.genre.indexOf(results.genres[i]._id) > -1) { results.genres[i].checked='true'; } } res.render('book_form', { title: 'Create Book',authors:results.authors, genres:results.genres, book: book, errors: errors.array() }); }); return; } else { // Data from form is valid. Save book. book.save(function (err) { if (err) { return next(err); } //successful - redirect to new book record. res.redirect(book.url); }); } } ];
添加了无选择 书本种类 情况的容错后代码如下(express-validador 自定义验证)
// Validate fields. body('title', 'Title must not be empty.').isLength({ min: 1 }).trim(), body('author', 'Author must not be empty.').isLength({ min: 1 }).trim(), body('summary', 'Summary must not be empty.').isLength({ min: 1 }).trim(), body('isbn', 'ISBN must not be empty').isLength({ min: 1 }).trim(), // 在这一部分添加多一个自定义验证 body('genre').custom(value => { if (!value.length) { return Promise.reject('Must select a genre') } return true }),