12_Node.js Web 开发_博客网站

2,307 阅读20分钟

下面开始用 Node.js 进行 Web 开发。

我是通过《Node.js开发指南》这本书来学习 Node.js Web 开发的,书中使用的 Express 框架是 2.5.8,而我的是 4.14.1,所以遇到了许多问题,在文章中我都有提到并讲解。

GitHub 地址

一、快速开始

1、建立项目

《Node.js开发指南》中建立项目的方式是:express -t ejs microblog,但是这种方式对于高版本的 Express 新建的标签替换引擎并不是 .ejs,而是 .jade,如果要使用 .ejs 我们可以通过下面命令建立网站基本结构。

express -e NodeJSBlog

执行命令后在当前目录下出现了一些文件,并且下边提示我们通过 npm install 安装依赖。

在 npm install 之后,打开 NodeJSBlog 目录下的 package.json,可以看到已安装的包及对应的版本号。

2、启动服务器

注意,我们之前开启 Node.js 服务器,都是执行 node xxx.js,然后去浏览器访问即可,但是 Express 4.x 以上就不是这种方式了,应该是 npm start,端口配置在 bin/www 中。

启动成功访问 localhost:3000/。

3、项目结构

我们看一下 express 在 NodeJSBlog 这个目录下都生成了哪些文件。

app.js
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var index = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 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')));

app.use('/', index);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

app.js 是项目的入口,首先引入了一系列我们所需要的模块,然后引入了 routes 目录下的两个本地模块,它的功能是为指定路径组织返回内容,相当于 MVC 架构中的控制器。

接下来是视图引擎设置, app.set() 是 Express 的参数设置工具,接受一个键(key)和一个值(value),可用的参数如下所示:

  • basepath:基础地址,通常用于 res.redirect() 跳转。
  • views:视图文件的目录,存放模板文件。
  • view engine:视图模板引擎。
  • view options:全局视图参数对象。
  • view cache:启用视图缓存。
  • case sensitive routes:路径区分大小写。
  • strict routing:严格路径,启用后不会忽略路径末尾的“ / ”。
  • jsonp callback:开启透明的 JSONP 支持

Express 依赖于 connect,提供了大量的中间件,可以通过 app.use() 启用

routes/index.js
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

routes/index.js 是路由文件,相当于控制器,用于组织展示的内容,app.js 中通过 app.get('/', routes.index); 将“ / ”路径映射到 exports.index 函数下,其中只有一个语句 res.render('index', { title: 'Express' }),功能是调用模板解析引擎,翻译名为 index 的模板,并传入一个对象作为参数,这个对象只有一个属性,即 title: 'Express'。

views/index.ejs
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
</html>

index.ejs 是模板文件,即 routes/index.js 中调用的模板,内容是:

<h1><%= title %></h1>
<p>Welcome to <%= title %></p>

它的基础是 HTML 语言,其中包含了形如 <%= title %> 的标签,功能是显示引用的变量,即 res.render 函数第二个参数传入的对象的属性。

补充 include

在书中 views 目录下是有 layout.ejs 的,它可以让所有模板去继承它,<%- body %> 中是独特的内容,其他部分是共有的,可以看作是页面框架。

书中 layout.ejs:

<head>
    <title>
        <%= title %>
    </title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
</head>

<body>
    <%- body %>
</body>

</html>

但是 Express 4.x 就没有 layout.ejs 了,解决方法:

官方推荐了 include 方式,它不仅能实现 layout 的功能,还是将 view 的那些可复用的 html 片段提取成模块,在需要使用的地方直接用 <% include xxx %>。

例如先在 views 目录下新建一个 public_file.ejs ,在里面添加需要引用的公共文件:

<link rel='stylesheet' href='/stylesheets/style.css' />

然后修改一下 index.ejs,使用 <% include listitem %> 方式引用上边公共文件:

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <% include public_file %>
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
</html>

重启服务,访问 localhost:3000/,可以看到 style.css 文件正常引用。

每一次修改我们都需要重启服务才能看到修改后的结果,您可以看我的《Node.js 应用程序自动重启》这篇文章去安装 supervisor 或 nodemon 两个插件来实现应用程序自动重启。

