关于中间件
广义的中间件指操作系统以外能够为应用提供功能的软件,大到云计算厂商的各类服务,小到某一个字段的检测模块。Express 中间件特指基于 Express 中间件机制提供功能的软件模块。Express 在 3.x 及以前的版本依赖 Connect 作为底层提供中间件机制,从 4.x 版本开始内置了与 Connect 兼容的中间件机制,因此基于 Connect 的中间件都能直接在 Express 中使用。
Express 会将处理过程以队列的方式进行组织,在分派请求时,通过递归传递 next 方法依次调用处理过程(详见源码):
写一个路由修正中间件
在上一章已完成的工程 licg9999/nodejs-server-examples - 02-validate 有一个小小的问题,无法访问路径不规范的接口,比如无法访问 http://localhost:9000/api//shop:
现在通过中间件来解决此类问题:
$ mkdir src/middlewares # 新建 src/middlewares 目录存放自定义中间件
$ tree -L 2 -I node_modules # 展示除了 node_modules 之外的目录内容结构
.
├── Dockerfile
├── package.json
├── public
│ ├── glue.js
│ ├── index.css
│ ├── index.html
│ └── index.js
├── src
│ ├── controllers
│ ├── middlewares
│ ├── moulds
│ ├── server.js
│ └── services
└── yarn.lock
// src/middlewares/urlnormalize.js
const { normalize } = require('path');
const { parse, format } = require('url');
module.exports = function urlnormalizeMiddleware() {
return (req, res, next) => {
// 解决windows、Linux系统使用normalize路径分隔符不一致的问题
const pathname = normalize(req.path).split('\\').join('/');
const urlParsed = parse(req.url);
let shouldRedirect = false;
// 重定向不规范的路径
if (req.path != pathname) {
urlParsed.pathname = pathname;
shouldRedirect = true;
}
// 执行重定向或者略过
if (shouldRedirect) {
res.redirect(format(urlParsed));
} else {
next();
}
};
};
// src/middlewares/index.js
const { Router } = require('express');
const urlnormalizeMiddleware = require('./urlnormalize');
module.exports = async function initMiddlewares() {
const router = Router();
router.use(urlnormalizeMiddleware());
return router;
};
// src/server.js
const express = require('express');
const { resolve } = require('path');
const { promisify } = require('util');
+const initMiddlewares = require('./middlewares');
const initControllers = require('./controllers');
// ...
async function bootstrap() {
server.use(express.static(publicDir));
server.use('/moulds', express.static(mouldsDir));
+ server.use(await initMiddlewares());
server.use(await initControllers());
await promisify(server.listen.bind(server, port))();
console.log(`> Started on port ${port}`);
}
bootstrap();
访问 http://localhost:9000/api//shop 即可看到自动重定向至有效路由:
补充店铺新增逻辑
到目前为止的店铺管理缺少了店铺新增逻辑,因为 post 解析需要依赖 body-parser 这一中间件,所以才在本章补充这一功能。执行 body-parser 安装命令:
$ yarn add body-parser
# ...
info Direct dependencies
└─ body-parser@1.19.0
# ...
后端处理:
// src/services/shop.js
// ...
class ShopService {
// ...
+ async create({ values }) {
+ await delay();
+
+ const id = String(
+ 1 +
+ Object.keys(memoryStorage).reduce((m, id) => Math.max(m, id), -Infinity)
+ );
+
+ return { id, ...(memoryStorage[id] = values) };
+ }
}
// ...
// src/controllers/shop.js
const { Router } = require('express');
+const bodyParser = require('body-parser');
const shopService = require('../services/shop');
const { createShopFormSchema } = require('../moulds/ShopForm');
class ShopController {
shopService;
async init() {
this.shopService = await shopService();
const router = Router();
router.get('/', this.getAll);
router.get('/:shopId', this.getOne);
router.put('/:shopId', this.put);
router.delete('/:shopId', this.delete);
+ router.post('/', bodyParser.urlencoded({ extended: false }), this.post);
return router;
}
// ...
+ post = async (req, res) => {
+ const { name } = req.body;
+
+ try {
+ await createShopFormSchema().validate({ name });
+ } catch (e) {
+ res.status(400).send({ success: false, message: e.message });
+ return;
+ }
+
+ const shopInfo = await this.shopService.create({ values: { name } });
+
+ res.send({ success: true, data: shopInfo });
+ };
}
// ...
前端处理:
// public/index.js
// ...
export async function refreshShopList() {
const res = await fetch('/api/shop');
const { data: shopList } = await res.json();
const htmlItems = shopList.map(
({ id, name }) => `
<li data-shop-id="${id}">
<div data-type="text">${name}</div>
<input type="text" placeholder="输入新的店铺名称" />
<a href="#" data-type="modify">确认修改</a>
<a href="#" data-type="remove">删除店铺</a>
<div class="error"></div>
</li>`
);
document.querySelector('#root').innerHTML = `
<h1>店铺列表:</h1>
-<ul class="shop-list">${htmlItems.join('')}</ul>`;
+<ul class="shop-list">${htmlItems.join('')}</ul>
+<h1>店铺新增:</h1>
+<form method="post" action="/api/shop">
+ <label>新店铺的名称:</label>
+ <input type="text" name="name" />
+ <button type="submit" data-type="create">确认新增</button>
+ <span class="error"></span>
+</form>`;
}
export async function bindShopInfoEvents() {
document.querySelector('#root').addEventListener('click', async (e) => {
e.preventDefault();
switch (e.target.dataset.type) {
case 'modify':
await modifyShopInfo(e);
break;
case 'remove':
await removeShopInfo(e);
break;
+ case 'create':
+ await createShopInfo(e);
+ break;
}
});
}
// ...
+export async function createShopInfo(e) {
+ e.preventDefault();
+ const name = e.target.parentElement.querySelector('input[name=name]').value;
+
+ try {
+ await createShopFormSchema().validate({ name });
+ } catch ({ message }) {
+ e.target.parentElement.querySelector('.error').innerHTML = message;
+ return;
+ }
+
+ await fetch('/api/shop', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: `name=${encodeURIComponent(name)}`,
+ });
+
+ await refreshShopList();
+}
看一下店铺新增的效果:
本章源码
licg9999/nodejs-server-examples - 03-middleware
更多阅读
从零搭建 Node.js 企业级 Web 服务器(零):静态服务
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
从零搭建 Node.js 企业级 Web 服务器(二):校验
从零搭建 Node.js 企业级 Web 服务器(三):中间件
从零搭建 Node.js 企业级 Web 服务器(四):异常处理
从零搭建 Node.js 企业级 Web 服务器(五):数据库访问
从零搭建 Node.js 企业级 Web 服务器(六):会话
从零搭建 Node.js 企业级 Web 服务器(七):认证登录
从零搭建 Node.js 企业级 Web 服务器(八):网络安全
从零搭建 Node.js 企业级 Web 服务器(九):配置项
从零搭建 Node.js 企业级 Web 服务器(十):日志
从零搭建 Node.js 企业级 Web 服务器(十一):定时任务
从零搭建 Node.js 企业级 Web 服务器(十二):远程调用
从零搭建 Node.js 企业级 Web 服务器(十三):断点调试与性能分析
从零搭建 Node.js 企业级 Web 服务器(十四):自动化测试
从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。