内容概要
- Express.js是什么?
- 使用中间件
- 优雅的处理Requests和Responses
- 返回HTML页面
背景
服务端逻辑很复杂,我们需要更多关注的是业务逻辑,而不是底层细节 ,框架可以隐藏底层细节,提供帮助函数,工具与规则来帮助构建应用。
Express.js是什么
Express.js 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。
Express.js以外的选择
- 纯Node.js
- Koa
- Sails.js
- Hapi.js
- ...
安装Express.js
npm install --save express
npm install --save-dev nodemon使用nodemon启动服务,可以监听文件的变动,并自动重启服务。
package.json
{
"name": "node-express",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon app.js",
"start-server": "node app.js"
},
"author": "",
"license": "MIT",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"nodemon": "^2.0.2"
}
}添加中间件
app.js
const http = require('http');
const express = require('express');
const app = express();
// 所有请求都会进入到中间件
app.use((req, res, next) => {
console.log('In the middleware!');
next(); // 使请求进入到下一个中间件
});
app.use((req, res, next) => {
console.log('In another middleware!');
});
const server = http.createServer(app);
server.listen(3000);终端运行
npm start启动服务,浏览器访问localhost:3000,可以在终端上看到进入中间件的日志
中间件是怎样工作的
在浏览器上没有看到消息返回,我们可以在中间件中调用send接口返回消息
app.use((req, res, next) => {
console.log('In another middleware!');
res.send('<h1>Hello from Express!</h1>');
});在浏览器中就可以看到Hello from Express!
Express.js send源码分析
在github的express库的response.js文件send函数中可以看到以下代码
case 'string':
if (!this.get('Content-Type')) {
this.type('html');
}
break;传参为字符串并未指定Content-Type时,默认设为html类型
处理不同的路由
app.use('/', (req, res, next) => {
console.log('This always runs!');
next();
});
app.use('/add-product', (req, res, next) => {
console.log('In another middleware!');
res.send('<h1>添加商品页</h1>');
});
app.use('/', (req, res, next) => {
console.log('In another middleware!');
res.send('<h1>Hello from Express!</h1>');
});在app.use第一个参数传入请求路径,可以处理不同的请求
限制只处理POST请求
app.post('/product', (req, res, next) => {
res.redirect('/');
});使用post,只有POST请求才能进入这个中间件
解析请求
可以使用body-parser包来解析请求
npm install --save body-parserconst express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/add-product', (req, res, next) => {
res.send('<form action="/product" method="POST"><input type="text" name="title"><button type="submit">添加商品</button></form>');
});
app.post('/product', (req, res, next) => {
console.log(req.body);
res.redirect('/');
});
app.use('/', (req, res, next) => {
res.send('<h1>Hello from Express!</h1>');
});
app.listen(3000);在浏览器访问http://localhost:3000/add-product,在输入框输入商品名称,点击添加商品,在终端可以看到请求参数{ title: '测试' }
使用Express路由
使用Express路由将路由处理代码移到单独的文件中
创建文件/routes/admin.js,/routes/shop.js
/routes/admin.js
const express = require('express');
const router = express.Router();
router.get('/add-product', (req, res, next) => {
res.send('<form action="/product" method="POST"><input type="text" name="title"><button type="submit">添加商品</button></form>' );
});
router.post('/add-product', (req, res, next) => {
console.log(req.body);
res.redirect('/');
});
module.exports = router;/routes/shop.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res, next) => {
res.send('<h1>Hello from Express!</h1>');
});
module.exports = router;app.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const adminRoutes = require('./routes/admin');
const shopRoutes = require('./routes/shop');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(adminRoutes);
app.use(shopRoutes);
app.listen(3000);使用Express路由使逻辑分离,在各自模块中处理自己的逻辑
添加404错误页面
app.js
app.use((req, res, next) => {
res.status(404).send('<h1>页面没有找到</h1>');
});在所有中间件后加入以上代码,当访问不存在的路径时,会返回页面没有找到的提示,status设置返回消息的状态码为404
过滤路径
app.use('/admin', adminRoutes);在use的第一个参数传入路径参数/admin,那么adminRoutes中的所有路径前面都加上了/admin
此时/routes/admin.js的路径变为
// /admin/add-product => GET
router.get('/add-product', (req, res, next) => {
res.send('<form action="/admin/add-product" method="POST"><input type="text" name="title"><button type="submit">添加商品</button></form>');
});
// /admin/add-product => POST
router.post('/add-product', (req, res, next) => {
console.log(req.body);
res.redirect('/');
});创建HTML页面
/views/add-product.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>商品管理</title>
</head>
<body>
<header>
<nav>
<ul>
<li><a href="/">商店</a></li>
<li><a href="/admin/add-product">商品管理</a></li>
</ul>
</nav>
</header>
<main>
<form action="/admin/add-product" method="POST">
<input type="text" name="title">
<button type="submit">添加商品</button>
</form>
</main>
</body>
</html>/views/shop.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>商店</title>
</head>
<body>
<header>
<nav>
<ul>
<li><a href="/">商店</a></li>
<li><a href="/admin/add-product">商品管理</a></li>
</ul>
</nav>
</header>
<main>
<h1>我的商品</h1>
<p>商品列表...</p>
</main>
</body>
</html>提供HTML页面服务
修改/routes/admin.js和/routes/shop.js将HTML页面内容返回给前端
/routes/admin.js
const path = require('path');
const express = require('express');
const router = express.Router();
// /admin/add-product => GET
router.get('/add-product', (req, res, next) => {
res.sendFile(path.join(__dirname, '../', 'views', 'add-product.html'));
});
// /admin/add-product => POST
router.post('/add-product', (req, res, next) => {
console.log(req.body);
res.redirect('/');
});
module.exports = router;注意路径拼接使用path.join可以不用管操作系统路径分隔符/和\的差异
返回404页面
添加文件/views/404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>页面没有找到</title>
</head>
<body>
<h1>页面没有找到!</h1>
</body>
</html>app.js增加以下代码
app.use((req, res, next) => {
res.status(404).sendFile(path.join(__dirname, 'views', '404.html'));
});使用帮助函数来导航
添加文件/util/path.js
const path = require('path');
module.exports = path.dirname(process.mainModule.filename);process.mainModule.filename为启动服务的文件完整路径
path.dirname(process.mainModule.filename)便可以获取到服务启动的路径
修改/routes/admin.js的路径拼接
const rootDir = require('../util/path');
// /admin/add-product => GET
router.get('/add-product', (req, res, next) => {
res.sendFile(path.join(rootDir, 'views', 'add-product.html'));
});修改/routes/shop.js的路径拼接
const rootDir = require('../util/path');
router.get('/', (req, res, next) => {
res.sendFile(path.join(rootDir, 'views', 'shop.html'));
});为页面加上样式
添加文件/public/css/main.css
body {
padding: 0;
margin: 0;
font-family: sans-serif;
}
main {
padding: 1rem;
}
.main-header {
width: 100%;
height: 3.5rem;
background-color: #dbc441;
padding: 0 1.5rem;
}
.main-header__nav {
height: 100%;
display: flex;
align-items: center;
}
.main-header__item-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
}
.main-header__item {
margin: 0 1rem;
padding: 0;
}
.main-header__item a {
text-decoration: none;
color: black;
}
.main-header__item a:hover,
.main-header__item a:active,
.main-header__item a.active {
color: #3e00a1;
}添加文件/public/css/products.css
.product-form {
width: 20rem;
max-width: 90%;
margin: auto;
}
.form-control {
margin: 1rem 0;
}
.form-control label,
.form-control input {
display: block;
width: 100%;
}
.form-control input {
border: 1px solid #dbc441;
font: inherit;
border-radius: 2px;
}
button {
font: inherit;
border: 1px solid #3e00a1;
color: #3e00a1;
background: white;
border-radius: 3px;
cursor: pointer;
}
button:hover,
button:active {
background-color: #3e00a1;
color: white;
}/views/add-product.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>商品管理</title>
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/product.css">
</head>
<body>
<header class="main-header">
<nav class="main-header__nav">
<ul class="main-header__item-list">
<li class="main-header__item"><a href="/">商店</a></li>
<li class="main-header__item"><a class="active" href="/admin/add-product">商品</a></li>
</ul>
</nav>
</header>
<main>
<form class="product-form" action="/admin/add-product" method="POST">
<div class="form-control">
<label for="title">名称</label>
<input type="text" name="title" id="title">
</div>
<button type="submit">添加商品</button>
</form>
</main>
</body>
</html>/views/shop.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>商品管理</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<header class="main-header">
<nav class="main-header__nav">
<ul class="main-header__item-list">
<li class="main-header__item"><a class="active" href="/">商店</a></li>
<li class="main-header__item"><a href="/admin/add-product">商品管理</a></li>
</ul>
</nav>
</header>
<main>
<h1>我的商品</h1>
<p>商品列表...</p>
</main>
</body>
</html>/views/404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>页面没有找到</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<header class="main-header">
<nav class="main-header__nav">
<ul class="main-header__item-list">
<li class="main-header__item"><a class="active" href="/">商店</a></li>
<li class="main-header__item"><a href="/admin/add-product">商品管理</a></li>
</ul>
</nav>
</header>
<h1>页面没有找到!</h1>
</body>
</html>此时访问localhost:3000,会发现css文件找不到,在下一节会来解决这个问题
创建静态文件服务
在app.js中增加以下代码来创建静态文件服务
app.use(express.static(path.join(__dirname, 'public')));静态文件请求便会转到public目录下,此时再访问localhost:3000,就能请求到css文件了
总结
Express.js是什么:
- Express.js是Node.js框架-提供了很多实用的函数和工具以及应该该怎样构建的规则(中间件)
- 高度易扩展,其它包可以插入到Express.js中使用(中间件)
中间件,next()和response
- Express.js重度依赖中间件函数-可以很容易的通过调用use()来添加
- 中间件函数处理请求,调用next()将请求导入到下一个函数或发送回复
- 通过path和method来过滤请求
- 使用express.Router优雅的将路由分离到文件中
- 使用sendFile()发送HTML文件
- 如果请求你的就是一个文件(例如css文件),可以通过express.static()为这些文件启动静态服务