二、路由控制

1、创建页面路由

简单说一下新增一个页面的流程。

首先在 views 目录下新建一个模板,例如 hello.ejs:

然后打开 index.js 文件,添加页面路由信息:

访问 localhost:3000/hello。

补充:在《Node.js开发指南》这本书中,还需要向 app.js 文件中添加页面路由信息,但在 Express 4.x 中是不需要的。

2、路径匹配

上面的例子是为固定的路径设置路由规则,Express 还支持更高级的路径匹配模式,例如我们想要展示一个用户的个人页面,路径为 /user/[username],可以用下面的方法定义路由 规则:

router.get('/user/:username', function(req, res, next) {
    res.send('user: ' + req.params.username);
});

重启项目,访问 localhost:3000/user/LiuZhenghe。

注意:调用模板解析引擎,用 res.render(),只是向页面发送数据,用 res.send()。

路径规则 /user/:username 会被自动编译为正则表达式,类似于 /user/([^/]+)/? 这样的形式,路径参数可以在响应函数中通过 req.params 的属性访问。

路径规则同样支持 JavaScript 正则表达式,例如 app.get(/user/([^/]+)/?,callback),这样的好处在于可以定义更加复杂的路径规则,而不同之处是匹配的参数是匿名的,因此需要通过 req.params[0]、req.params[1] 这样的形式访问。

3、REST 风格的路由规则

Express 支持 REST 风格的请求方式,在介绍之前我们先说明一下什么是 REST。

REST 的意思是表征状态转移(Representational State Transfer),它是一种基于 HTTP 协议的网络应用的接口风格,充分利用 HTTP 的方法实现统一风格接口的服务。

HTTP 协议定义了以下 8 种标准的方法:

  • GET:请求获取指定资源。
  • HEAD:请求指定资源的响应头。
  • POST:向指定资源提交数据。
  • PUT:请求服务器存储一个资源。
  • DELETE:请求服务器删除指定资源。
  • TRACE:回显服务器收到的请求,主要用于测试或诊断。
  • CONNECT:HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
  • OPTIONS:返回服务器支持的HTTP请求方法。

其中我们经常用到的是 GET、POST、PUT 和 DELETE 方法,根据 REST 设计模式,这4种方法通常分别用于实现以下功能。

  • GET:获取
  • POST:新增
  • PUT:更新
  • DELETE:删除

这是因为这 4 种方法有不同的特点,按照定义,它们的特点如下表所示:

请求方式 安全 幂等
GET
POST
PUT
DELETE

所谓安全是指没有副作用,即请求不会对资源产生变动,连续访问多次所获得的结果不受访问者的影响,而幂等指的是重复请求多次与一次请求的效果是一样的,比如获取和更新操作是幂等的,这与新增不同,删除也是幂等的,即重复删除一个资源,和删除一次是一样的。

Express 对每种 HTTP 请求方法都设计了不同的路由绑定函数,例如前面例子全部是 app.get,表示为该路径绑定了 GET 请求,向这个路径发起其他方式的请求不会被响应。

下表是 Express 支持的所有 HTTP 请求的绑定函数。

请求方式 绑定函数
GET app.get(path, callback)
POST app.post(path, callback)
PUT app.put(path, callback)
DELETE app.delete(path, callback)
PATCH app.patch(path, callback)
TRACE app.trace(path, callback)
CONNECT app.connect(path, callback)
OPTIONS app.options(path, callback)
所有方法 app.all(path, callback)

例如我们要绑定某个路径的 POST 请求,则可以用 app.post(path, callback) 的 方法,需要注意的是 app.all 函数,它支持把所有的请求方式绑定到同一个响应函数,是一个非常灵活的函数,在后面我们可以看到许多功能都可以通过它来实现。

4、控制权转移

Express 支持同一路径绑定多个路由响应函数,例如:

index.js

// ...
/* 路径匹配模式 */
router.all('/user/:username', function(req, res, next) {
    res.send('all methods captured');
});

router.get('/user/:username', function(req, res, next) {
    res.send('user: ' + req.params.username);
});
// ...

当再次访问 localhost:3000/user/LiuZhenghe 时,发现页面被第一条路由规则捕获。

