十一小长假,学习了一下比较流行的网站开发技术:NodeJS+Express+MongoDB,记录一下初入门感觉。
我安装的软件版本是:
Node : v6.3.1
Express: 4.13.4
MongoDB: 3.2.4
接下来,我们新建一个工程,在命令行中输入:
$ express -e blog
$ cd blog && npm install
初始化一个 express 项目并安装所需模块,如下图所示:
然后运行:
DEBUG=blog:* npm start
但一般,我个人习惯我们会把运行程序的命令写到一个shell里面,如下:
#!/usr/bin/env bash
DEBUG=blog:* npm start
启动项目,此时命令行中会显示 blog Express server listening on port 3000 +0ms ,在浏览器里访问 localhost:3000,会显示如下图所示结果:
至此,我们用 express 初始化了一个项目工程,并指定使用 ejs 作为模板引擎(命令行中的 -e 参数),下面具体讲解工程的内部结构。
我们打开刚刚创建的 blog 文件夹,里面如图所示:
接下来,具体解释每一个文件在工程中的作用:
app.js
: 启动文件,或者说入口文件。
package.json
: 存储着工程的信息及模块依赖,当在 dependencies 中添加依赖的模块时, 运行 npm install
,npm 会检查当前目录下的 package.json,并自动安装所有指定的模块。
node_modules
: 存放 package.json 中安装的模块,当你在 package.json 添加依赖的模块并安装后,存放在这个文件夹下。
public
: 存放 image、css、js 等文件。
routes
: 存放路由文件。
views
: 存放视图文件或者说模板文件。
bin
: 存放可执行文件。
现在,我们打开 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 routes = 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('/', routes);
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 handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
这里我们通过 require() 加载了 express、path 等模块,以及 routes 文件夹下的 index.js 和 user.js 路由文件。下面解释每一行代码的含义:
(1) var app = express()
生成一个 express 实例 app。
(2) app.set('view', path.join(dirname, 'views'))
设置 views 文件夹为存放视图文件的目录,即存放模板文件的地方,dirname 为全局变量,存储当前正在执行的脚本所在的目录。
(3) app.set('view engine', 'ejs')
设置视图模板引擎为 ejs 。
(4) app.use(favicon(dirname + '/public/favicon.ico'))
设置 /public/favicon.ico 为 favicon 图标。
(5) app.use(logger('dev'))
加载日志中间件
(6) app.use(bodyParser.json())
加载解析 json 的中间件。
(7) app.use(bodyParser.urlencoded({ extended: false }))
加载解析 urlencoded 请求体的中间件。
(8) app.use(cookieParser())
加载解析 cookie 的中间件。
(9) app.use(express.static(path.join(dirname, 'public')))
设置 public 文件夹为存放静态文件的目录。
(10) app.use('/', routes)和app.use('/users', users)
路由控制。
(11)
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
捕获404错误,并转发到错误处理器。
(12)
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
开发环境下的错误处理器,将错误信息渲染 error 模板并显示到浏览器中。
(13)
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
生产环境下的错误处理器,将错误信息渲染 error 模板并显示到浏览器中。
(14) module.exports = app
导出 app 实例供其他模板调用。
接下来我们再看看 bin/www 文件:
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('blog:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
接下来我们解释一下文件中的每一行代码:
(1) #!/usr/bin/env node
表明这是一个 node 可执行文件。
(2) var debug = require('debug')('blog:server')
引入 debug 模块,打印调试日志。
(3) var app = require('../app')
引入我们上面导出的 app 实例。
(4)
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
设置端口号。
(5) var server = http.createServer(app)
创建 HTTP 服务。
(6)
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
启动工程并监听 3000 端口。
接下来我们学习 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;
这个代码的意思是,先导入 express 模块,然后生成一个路由实例用来捕获访问主页的 GET 请求,导出这个路由并在 app.js 中通过 app.use('/', routes) 加载。这样,当访问主页时,就会调用 res.render('index', {title: 'Express'}) 渲染 views/index.ejs 模板并显示到浏览器中。
接下来,我们可以看看 views/index.ejs 文件:
<%= title %>
<%= title %>
Welcome to <%= title %>
在渲染模板时,我们传入了一个变量 title 值为 express 字符串,模板引擎将所有 <%= title %> 替换为 express,然后将渲染后生成的html显示到浏览器中,如上图网页显示。
至此,我们大致了解了如何创建一个工程并且一些基础,接下来我们学习 express 的基本使用及路由控制。
路由控制
工作原理
先来查看一下 routes/index.js 文件中的主要路由分配代码:
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
这段代码的意思是当访问主页时,调用 ejs 模板引擎,来渲染 index.ejs 模板文件(即将 title 变量全部替换为字符串 Express),生成静态页面并显示在浏览器中。
接下来,我们学习一下路由添加规则,当我们访问 localhost:3000 时,会显示:
但是,当我们访问 localhost:3000/asdf 这种不存在的页面时,就会显示:
为什么会显示这个呢?因为我们在路由规则中,不存在 /asdf 的路由规则,而且它也不是一个 public 目录下的文件,所有 express 返回了 404 Not Found 的错误。下面我们来添加这条路由规则,使得当访问 localhost:3000/asdf 时,页面显示 Hello World! 。
注意:以下路由修改仅用于测试,看到效果后把代码还原回来。
修改 index.js,在路由规则中添加一条路由规则:
router.get('/asdf', function(req, res, next) {
res.send('Hello World!');
});
重启之后,重新访问 localhost:3000/asdf 页面显示如下:
有没有感觉添加路由规则很简单啊,自己动手试试吧。
模板引擎
先来解释一下,什么是模板引擎:模板引擎(Template Engine)是一个将页面模板和要显示的数据结合起来生成HTML页面的工具。如果说上面讲到的 express 中的路由控制方法相当于 MVC 中的控制器的话,那模板引擎就相当于 MVC 中的视图。
模板引擎的功能是将页面模板和要显示的数据结合起来生成 HTML 页面。它既可以运 行在服务器端又可以运行在客户端,大多数时候它都在服务器端直接被解析为 HTML,解析完成后再传输给客户端,因此客户端甚至无法判断页面是否是模板引擎生成的。有时候模板引擎也可以运行在客户端,即浏览器中,典型的代表就是 XSLT,它以 XML 为输入,在客户端生成 HTML 页面。但是由于浏览器兼容性问题,XSLT 并不是很流行。目前的主流还是由服务器运行模板引擎。 在 MVC 架构中,模板引擎包含在服务器端。控制器得到用户请求后,从模型获取数据,调用模板引擎。模板引擎以数据和页面模板为输入,生成 HTML 页面,然后返回给控制器,由控制器交回客户端。
——《Node.js开发指南》
那么,什么是 ejs 呢?
ejs 是模板引擎的一种,也是我们这个教程中使用的模板引擎,因为它使用起来十分简单,而且与 express 集成良好。
使用模板引擎
前面我们通过以下两行代码设置了模板文件的存储位置和使用的模板引擎:
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
注意:我们通过 express -e blog 只是初始化了一个使用 ejs 模板引擎的工程而已,比如 node_modules 下添加了 ejs 模板,views 文件夹下有 index.ejs 。并不是说强制该工程只能使用 ejs 不能使用其他的模板引擎,比如 jade ,真正指定使用哪个模板引擎的是 app.set('view engine', 'ejs') 。
在 routes/index.js 中通过调用 res.render() 渲染模板,并将其产生的页面直接返回给客户端。它接受两个参数,第一个是模板的名称,即 views 目录下的模板文件名,扩展名 .ejs 可选。第二个参数是传递给模板的数据对象,用于模板翻译。
接下来,我们看看 views/index.ejs 中的代码:
<%= title %>
<%= title %>
Welcome to <%= title %>
当我们 res.render('index', {title: 'Express'}); 时,模板引擎会把 <%= title %> 替换成 Express,然后把替换成的页面显示给用户。
渲染后生成的页面代码为:
Express
Express
Welcome to Express
注意:我们通过 app.use(express.static(path.join(__dirname, 'public'))) 设置了静态文件目录为 public 文件夹,所以上面代码中的 href = '/stylesheets/style.css' 就相当于 href = 'public/stylesheets/style.css' 。
ejs 的标签系统非常简单,它只有以下三种标签:
- <% code %>: JavaScript 代码。
- <%= code %>: 显示替换过 HTML 特殊字符的内容。
- <%- code %>: 显示原始 HTML 内容。
注意:<%= code %> 和 <%- code %> 的区别,当变量 code 为普通字符串时,两者没有区别。当 code 比如为
这种字符串时,<%= code %> 会原样输出 hello
,而 <%- code %> 则会直接显示 H1 大小的 hello 字符串。 hello
我们还可以在 <% %> 内使用 JavaScript 代码。下面是 ejs 的官网实例:
The Data
supplies: ['mop', 'broom', 'duster']
The Template
<% for(var i=0; i
- <%= supplies[i] %>
<% } %>
The Result
- mop
- broom
- duster
我们可以用上述三种标签实现页面模板系统能实现的任何内容。
页面布局
这里我们不使用 layout 进行页面布局,而是使用更为简单灵活的 include 。include 的简单使用如下:
index.ejs
<%- include a %>
hello,world!
<%- include b %>
a.ejs
this is a.ejs
b.ejs
this is b.ejs
最终 index.ejs 会显示:
this is a.ejs
hello, world!
this is b.ejs
至此,我们学习完了模板引擎的相关知识。
接下来,我们利用Express实现一个简单的博客功能。
Reference:
Express入门教程:一个简单的博客