1.Express框架
1.1 express是什么?
创建http服务器特别麻烦,express框架解决了这个的问题。
Express在node界的地位,就相当于jQuery在DOM界的地位。Express 是一个自身功能极简,完全是由路由和中间件构成一个的 web 开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件。
中文官网(实际上也是英语的):http://www.expressjs.com.cn/
官方的概念为:基于 Node.js 平台,快速、开放、极简的 web 开发框架。
1.2 express的安装和使用、
安装方式为:
npm install express --save //局部
npm install express --g //全局
express框架的API非常简单,如下:
var express = require("express"); //返回的是一个函数
var app = express(); //创建app,express函数执行返回一个app对象
//当用户get请求访问/的时候
app.get("/", (req,res)=>{
//输出可用end()或send()方法,而且不用设置字符集,因为express封装好了
res.send("首页
");
})
//当用户get请求访问/music的时候
app.get("/music", (req,res)=>{
res.send("音乐频道
");
})
app.listen(3000, function(err){
console.log("3000端口")
})
express开发项目可以使用express应用程序生成器:
安装:全局安装express-generator
npm install -g express-generator
创建:在想要的文件夹位置运行命令,创建一个名为 myapp 的 express 应用程序
express --view=pug myapp
成功后会自动在目标位置创建一个名为 myapp 的项目并生成很多文件,命令行显示效果如下:
执行命令:在命令行最下面有提示:
1、进入项目文件夹:cd myapp
2、安装依赖:npm install
3、启动:npm start
查看:在浏览器输入localhost:3000,会打开一个页面显示Welcome to Express,说明已经成功启动服务了。至此。一个express项目完成。
1.3 express的项目结构
着你的应用发展到一定的大小和复杂时,事情可能就会变得令人困惑了。你的代码太乱了。随着你团队的成长,在原来代码基础上继续工作将变得很困难。每当代码合并时,你都要和冲突进行“斗争”。添加新的特性和不断地处理新的情况的话,需要改变应用的结构。而且,有如此多不同的方法来组织你的文件你和代码,并且在这些方法中很难选择出最适合你的。,为了方便后期的开发,一个express项目需要一个合理的项目结构:
├── app.js //项目的入口文件
├── bin //存放项目启动的执行文件
│ └── www
├── node_modules //安装的依赖
├── package.json //项目的依赖配置与版本信息
├── public //存放静态文件的目录
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes //路由文件,默认创建的express项目包括index.js和user.js
│ ├── index.js
│ └── users.js
└── views //视图文件,保存模板引擎中的模板
├── error.pug
├── index.pug
└── layout.pug
2. 应用 Application
2.1 app.use()和app.get()的区别
回调函数不同
app.use(path,callback)中的callback既可以是router对象又可以是函数
app.get(path,callback)中的callback只能是函数
当一个路由有好多个子路由时用app.use(path,router)
例子:
http://localhost:3000/home/one
http://localhost:3000/home/second
http://localhost:3000/home/three
路由/home后面有三个子路由紧紧跟随,分别是/one,/second,/three
如果使用app.get(),则要不断的重复,很麻烦,也不利用区分
app.get("/home",callback)
app.get("/home/one",callback)
app.get("/home/second",callback)
app.get("/home/three",callback)
我们可以创建一个router.js 专门用来一个路由匹配多个子路由
var express = require('express')
var router = express.Router()
router.get("/",(req,res)=>{
res.send("/")
})
router.get("/one",(req,res)=>{
res.send("one")
})
router.get("/second",(req,res)=>{
res.send("second")
})
router.get("/treen",(req,res)=>{
res.send("treen")
})
module.exports = router;
在app.js中导入router.js
var express = require('express')
var router = require("./router")
var app = express()
app.use('/home',router) //router路由对象中的路由都会匹配到"/home"路由后面
app.get('/about', function (req, res) {
console.log(req.query)
res.send('你好,我是 Express!')
})
// 4 .启动服务
app.listen(3000, function () {
console.log('app is running at port 3000.')
})
什么时用app.use,什么时用app.get呢?
路由规则是app.use(path,router)
定义的,router
代表一个由express.Router()
创建的对象,在路由对象中可定义多个路由规则。可是如果我们的路由只有一条规则时,可直接接一个回调作为简写,也可直接使用app.get
或app.post
方法。即
当一个路径有多个匹配规则时,使用app.use()
app.use(express.static('public'));
为了提供对静态资源文件(图片,css,js文件)的服务,请使用Express内置的中间函数express.static.
传递一个包含静态资源的目录给express.static中间件用于立即开始提供文件。 比如用以下代码来提供public目录下的图片、css文件和js文件:
app.use(express.static('public'));
如果前台想请求后台public目录下images/08.jpg静态的图片资源
通过: http://localhost:3000/images/08.jpg
通过多次使用 express.static中间件来添加多个静态资源目录:
app.use(express.static('public'));
app.use(express.static('file'));
Express将会按照你设置静态资源目录的顺序来查看静态资源文件。
为了给静态资源文件创建一个虚拟的文件前缀(文件系统中不存在),可以使用express.static函数指定一个虚拟的静态目录,如下:
app.use('/static', express.static('public'))
现在你可以使用‘/static’作为前缀来加载public文件夹下的文件了
比如: http:// localhost:3000/static/image/kitten.jpg
2.2 express路由
2.2.1基本路由
通常HTTP URL的格式是这样的:
http://host[:port][path]
http表示协议。
host表示主机。
port为端口,可选字段,不提供时默认为80。
path指定请求资源的URI(Uniform Resource Identifier,统一资源定位符),如果URL中没有给出path,一般会默认成“/”(通常由浏览器或其它HTTP客户端完成补充上)。
所谓路由,就是如何处理HTTP请求中的路径部分。比如“http://xxx.com/users/profile”这个URL,路由将决定怎么处理/users/profile这个路径。
还是先直接看一个demo:基本的路由示例
const express = require('express')
const app = express()
app.get('/', function (req, res) {
res.send('hello, express')
})
app.get('/users/:name', function (req, res) {
res.send('hello, ' + req.params.name)
})
app.listen(3000)
在上面的demo中,当我们访问'/'这个路径的时候,服务端响应返回一个'hello express'
,其实这就是一个express基本的路由。
express路由是由一个 URI、HTTP 请求(GET、POST等)和若干个句柄组成,它的结构如下: app.method(path, [callback...], callback), app是 express对象的一个实例,method是一个 HTTP 请求方法, path是服务器上的路径, callback是当路由匹配时要执行的函数。
下面展示一个post请求的路由
app.post('/', function (req, res) {
res.send('POST request to the homepage');
});
2.2.2 路由句柄
处理特定路由的回调函数叫做路由句柄,路由句柄行为类似中间件,例如:
使用一个回调函数处理路由:
app.get('/example/a', function (req, res) {
res.send('Hello from A!');
});
使用多个回调函数处理路由
//需要指定 next 对象
app.get('/example/b', function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from B!');
});
使用回调函数数组处理路由
var cb0 = function (req, res, next) {
console.log('CB0');
next();
}
var cb1 = function (req, res, next) {
console.log('CB1');
next();
}
var cb2 = function (req, res) {
res.send('Hello from C!');
}
app.get('/example/c', [cb0, cb1, cb2]);
express 要路由到相应的处理函数,需要method和path两个都满足条件。
(1) method就是'get'、'post'等,根据这个来找到map中相应的属性
(2)然后在数组中,检查path是否符合里面的Route的要求,如果符合,相应的callback函数会依次(看后面的解释)执行。callback函数的签名是:
function(req,res,next){}
如果一个callback没有结束一个请求响应的生命周期( 比如写下res.end('...') ),那么一般需要在最后写上next(),像下面那样
function(req,res,next){
//..自己的业务逻辑
next();
}
这样,就会继续检查下面的一个Route
如果callback中,会终结一个请求响应周期(一般也是这样),那么就算有next(),也不会继续检查下一个Route
//一般路由函数都应该和这个类似
function(req,res,next){
//..自己的业务逻辑
res.end('...');//或res.render('...')等
}
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'}
app.get:('/',function(req,res))获得get请求
app.post:('/',function(req,res))获得post请求
app.all:('/',function(req,res))获得所有(包括get和post)请求
2.2.3 路由模块
router目的是将路由进行进一步的划分。
可以将其视为“迷你应用程序”,只能执行中间件和路由功能。每个Express应用程序都有一个内置的应用程序路由器。
顶级express对象具有Router()创建新router对象的功能。
var router = express.Router([options]);
可选options参数指定路由器的行为。
属性 | 描述 | 默认 |
---|---|---|
caseSensitive | 启用区分大小写 | 默认情况下禁用,将“/ Foo”和“/ foo”视为相同。 |
mergeParams | 保留req.params父路由器的值。如果父项和子项具有冲突的参数名称,则子项的值优先。 | false |
strict | 启用严格路由。 | 默认情况下禁用,“/ foo”和“/ foo /”由路由器处理相同。 |
其实我们可以将router视为一个路由分支,因此router是有中间件和HTTP方法路由(如get,put,post,等),router就像一个应用程序。
之前我们的代码都是写在了app.js中,不利于阅读维护,多人开发时肯定问题繁多,所以我们需要将路由模块化。
如果我们的功能主要有登录,商品管理,那么主要的模块就是登录和商品
所以在路由目录就有登录和商品两部分的路由
login.js
const express = require("express");
const router = express.Router();
router.get("/",function (req,res) {
res.send("登录页面")
});
router.get("doLogin",function (req,res) {
});
module.exports = router;
product.js
const express = require("express");
const router = express.Router();
router.get("/",function (req,res) {
res.send("商品首页")
});
router.get("/add",function (req,res) {
res.send("商品首页")
});
router.get("/delete",function (req,res) {
res.send("商品首页")
});
module.exports = router;
admin.js
这两部分放到一个公共的路由文件中
const express = require("express");
const router = express.Router();
//相当于后台的路由,所有的后台处理都需要从这里经过
const login = require("admin/login");
const product = require("admin/product");
router.use("/login",login);
router.use("/product",product);
module.exports = router;
在app.js中导入
const express = require("express");
const app = new express();
//引入route模块
const admin = require("./route/admin");
//加载admin模块
app.use("/admin",admin);
app.listen("3001","127.0.0.1");
2.3 中间件
Express里有个中间件(middleware)的概念。所谓中间件,就是在收到请求后和发送响应之前这个阶段执行的一些函数。
要在一条路由的处理链上插入中间件,可以使用express对象的use方法。该方法原型如下:
app.use([path,] function [, function...])
当app.use没有提供path参数时,路径默认为“/”。当你为某个路径安装了中间件,则当以该路径为基础的路径被访问时,都会应用该中间件。比如你为“/abcd”设置了中间件,那么“/abcd/xxx”被访问时也会应用该中间件。
中间件函数的原型如下:
function (req, res, next)
第一个参数是Request对象req。第二个参数是Response对象res。第三个则是用来驱动中间件调用链的函数next,如果你想让后面的中间件继续处理请求,就需要调用next方法。
给某个路径应用中间件函数的典型调用是这样的:
app.use('/abcd', function (req, res, next) {
console.log(req.baseUrl);
next();
})
中间件可以分成几类:
(1)应用级中间件
var app = express();
// 没有挂载路径的中间件,应用的每个请求都会先执行该中间件,next()会让其进入下一个中间件
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 挂载至 /user/:id 的中间件,任何指向 /user/:id 的请求都会执行它,next()后接着进入下一个中间件
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 路由和句柄函数(中间件系统),处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
一个中间件执行完需要使用next()或者end()函数将执行权交给下一个中间件,否则之后的中间件将不会执行。
(2) app.static中间件
Express提供了一个static中间件,可以用来处理网站里的静态文件的GET请求,可以通过express.static访问。
express.static的用法如下:
express.static(root, [options])
第一个参数root,是要处理的静态资源的根目录,可以是绝对路径,也可以是相对路径。第二个可选参数用来指定一些选项,比如maxAge、lastModified等,更多选项的介绍看这里:http://expressjs.com/guide/using-middleware.html#middleware.built-in。
一个典型的express.static应用如下:
var options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function (res, path, stat) {
res.set('x-timestamp', Date.now());
}
}
app.use(express.static('public', options));
上面这段代码将当前路径下的public目录作为静态文件,并且为Cache-Control头部的max-age选项为1天。还有其它一些属性,请对照express.static的文档来理解。
使用express创建的HelloExpress项目的app.js文件里有这样一行代码:
app.use(express.static(path.join(__dirname, 'public')));
这行代码将HelloExpress目录下的public目录作为静态文件交给static中间件来处理,对应的HTTP URI为“/”。path是一个Node.js模块,__dirname是Node.js的全局变量,指向当前运行的js脚本所在的目录。path.join()则用来拼接目录。
有了上面的代码,你就可以在浏览器里访问“http://localhost:3000/stylesheets/style.css”。我们做一点改动,把上面的代码修改成下面这样:
app.use('/static', express.static(path.join(__dirname, 'public')));
上面的代码呢,针对/static路径使用static中间件处理public目录。这时你再用浏览器访问“http://localhost:3000/stylesheets/”就会看到一个404页面,将地址换成“http://localhost:3000/static/stylesheets/style.css”就可以了。
(3)路由级中间件
路由级中间件和应用级中间件类似,只是他绑定的对象express.Router(),还是直接抛demo吧
//引入express.Router()方法
var app = express();
var router = express.Router();
// 没有挂载路径的中间件,通过该路由的每个请求都会执行该中间件
router.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 一个中间件栈,显示任何指向 /user/:id 的 HTTP 请求的信息
router.use('/user/:id', function(req, res, next) {
console.log('Request URL:', req.originalUrl);
next();
}, function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 一个中间件栈,处理指向 /user/:id 的 GET 请求
router.get('/user/:id', function (req, res, next) {
// 如果 user id 为 0, 跳到下一个路由
if (req.params.id == 0) next('route');
// 负责将控制权交给栈中下一个中间件
else next(); //
}, function (req, res, next) {
// 渲染常规页面
res.render('OK');
});
// 处理 /user/:id, 渲染一个特殊页面
router.get('/user/:id', function (req, res, next) {
console.log(req.params.id);
res.render('special');
});
//将路由挂载到应用上
app.use('/',router)
2.2.4响应方法
下表中响应对象(res)的方法向客户端返回响应,终结请求响应的循环。如果在路由句柄中一个方法也不调用,来自客户端的请求会一直挂起。

2.4 功能开关,变量容器
app.set()
和 app.get()
可以用来保存 app 级别的变量(对, app.get()
还和 GET 方法的实现名字上还冲突了):
const express = require('express');
const app = express();
app.all('/', (req, res) => {
app.set('title', '标题123');
res.send('ok');
});
app.all('/t', (req, res) => {
res.send(app.get('title'));
});
app.listen(8888);
上面的代码,启动之后直接访问 /t
是没有内容的,先访问 /
再访问 /t
才可以看到内容。
对于变量名, Express 预置了一些,这些变量的值,可以叫 settings ,它们同时也影响整个应用的行为:
case sensitive routing
env
etag
jsonp callback name
json escape
json replacer
json spaces
query parser
strict routing
subdomain offset
trust proxy
views
view cache
view engine
x-powered-by
具体的作用,可以参考 https://expressjs.com/en/4x/api.html#app.set 。
(上面这些值中,干嘛不放一个最基本的 debug 呢……)
除了基本的 set() / get()
,还有一组 enable() / disable() / enabled() / disabled()
的包装方法,其实就是 set(name, false)
这种。 set(name)
这种只传一个参数,也可以获取到值,等于 get(name)
。
app.set()设定表可查看
express 4.x版本官网的app.set()的属性和使用方法
2.5 视图与模板引擎
在开始之前,我们有必要解释下何为* 视图引擎(view engine)*
?视图引擎作为编程术语它主要意思是指“进行视图渲染的模块”。而 Express 框架中最常用的两个视图引擎是 Pug 和 EJS 。需要注意的是,Pug 早期的名称是 Jade 由于某些原因不得已改名 。
另外,Express 并没有指定必须使用何种引擎。只要该视图引擎的设计符合 Express API 规范,你就可以将其应用到工程中。下面,我们看看它到底是如何工作的。
下面我们通过一个简单示例回顾下 EJS 渲染过程:
var express = require("express");
var path = require("path");
var app = express();
app.set("view engine", "ejs");
app.set("views", path.resolve(__dirname, "views"));
app.get("/", function(req, res) {
res.render("index",{name:res.params.name});
});
app.listen(3000);复制代码在运行代码之前,你需要通过 npm install 安装 EJS 和 Express。在安装完成后访问应用主页的话,程序就会寻找 views/index.ejs 文件并使用 EJS 对其进行渲染。另外,工程中一般都只会使用一个视图引擎,因为多个引擎会给工程引入不必要的复杂性。
express用esj---改为文件是html后缀的:
var express = require("express");
var app = express();
// 规定何种文件用何种方法来渲染
app.engine('html', require('ejs').__express);
//设置模板引擎中模板所在的文件夹
app.set('views', process.cwd() + '/views');
//设置视图引擎的后缀名,当render函数的路径省略了后缀的时候,用这个来适配默认的后缀以查找引擎。
app.set('view engine', 'ejs');
app.get('/views/:name', function(req, res) {
res.render('index', {
appName: req.params.name,
appTitle: 'peng'
});
});
app.listen(3000);
EJS 语法
除了用做 HTML 模版之外,它还能应用于字符串和纯文本中。请看 EJS 是如何对下面文本模版进行渲染的:
Hi <%= name %>!
You were born in <%= birthyear %>, so that means you're <%= (new Date()).getFullYear() - birthyear %> years old.
<% if (career) { -%>
<%=: career | capitalize %> is a cool career!
<% } else { -%>
Haven't started a career yet? That's cool.
<% } -%>
将下面的 JSON 数据传入上面摸板中:
{
name: "Tony Hawk",
birthyear: 1968,
career: "skateboarding",
bio: "Tony Hawk is the coolest skateboarder around."
}
最终渲染出来的:
Hi Tony Hawk!
You were born in 1968, so that means you’re 47 years old.
Skateboarding is a cool career!
Oh, let’s read your bio: Tony Hawk is the coolest skateboarder around. See
上面代码该示例演示了 EJS 常用的四种语法:打印
、 打印并转义
、执行 JS 代码
、过滤
。
在 EJS 你可以使用两种语法打印表达式的值:<%= expression %>
和 <%- expression %>
,其中前者会对结果进行 HTML 转义。例如,当传入的 expression 值为 Express 时,前者执行的结果是 Express 而后者得到的字符串是 Express。我建议你使用前一种方式,因为它更为可靠。
同样,EJS 还允许你 通过 * <% expression %>*
语法在其中执行 JS 表达式,并且该表达式并不会被打印出来。该特性在执行循环和条件判断的时候非常有用。另外,你还可以通过 * <% expression -%>*
避免不必要的换行。
通过 * <%=: expression | xxx %>*
语法,我们可以对表达式结果再进行一次过滤处理。例如,上面我们就对表达式结果应用了首字母大写过滤器。当然,除了自带的大量过滤器之外,你还可以进行自定义。
在已有 EJS 文件中嵌入其他 EJS 模版
EJS 引擎允许你在当前模版中使用另一个 EJS 模版。这样我们就能对整个进行组件拆分复用。例如,将 HTML 的头部和尾部拆分为 header 和 footer 模块,然后在其他模版中进行组合复用。
示例如下:首先我们创建 index.ejs 并拷贝代码:
<%= appTitle %>
<%-include ('./header.ejs')%>