原因是 Express 在处理路由规则时,会优先匹配先定义的路由规则,因此后面相同的规则被屏蔽。

Express 提供了路由控制权转移的方法,即回调函数的第三个参数 next,通过调用 next(),会将路由控制权转移给后面的规则,例如:

// ...
/* 路径匹配模式 */
router.all('/user/:username', function(req, res, next) {
    console.log('all methods captured');
    next();
});

router.get('/user/:username', function(req, res, next) {
    res.send('user: ' + req.params.username);
});
// ...

此时刷新页面,在控制台可以看到“all methods captured”,浏览器显示了 user: LiuZhenghe。

这是一个非常有用的工具,可以让我们轻易地实现中间件,而且还能提高代码的复用程度,例如我们针对一个用户查询信息和修改信息的操作,分别对应了 GET 和 PUT 操作,而两者共有的一个步骤是检查用户名是否合法,因此可以通过 next() 方法实现。

三、模板引擎

模板引擎也就是视图,视图决定了用户最终能看到什么,这里我们用 ejs 为例介绍模板引擎的使用方法。

1、使用模板引擎

在 app.js 中,以下两句设置了模板引擎和页面模板的位置:

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

在 index.js 中通过 res.render() 调用模板,res.render() 的功能是调用模板引擎,并将其产生的页面直接返回给客户端,它接受两个参数,第一个是模板的名称,即 views 目录下的模板文件名,不包含文件的扩展名;第二个参数是传递给模板的数据,用于模板翻译。

ejs 的标签系统非常简单,它只有以下 3 种标签:

  • <% code %>:JavaScript 代码。
  • <%= code %>:显示替换过 HTML 特殊字符的内容。
  • <%- code %>:显示原始 HTML 内容。

我们可以用它们实现页面模板系统能实现的任何内容。

2、片段视图

《Node.js开发指南》中所讲的片段视图(partials)在 Express 4.x 中已经不支持了,在上面项目结构分析那一节中我曾补充过 include,这里再次介绍一下它的用法。

官方推荐了 include 方式,它不仅能实现 layout 的功能,还是将 view 的那些可复用的 html 片段提取成模块,在需要使用的地方直接用 <% include xxx %>,看下面这个例子:

首先在 index.js 中新增以下内容:

// 片断视图
router.get('/list', function(reg, res) {
    res.render('list', {
        title: "List",
        items: [2019, 'Node.js', 'NodeJSBlog', 'Express']
    });
});

然后新建 list.ejs 文件并添加以下内容:

<ul>
<% items.forEach(function(listitem){ %>
<% include listitem %>
<% }) %>
</ul>

同时新建 listitem.ejs 文件并添加:

<li><%= listitem %></li>

访问 localhost:3000/list,可以看到以下内容:

四、开始建立博客网站

1、功能分析

博客网站首先应该有登录注册功能,然后是最核心的功能——信息发表,这个功能涉及到许多方面,包括数据库访问,前端显示等。

一个完整的博客系统,应该有评论,收藏,转发等功能,处于本人目前的能力水平还不能都实现,先做一个博客网站的雏形吧。

2、路由规划

根据功能设计,我们把路由按照以下方案规划:

  • /:首页
  • /u/[user]:用户的主页
  • /post:发表信息
  • /reg:用户注册
  • /login:用户登录
  • /logout:用户登出

以上页面还可以根据用户状态细分,发表信息以及用户登出页面必须是已登录用户才能操作的功能,而用户注册和用户登入所面向的对象必须是未登入的用户,首页和用户主页则针对已登入和未登入的用户显示不同的内容。

在 index.js 中添加以下内容:

router.get('/', function(req, res) {
    res.render('index', {
        title: 'Express'
    });
});
router.get('/u/:user', function(req, res) {});
router.post('/post', function(req, res) {});
router.get('/reg', function(req, res) {});
router.post('/reg', function(req, res) {});
router.get('/login', function(req, res) {});
router.post('/login', function(req, res) {});
router.get('/logout', function(req, res) {});

其中 /post、/login 和 /reg 由于要接受表单信息,因此使用 router.post 注册路由,/login 和 /reg 还要显示用户注册时要填写的表单,所以要以 router.get 注册。

