从零搭建 Node.js 企业级 Web 服务器(三):中间件

关于中间件

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

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

从零搭建 Node.js 企业级 Web 服务器(三):中间件_第1张图片

写一个路由修正中间件

在上一章已完成的工程 host1-tech/nodejs-server-examples - 02-validate 有一个小小的问题,无法访问路径不规范的接口,比如无法访问 http://localhost:9000/api//shop

从零搭建 Node.js 企业级 Web 服务器(三):中间件_第2张图片

现在通过中间件来解决此类问题:

$ 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) => {
    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.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 即可看到自动重定向至有效路由:

7a5c15c3f2c25fa738b99126bc6b04a0bf954a53.gif

补充店铺新增逻辑

到目前为止的店铺管理缺少了店铺新增逻辑,因为 post 解析需要依赖 body-parser 这一中间件,所以才在本章补充这一功能。执行 body-parser 安装命令:

$ yarn add body-parser
# ...
info Direct dependencies
└─ [email protected]
# ...

后端处理:

// 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 }) => `
  • ${name}
    确认修改 删除店铺
  • ` ); document.querySelector('#root').innerHTML = `

    店铺列表:

    -
      ${htmlItems.join('')}
    `; +
      ${htmlItems.join('')}
    +

    店铺新增:

    +
    + + + + +
    `; } 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 服务器(三):中间件
    从零搭建 Node.js 企业级 Web 服务器(四):异常处理
    从零搭建 Node.js 企业级 Web 服务器(五):数据库访问

    你可能感兴趣的:(javascript,node.js,前端,后端,服务器)