对于中间件

狭义的中间件指操作系统以外可能为利用提供性能的软件,大到云计算厂商的各类服务,小到某一个字段的检测模块。Express 中间件特指基于 Express 中间件机制提供性能的软件模块。Express 在 3.x 及以前的版本依赖 Connect 作为底层提供中间件机制,从 4.x 版本开始内置了与 Connect 兼容的中间件机制,因而基于 Connect 的中间件都能间接在 Express 中应用。

Express 会将处理过程以队列的形式进行组织,在分派申请时,通过递归传递 next 办法顺次调用处理过程(详见源码):

写一个路由修改中间件

在上一章已实现的工程 host1-tech/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.jsconst { normalize } = require('path');const { parse, format } = require('url');module.exports = function urlnormalizeMiddleware() {  return (req, res, next) => {    const pathname = normalize(req.path);    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.jsconst { Router } = require('express');const urlnormalizeMiddleware = require('./urlnormalize');module.exports = async function initMiddlewares() {  const router = Router();  router.use(urlnormalizeMiddleware());  return router;};
// src/server.jsconst 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.jsconst { 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();+}

看一下店铺新增的成果:

本章源码

host1-tech/nodejs-server-examples - 03-middleware

更多浏览

从零搭建 Node.js 企业级 Web 服务器(零):动态服务
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
从零搭建 Node.js 企业级 Web 服务器(二):校验
从零搭建 Node.js 企业级 Web 服务器(三):中间件