3、使用 Bootstrap

下载 jquery.js,bootstrap.css 和 bootstrap.js,放到 public 下对应的目录中。

在 public_file.ejs 中引用,可以使用 include 方法给模板添加公共文件。

Bootstrap官网 查看并使用所需的模板或组件。

下图是我从 Bootstrap 官网找的一个模板,并放到了 index.ejs 目录下并进行了简单地修改,并将页面公共部分(头尾部分)取出,用 include 方式来复用。

五、用户注册和登录

1、访问数据库

我们选用 MongoDB 作为网站的数据库系统,它是一个开源的 NoSQL 数据库,相比 MySQL 那样的关系型数据库,它更为轻巧、灵活,非常适合在数据规模很大、事务性不强的场合下使用。

连接数据库

通过 npm 安装 mongodb。

npm install mongodb --save

补充:通过 --save 安装,包名和版本号将会出现在 package.json 中。

接下来在项目主目录中创建 settings.js 文件,这个文件用于保存数据库的连接信息,我们将用到的数据库命名为 NodeJSBlog,数据库服务器在本地,因此 settings.js 文件的内容如下:

settings.js

module.exports = {
    cookieSecret: 'NodeJSBlogbyvoid',
    db: 'NodeJSBlog',
    host: 'localhost',
};

其中,db 是数据库的名称,host 是数据库的地址,cookieSecret 用于 Cookie 加密与数据库无关,我们留作后用。

接下来新建 models 目录,并在目录中创建 db.js:

models/db.js

var settings = require('../settings'),
    Db = require('mongodb').Db,
    Connection = require('mongodb').Connection,
    Server = require('mongodb').Server;
module.exports = new Db(settings.db, new Server(settings.host, 27017, {}), {
    safe: true
});

以上代码通过 module.exports 输出了创建的数据库连接,在后面的小节中我们会用到这个模块,由于模块只会被加载一次,以后我们在其他文件中使用时均为这一个实例。

2、会话支持

《Node.js开发指南》中对会话支持是这样描述的:

会话是一种持久的网络协议,用于完成服务器和客户端之间的一些交互行为。会话是一个比连接粒度更大的概念,一次会话可能包含多次连接,每次连接都被认为是会话的一次操作。在网络应用开发中,有必要实现会话以帮助用户交互。例如网上购物的场景,用户浏览了多个页面,购买了一些物品,这些请求在多次连接中完成。许多应用层网络协议都是由会话支持的,如 FTP、Telnet 等,而 HTTP 协议是无状态的,本身不支持会话,因此在没有额外手段的帮助下,前面场景中服务器不知道用户购买了什么。

为了在无状态的 HTTP 协议之上实现会话,Cookie 诞生了。Cookie 是一些存储在客户端的信息,每次连接的时候由浏览器向服务器递交,服务器也向浏览器发起存储 Cookie 的请求,依靠这样的手段服务器可以识别客户端。我们通常意义上的 HTTP 会话功能就是这样实现的。具体来说,浏览器首次向服务器发起请求时,服务器生成一个唯一标识符并发送给客户端浏览器,浏览器将这个唯一标识符存储在 Cookie 中,以后每次再发起请求,客户端浏览器都会向服务器传送这个唯一标识符,服务器通过这个唯一标识符来识别用户。

对于开发者来说,我们无须关心浏览器端的存储,需要关注的仅仅是如何通过这个唯一标识符来识别用户。很多服务端脚本语言都有会话功能,如 PHP,把每个唯一标识符存储到文件中。Express 也提供了会话中间件,默认情况下是把用户信息存储在内存中,但我们既然已经有了 MongoDB,不妨把会话信息存储在数据库中,便于持久维护。

但是如果你的 Express 版本是 4.x,按照书中接下来的内容编写代码,就会出各种问题,下面我来说一下 Express 4.x 中的会话支持。

在 Express 4.x 中我们需要自己安装 express-session 包,然后添加引用:

var session = require('express-session');

然后再引用 connect-mongo 包:

var MongoStore = require('connect-mongo')(session);

最终在 app.js 中新增的内容就是:

