美文网首页Node.js
12_Node.js Web 开发

12_Node.js Web 开发

作者: e20a12f8855d | 来源:发表于2019-01-30 17:16 被阅读2次

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

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

    一、快速开始

    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 由于要接受表单信息,因此使用 app.post 注册路由,/login
    和 /reg 还要显示用户注册时要填写的表单,所以要以 app.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.js');
    var Db = require('mongodb').Db;
    var Connection = require('mongodb').Connection;
    var Server = require('mongodb').Server;
    module.exports = new Db(settings.db, new Server(settings.host, Connection.DEFAULT_ PORT, {}));
    

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

    2、会话支持

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

    为了在无状态的 HTTP 协议之上实现会话,Cookie 诞生了。Cookie 是一些存储在客户端的信息,每次连接的时候由浏览器向服务器递交,服务器也向浏览器发起存储 Cookie 的请求,依靠这样的手段服务器可以识别客户端。我们通常意义上的 HTTP 会话功能就是这样实现的。具体来说,浏览器首次向服务器发起请求时,服务器生成一个唯一标识符并发送给客户端浏览器,浏览器将这个唯一标识符存储在 Cookie 中,以后每次再发起请求,客户端浏览器都会向服务器传送这个唯一标识符,服务器通过这个唯一标识符来识别用户。对于开发者来说,我们无须关心浏览器端的存储,需要关注的仅仅是如何通过这个唯一标识符来识别用户。很多服务端脚本语言都有会话功能,如 PHP,把每个唯一标识符存储到文件中。

    Express 也提供了会话中间件,默认情况下是把用户信息存储在内存中,但我们既然已经有了 MongoDB,不妨把会话信息存储在数据库中,便于持久维护。为了使用这一功能,我们首先要通过 npm 安装一个 connect-mongo 的模块:

    然后打开 app.js,添加以下内容:

    app.js

    var MongoStore = require('connect-mongo');
    var settings = require('settings');
    app.configure(function() {
        app.set('views', __dirname + '/views');
        app.set('view engine', 'ejs');
        app.use(express.bodyParser());
        app.use(express.methodOverride());
        app.use(express.cookieParser());
        app.use(express.session({
            secret: settings.cookieSecret,
            store: new MongoStore({
                db: settings.db
            })
        }));
        app.use(app.router);
        app.use(express.static(__dirname + '/public'));
    });
    

    其中 express.cookieParser() 是 Cookie 解析的中间件。express.session() 则提供会话支持,设置它的 store 参数为 MongoStore 实例,把会话信息存储到数据库中,以避免丢失。

    在后面的小节中,我们可以通过 req.session 获取当前用户的会话对象,以维护用
    户相关的信息。

    未完待续......


    期待您的关注!

    相关文章

      网友评论

        本文标题:12_Node.js Web 开发

        本文链接:https://www.haomeiwen.com/subject/xcpcsqtx.html