Express 框架
第一章 Express简介
npm提供了大量的第三方模块,其中不乏许多Web框架,我们没有必要重复发明轮子,因而选择使用 Express作为开发框架,因为它是目前最稳定、使用最广泛,而且Node.js官方推荐的唯一一个 Web开发框架。
Express是一个轻量级的Web框架,多数功能只是对HTTP协议中常用操作的封装,更多的功能需要插件或者整合其他模块来完成.
Express中文官网 http://www.expressjs.com.cn/
第二章 初始化一个Express应用
- 我们想要安装Express,首先需要安装Epxress生成器,在全局通过命令安装
npm install express-generator -g
- 等待安装完毕,查看帮助信息:
express --help
可以看到,
Usage: express [options] [dir]
我们可以用express 在当前目录创建dir,并且依据配置选项
我们可以通过option设置express使用的模板,这里我们用ejs,也可以设置css的引擎,比如less和sass,这里我们先使用原生的css
所以只需要输入
express myapp --view ejs
express会给我们快速的搭建一个app框架,然后根据提示进入myapp文件夹,运行
npm install
npm会自动帮助我们安装当前应用所需要的模块,等待安装完毕之后,启动:
npm start
浏览器打开:"localhost:3000",可以看到express框架给我返回的信息。
在调试过程中,如果我们不想反复的重启服务端,可以npm安装nodemon,使用nodemon启动express项目
第三章 目录分析
回到我们安装express之前,看一下express快速搭建的目录结构:
create : taobao
create : taobao/package.json //依赖关系文件
create : taobao/app.js // 入口文件
create : taobao/public //公共资源文件夹
create : taobao/public/images
create : taobao/public/stylesheets
create : taobao/public/stylesheets/style.css
create : taobao/public/javascripts
create : taobao/views //视图文件夹
create : taobao/views/index.ejs
create : taobao/views/error.ejs
create : taobao/routes //路由文件夹
create : taobao/routes/index.js
create : taobao/routes/users.js
create : taobao/bin
create : taobao/bin/www //配置文件
由此可见,express是一个标准的MVC框架,通过路由分配视图,并且可以在路由中写上我们的控制结构.
修改 routes下的index.js
router.get('/', function(req, res, next) {
res.render('index', { title: '你好express' });
});
发现我们的页面出现了改变,这个路由跟我们自己写的路由作用是差不多的,具体用法我们后边再谈.
第四章 模板的使用方式
我们在安装express的时候,使用的模板是ejs,具体什么是模板呢?ejs又怎么用呢?
简单来讲,模板就是帮助我们在view层进行输出的工具.我们打开view下的index.ejs,可以看到,这个模板其实就是在ejs下写了我们常见的html代码,只是多了几个不一样的标签:
<!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>
<%= title %>
可以帮助我们将路由中的{ title: '你好express' }
输出到模板当中,其中的render就是模板渲染的方法,帮助我们渲染第一个参数index所对应的模板.类似angular,我们也可以在标签内部写自己的表达式
<h1><%= title.toUpperCase() %></h1>
4.1 模板引擎的用法
ejs有三种常用的标签
-
<% code %>
:运行 JavaScript 代码,不输出 -
<%= code %>
:显示转义后的 HTML内容 -
<%- code %>
:显示原始 HTML 内容
注意:
<%= code %>
和<%- code %>
都可以是JavaScript表达式生成的字符串,当变量code为普通字符串时,两者没有区别。当 code 比如为<h1>hello</h1>
这种字符串时,<%= code %>
会原样输出<h1>hello</h1>
,而<%- code %>
则会显示H1大的hello字符串
下面的例子可以帮我们解释<% code %>
的用法:
index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<ul>
<% for(var i=0; i<contents.length; i++) { %>
<li><%= contents[i] %></li>
<% } %>
</ul>
</body>
</html>
index.js
router.get('/', function(req, res, next) {
res.render('index',{
title: 'express',
contents:['AngularJS','Node.js','Express']
});
});
查看渲染之后的结果.
同样,如果你想要动态的调整js代码,可以将js代码在<script>
标签中按照原样渲染.
4.3 渲染模板的方法
我们刚刚看到,routes下的index.js使用render可以将后边的参数渲染到模板之中,但是,我们在views下无法看到users.ejs,这是因为在user.js下使用了send方法,可以将后边的信息直接渲染而不通过模板.
4.4 includes
我们使用模板引擎通常不是一个页面对应一个模板,这样就失去了模板的优势,而是把模板拆成可复用的模板片段组合使用,如在 views 下新建 header.ejs 和 footer.ejs,并修改users.ejs
views/header.ejs
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
</style>
</head>
<body>
views/footer.ejs
</body>
</html>
views/users.ejs
<%- include('header') %>
<h1><%= name.toUpperCase() %></h1>
<p>hello, <%= name %></p>
<%- include('footer') %>
routes/users.js
router.get('/', function(req, res, next) {
// res.send('respond with a resource');
res.render('users',{
name: 'express'
});
});
访问'localhost:3000/users'可以看到渲染后的结果.
我们将users.ejs拆成出了header.ejs和footer.ejs,并在users.ejs通过 ejs 内置的 include方法引入,从而实现了跟以前一个模板文件相同的功能.
注意:要用
<%- include('header') %>
而不是<%= include('header') %>
第五章 路由
我们在路由中将数据渲染并且分配到模板当中,那么我们如何写一个路由呢?
5.1 入口文件
首先,我们先要了解入口文件的组成,打开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');
路由都是模块写法,而入口文件将这些路由引入,我们就可以直接使用了.
app.use('/', index);
app.use('/users', users);
这则使用了我们的路由
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
这两句话设置了我们的模板引擎,如果我们想要使用sass或者less等css引擎,同样可以向下追加.
// catch 404 and forward to error handler
...
// error handler
...
剩下的部分则是规定了404和500错误下的页面渲染方法.
由此,我们可以在routes下添加的路由文件,并且在入口文件引入
5.2 新增路由
在routes下添加新的路由:
routers/tests.js
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
res.send('这是新的路由');
});
module.exports = router;
同样,在入口文件添加
app.js
var tests = require('./routes/tests');
app.use('/tests', tests);
访问'localhost:3000/tests'则可以获取我们新增加的路由信息.
5.3 路由中的参数
其中 req,res是我们熟悉的请求和响应对象,当我们的地址栏访问模板的时候,可以获取参数
ruoter.get('/:name', function(req, res, next) {
res.send('hello, ' + req.params.name);
});
我们在地址栏请求'localhost:3000/tests/Y18',我们可以看到响应信息
hello, Y18
不难看出:req 包含了请求来的相关信息,res 则用来返回该请求的响应。下面介绍几个常用的
req 的属性:
-
req.query
: 解析后的 url 中的 querystring,如?name=haha
,req.query 的值为{name: 'haha'}
-
req.params
: 解析 url 中的占位符,如/:name
,访问 /haha,req.params 的值为{name: 'haha'}
-
req.body
: 解析后请求体,需使用相关的模块,如 body-parser,请求体为{"name": "haha"}
,则 req.body 为{name: 'haha'}
,我们可以用接受post请求为例. - 注意,无论是send或者render,我们只能获取第一次符合标准的渲染页面
5.4 next和中间件
前面我们讲解了express中路由和模板引擎ejs的用法,但express的精髓并不在此,在于中间件的设计理念。
我们可以看到,在路由的回调函数中,有一个参数next,这就是我们使用中间件最重要的部分。
express中的中间件(middleware)是用来处理请求的,当一个中间件处理完,可以通过调用 next()
传递给下一个中间件,如果没有调用 next()
,则请求不会往下传递,如内置的 res.render
其实就是渲染完 html 直接返回给客户端,如果调用了next()
,那么请求会继续向下传递,访问路由组中的下一个路由。
next中间件分为应用级中间件和路由级中间件
应用级中间件
我们回头再来看一下入口文件:
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);
app.use('/tests', tests);
其实,这些app.use()也是给我们加载中间件,只是这些中间件是在应用中使用的,应用级中间件绑定到 app 对象 使用 app.use() 和 app.METHOD(), 其中, METHOD 是需要处理的 HTTP 请求的方法,例如 GET, PUT, POST 等等,全部小写。我们自定义一下全局中间件:
// 挂载到全局的中间件,任何请求都会执行它
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 挂载至 /hello/:name 的中间件,任何指向 /hello/:name 的请求都会执行它
app.use('/hello/:name', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 路由和句柄函数(中间件系统),处理指向 /hello/:name 的 GET 请求
app.get('/hello/:name', function (req, res, next) {
res.send('hello '+ req.params.name);
});
如果我们不指定路径,则默认匹配所有路径,我们在最后一个路由中并没有使用next,所以请求没有向下传递.
我们在上方引入的其余全局中间件,即第三方中间件,其实也在回调函数的末尾使用了next方法,使得请求能够继续向下传递.
在我们访问'localhost:3000/hello/Y18',可以在控制台和页面都能看到数据返回.
注意:
作为中间件系统的路由句柄,使得为路径定义多个路由成为可能。
// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/hello/:id', function (req, res, next) {
console.log('ID:', req.params.id);
next();
}, function (req, res, next) {
res.send('hello Info');
});
// 处理 /user/:id, 打印出用户 id
app.get('/hello/:id', function (req, res, next) {
res.end(req.params.id);
});
但是由于第一个路由已经终止了请求-响应循环,第二个路由虽然不会带来任何问题,但却永远不会被调用.
如果需要在中间件栈中跳过剩余中间件,调用 next('route') 方法将控制权交给下一个路由
// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/hello/:id', function (req, res, next) {
console.log('ID:', req.params.id);
if(req.params.id == 4) next('route');
else next();
}, function (req, res, next) {
res.send('hello Info');
});
// 处理 /user/:id, 打印出用户 id
app.get('/hello/:id', function (req, res, next) {
res.end(req.params.id);
});
路由级中间件
路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()
修改index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/:name', function(req, res, next) {
// 如果 user id 为 0, 跳到下一个路由
if (req.params.name != 'Y18') next('route');
// 否则将控制权交给栈中下一个中间件
else next();
}, function (req, res, next) {
// 渲染常规页面
res.send('你别来了,Y18');
});
// 处理name为Y18的路由, 渲染一个特殊页面
router.get('/:name', function (req, res, next) {
res.send('欢迎你~ '+ req.params.name);
});
module.exports = router;
错误处理中间件
当我们访问一个不存在的路由时,虽然我们最后一个路由没有写next中间件,但是错误路由依旧执行,查看源码:
https://github.com/expressjs/express/blob/master/lib/router/index.js
function next(err) {
... //此处源码省略
// find next matching layer
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path);
route = layer.route;
if (typeof match !== 'boolean') {
// hold on to layerError
layerError = layerError || match;
}
if (match !== true) {
continue;
}
... //此处源码省略
}
... //此处源码省略
// this should be done for the layer
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
next函数内部有个while循环,每次循环都会从stack中拿出一个layer,这个layer中包含了路由和中间件信息,然后就会用layer和请求的path就行匹配,如果匹配成功就会执行layer.handle_request,调用中间件函数。但如果匹配失败,就会循环下一个layer(即中间件)。
express有成百上千的第三方中间件,在开发过程中我们首先应该去npm上寻找是否有类似实现的中间件,尽量避免造轮子,节省开发时间。下面给出几个常用的搜索 npm 模块的网站:
第六章 在express中使用less或sass
如果我们想要使用sass或者less,首先要对其进行学习;
Express支持sass,less等css引擎,我们现在使用sass编译模板的样式:
重新安装一个express应用:express app --view ejs -c sass
我们可以看到在入口文件中看到变化:
app.use(require('node-sass-middleware')({
src: path.join(__dirname, 'public'),
dest: path.join(__dirname, 'public'),
indentedSyntax: true,
sourceMap: true
}));
这其实就是引入了node的sass中间件。
同时,在public/stylesheets中可以看到style.sass这个sass文件。
启动express,访问'localhost:8000/'我们可以看到在这个文件夹中生成了两个新的文件:
'style.css'
'style.css.map'
这就是我们熟悉的sass经过编译后的文件。
同样,我们可以写自己的sass文件,决定样式:
修改style.sass
$fontStack: Helvetica, sans-serif;
$primaryColor: #8A2BE2;
body
font-family: $fontStack;
padding: 50px;
p
color: $primaryColor;
ul li
color: #BDB76B;
a
text-decoration: none;
修改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>
<ul>
<li>AngularJS</li>
<li>Node.js</li>
<li>Express</li>
</ul>
<a id="test" href="www.zixue.it">自学it网</a>
</body>
</html>
访问'localhost:3000',可以看到我们修改之后的样式.
我们可以修改indentedSyntax改为false,实现scss方式的书写
网友评论