var settings = require('./settings');
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
app.use(session({
    secret: settings.cookieSecret,
    store: new MongoStore({
        db: settings.db,
    })
}));

但是此时程序会报错:'Connection strategy not found':

从网上查找到该问题之后找到了解决办法,打开 package.json 文件,将 connect-mongo 的版本改为 0.8.2,然后执行 npm install 即可解决。

如果程序没有报错,那么就成功了,你可以使用 mongo.exe 或可视化工具来查看数据库是否新建成功。

3、注册和登录

3.1、注册

通过可视化工具或 mongodb 终端新建一个用户表 users。

注册页面:

首先添加注册页面的模板 views/reg.ejs,并添加表单结构:

<div class="container">
    <div class="bs-example" data-example-id="basic-forms">
        <form>
            <div class="form-group">
                <label for="username">用户名</label>
                <input type="text" class="form-control" id="username" placeholder="请输入用户名">
            </div>
            <div class="form-group">
                <label for="password">密码</label>
                <input type="password" class="form-control" id="password" placeholder="请输入密码">
            </div>
            <div class="form-group">
                <label for="password_repeat">再次输入密码</label>
                <input type="password" class="form-control" id="password_repeat" placeholder="再次输入密码">
            </div>
            <button type="submit" class="btn btn-default">注册</button>
        </form>
    </div>
</div>

然后打开 index.js 添加注册页面的路由信息:

index.js

router.get('/reg', function(req, res) {
    res.render('reg', {
        title: '用户注册'
    });
});

然后访问 localhost:3000/reg。

注册响应:

在书中使用了 flash,但是最新版本的 Express 已经不支持 flash 了,你需要先通过 npm 安装 connect-flash。

然后在 app.js 中添加如下代码:

var flash = require('connect-flash');

在 routes/index.js 中添加 /reg 的 POST 响应函数:

routes/index.js

// 注册响应
var crypto = require('crypto');
var User = require('../models/user.js');
var MongoClient = require('mongodb').MongoClient;
const DB_CONN_STR='mongodb://localhost:27017/users';
router.post('/reg', function(req, res) {
    let newUser = {
        username: req.body.username,
        password: req.body.password,
        password_repeat: req.body.password_repeat
    };
    let addStr = [{
        username: newUser.username,
        password: newUser.password
    }];
    MongoClient.connect(DB_CONN_STR, function(err, db) {
        db.collection('users').findOne({
            username: newUser.username
        }, function(err, result) {
            if (!result) {
                if (newUser.password === newUser.password_repeat) {
                    MongoClient.connect(DB_CONN_STR, function(err, db) {
                        req.session.error = '注册成功,请登录!';
                        db.collection('users').insert(addStr);
                        db.close();
                        return res.redirect('/login');
                    });
                } else {
                    req.session.error = '两次密码不一致!';
                    return res.redirect('/register');
                }
            } else {
                req.session.error = '用户名已存在!';
                return res.redirect('/register');
            }
        })
        db.close();
    });
});

  • req.body 就是 POST 请求信息解析过后的对象,例如我们要访问用户传递的 password 域的值,只需访问 req.body['password'] 即可。
  • req.flash 是 Express 提供的一个奇妙的工具,通过它保存的变量只会在用户当前和下一次的请求中被访问,之后会被清除,通过它我们可以很方便地实现页面的通知和错误信息显示功能。
  • res.redirect 是重定向功能,通过它会向用户返回一个 303 See Other 状态,通知浏览器转向相应页面。
  • crypto 是 Node.js 的一个核心模块,功能是加密并生成各种散列,使用它之前首先要声明 var crypto = require('crypto'),我们代码中使用它计算了密码的散列值。
  • User 是我们设计的用户对象,在后面我们会详细介绍,这里先假设它的接口都是可用的,使用前需要通过 var User = require('../models/user.js') 引用。
  • User.get 的功能是通过用户名获取已知用户,在这里我们判断用户名是否已经存在,User.save 可以将用户对象的修改写入数据库。
  • 通过 req.session.user = newUser 向会话对象写入了当前用户的信息,在后面我们会通过它判断用户是否已经登录。

用户模型

