Building a Node.js Enterprise Web Server from Scratch: Middleware

About middleware

In a broad sense, middleware refers to software that can provide functions for applications other than the operating system, ranging from various services of cloud computing vendors to detection modules in a certain field. Express middleware refers specifically to software modules that provide functions based on the Express middleware mechanism. Express depends on 3.x and earlier versions Connect The middleware mechanism is provided as the bottom layer. Since version 4.x, a middleware mechanism compatible with Connect has been built in, so the middleware based on Connect can be used directly in Express.

Express organizes the processing process in a queue, and calls the processing process in turn by recursively passing the next method when dispatching the request (see details for details). source code):

Write a route correction middleware

Projects completed in the previous chapter host1-tech/nodejs-server-examples - 02-validate There is a small problem, the interface with non-standard path cannot be accessed, such as cannot access http://localhost:9000/api//shop:

Such problems are now solved by middleware:

$ mkdir src/middlewares       # Create a new src/middlewares directory to store custom middleware

$ tree -L 2 -I node_modules   # Show the directory content structure except 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;

    // Redirect non-canonical paths
    if (req.path != pathname) {
      urlParsed.pathname = pathname;
      shouldRedirect = true;
    }

    // perform redirection or skip
    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();

access http://localhost:9000/api//shop You can see the automatic redirection to a valid route:

Supplementary store new logic

The store management so far lacks the store's new logic, because post parsing needs to rely on body-parser This middleware, so this function is supplemented in this chapter. Execute the body-parser installation command:

$ yarn add body-parser
# ...
info Direct dependencies
└─ body-parser@1.19.0
# ...

Backend processing:

// 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 });
+  };
}
// ...

Front-end processing:

// 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="Enter a new store name" />
  <a href="#"Data-type=" modify ">confirm modification </a>
  <a href="#"Data-type=" remove "> delete shop </a>
  <div class="error"></div>
</li>`
  );
  document.querySelector('#root').innerHTML = `
<h1>Store list:</h1>
-<ul class="shop-list">${htmlItems.join('')}</ul>`;
+<ul class="shop-list">${htmlItems.join('')}</ul>
+<h1>Store additions:</h1>
+<form method="post" action="/api/shop">
+  <label>Name of the new store:</label>
+  <input type="text" name="name" />
+  <button type="submit" data-type="create">Confirm new</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();
+}

Take a look at the new effects of the store:

Source code of this chapter

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

read more

Building a Node.js Enterprise Web Server from Scratch (Zero): Static Serving
Building a Node.js enterprise-level Web server from scratch (1): interface and layering
Building a Node.js enterprise-level Web server from scratch (2): verification
Building a Node.js enterprise-level Web server from scratch (3): middleware

Tags: Javascript node.js Front-end Back-end server

Posted by bbreslauer on Fri, 01 Jul 2022 21:34:39 +0300