在前面的代码中,我们直接使用了 User 对象,User 是一个描述数据的对象,即 MVC 架构中的模型,前面我们使用了许多视图和控制器,这是第一次接触到模型。与视图和控制器不同,模型是真正与数据打交道的工具,没有模型,网站就只是一个外壳,不能发挥真实的作用,因此它是框架中最根本的部分。现在就让我们来实现 User 模型吧。

在 models 目录中创建 user.js 的文件,内容如下:

models/user.js

var mongodb = require('./db');

function User(user) {
    this.name = user.name;
    this.password = user.password;
};
module.exports = User;
User.prototype.save = function save(callback) {
    // 存入 Mongodb 的文档
    var user = {
        name: this.name,
        password: this.password,
    };
    mongodb.open(function(err, db) {
        if (err) {
            return callback(err);
        }
        // 读取 users 集合
        db.collection('users', function(err, collection) {
            if (err) {
                mongodb.close();
                return callback(err);
            }
            // 为 name 属性添加索引
            collection.ensureIndex('name', {
                unique: true
            });
            // 写入 user 文档
            collection.insert(user, {
                safe: true
            }, function(err, user) {
                mongodb.close();
                callback(err, user);
            });
        });
    });
};

User.get = function get(username, callback) {
    mongodb.open(function(err, db) {
        if (err) {
            return callback(err);
        }
        // 读取 users 集合
        db.collection('users', function(err, collection) {
            if (err) {
                mongodb.close();
                return callback(err);
            }
            // 查找 name 属性为 username 的文档
            collection.findOne({
                name: username
            }, function(err, doc) {
                mongodb.close();
                if (doc) {
                    // 封装文档为 User 对象
                    var user = new User(doc);
                    callback(err, user);
                } else {
                    callback(err, null);
                }
            });
        });
    });
};

以上代码实现了两个接口,User.prototype.save 和 User.get,前者是对象实例的方法,用于将用户对象的数据保存到数据库中,后者是对象构造函数的方法,用于从数据库中查找指定的用户。

视图交互

现在几乎已经万事俱备,只差视图的支持了。为了实现不同登录状态下页面呈现不同内容的功能,我们需要创建动态视图助手,通过它我们才能在视图中访问会话中的用户数据,同时为了显示错误和成功的信息,也要在动态视图助手中增加响应的函数。

在书中,在 app.js 中添加的视图交互代码是:

app.dynamicHelpers({
    user: function(req, res) {
        return req.session.user;
    },
    error: function(req, res) {
        var err = req.flash('error');
        if (err.length)
            return err;
        else
            return null;
    },
    success: function(req, res) {
        var succ = req.flash('success');
        if (succ.length)
            return succ;
        else
            return null;
    },
});

但是在 Express 4.x 中会报错“app.dynamicHelpers is not a function ”,此处应该添加:

app.js

app.use(flash());
app.use(function(req, res, next) {
    res.locals.user = req.session.user;
    res.locals.post = req.session.post;
    var error = req.flash('error');
    res.locals.error = error.length ? error : null;
    var success = req.flash('success');
    res.locals.success = success.length ? success : null;
    next();
});

注意,这段代码不要放的太靠后,应该放到路由控制代码之前。

接下来修改公共导航部分。

header.ejs

<nav class="navbar navbar-inverse">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                <span class="sr-only"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">NodeJS Blog</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
            <form class="navbar-form navbar-right">
                <% if (!user) { %>
                <a href="/login" type="submit" class="btn btn-success">登录</a>
                <a href="/reg" type="submit" class="btn btn-success">注册</a>
                <% } else { %>
                <a href="" type="submit" class="btn">注销</a>
                <% } %>
            </form>
        </div>
    </div>
</nav>

然后打开注册页,输入用户名、密码,点击注册按钮,发现又遇到坑了...“db.collection is not a function”。

引起这个错误的原因是你通过 npm 安装的 mongodb 的版本和你 Node.js 操作数据的 api 版本不一致,查看了一下 package.json,发现 mongodb 的版本是 3.1.13。

解决方法,下载低版本 mongodb,例:

"mongodb": "^2.2.33",

npm install 安装。

未完待续......


期待您的关注!