Build from scratch Node.js Enterprise web server (1): interface and layering

Time:2021-3-4

Layered specification

Starting from this chapter, we will enter the core content of enterprise web server. Generally, a complete business logic is defined and implemented by view layer, control layer, service layer and model layer, as shown in the following figure:

Build from scratch Node.js  Enterprise web server (1): interface and layering

From top to bottom, the level of abstraction gradually deepens. From the bottom up, business details are gradually clear. The view layer belongs to the web front-end content, this paper uses theJavaScript ModulesMake a demonstration.

This chapter focuses on the control layer and service layer, and expands the core part of business logic.

Write a simple version of shop management

Directly from the completed projects in the previous chapterhost1-tech/nodejs-server-examples – 00-staticFirst, write the service layer content

$MKDIR Src / services # create a new Src / services directory to store the service layer logic

$ tree -L 2 -I node_ Modules # display except node_ Directory content structure outside modules
.
├── Dockerfile
├── package.json
├── public
│   └── index.html
├── src
│   ├── server.js
│   └── services
└── yarn.lock
// src/services/shop.js

//Store data
const memoryStorage = {
  '1001': {Name: 'liangpin shop'},
  '1002': {Name: 'laiyifen'},
  '1003': {Name: 'three squirrels'},
  '1004': {Name: 'baicaowei'},
};

//Analog delay
async function delay(ms = 200) {
  await new Promise((r) => setTimeout(r, ms));
}

class ShopService {
  async init() {
    await delay();
  }

  async find({ id, pageIndex = 0, pageSize = 10 }) {
    await delay();

    if (id) {
      return [memoryStorage[id]].filter(Boolean);
    }

    return Object.keys(memoryStorage)
      .slice(pageIndex * pageSize, (pageIndex + 1) * pageSize)
      .map((id) => ({ id, ...memoryStorage[id] }));
  }

  async modify({ id, values }) {
    await delay();

    const target = memoryStorage[id];

    if (!target) {
      return null;
    }

    return Object.assign(target, values);
  }

  async remove({ id }) {
    await delay();

    const target = memoryStorage[id];

    if (!target) {
      return false;
    }

    return delete memoryStorage[id];
  }
}

//Singleton mode
let service;
module.exports = async function () {
  if (!service) {
    service = new ShopService();
    await service.init();
  }
  return service;
};

The above service layer provides the basic business logic for store management. The storage is temporarily simulated by memory and delay, and now the restful interface is exposed through the control layer

$MKDIR Src / controllers # create a new Src / controllers directory to store the control layer logic

$ tree -L 2 -I node_ Modules # display except node_ Directory content structure outside modules
.
├── Dockerfile
├── package.json
├── public
│   └── index.html
├── src
│   ├── controllers
│   ├── server.js
│   └── services
└── yarn.lock
// src/controllers/shop.js
const { Router } = require('express');
const shopService = require('../services/shop');

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);
    return router;
  }

  getAll = async (req, res) => {
    const { pageIndex, pageSize } = req.query;
    const shopList = await this.shopService.find({ pageIndex, pageSize });

    res.send({ success: true, data: shopList });
  };

  getOne = async (req, res) => {
    const { shopId } = req.params;
    const shopList = await this.shopService.find({ id: shopId });

    if (shopList.length) {
      res.send({ success: true, data: shopList[0] });
    } else {
      res.status(404).send({ success: false, data: null });
    }
  };

  put = async (req, res) => {
    const { shopId } = req.params;
    const { name } = req.query;
    const shopInfo = await this.shopService.modify({
      id: shopId,
      values: { name },
    });

    if (shopInfo) {
      res.send({ success: true, data: shopInfo });
    } else {
      res.status(404).send({ success: false, data: null });
    }
  };

  delete = async (req, res) => {
    const { shopId } = req.params;
    const success = await this.shopService.remove({ id: shopId });

    if (!success) {
      res.status(404);
    }
    res.send({ success });
  };
}

module.exports = async () => {
  const c = new ShopController();
  return await c.init();
};
// src/controllers/index.js
const { Router } = require('express');
const shopController = require('./shop');

module.exports = async function initControllers() {
  const router = Router();
  router.use('/api/shop', await shopController());
  return router;
};
// src/server.js
const express = require('express');
const { resolve } = require('path');
const { promisify } = require('util');
+const initControllers = require('./controllers');

const server = express();
const port = parseInt(process.env.PORT || '9000');
const publicDir = resolve('public');

async function bootstrap() {
  server.use(express.static(publicDir));
+  server.use(await initControllers());
  await promisify(server.listen.bind(server, port))();
  console.log(`> Started on port ${port}`);
}

bootstrap();

Now useyarn startStart the application and access the interface directly through the browserhttp://localhost:9000/api/shopAndhttp://localhost:9000/api/shop/1001

Add a store management interface

withJavaScript ModulesWrite a store management interface for demonstration only (recommended in actual production)ReactorVue), callGETPUTDELETEInterface to query, modify and delete store information

<!-- public/index.html -->
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
-    <h1>It works!</h1>
+    <div id="root"></div>
+
+    <script type="module">
+      import { refreshShopList, bindShopInfoEvents } from './index.js';
+
+      async function bootstrap() {
+        await refreshShopList();
+        await bindShopInfoEvents();
+      }
+
+      bootstrap();
+    </script>
  </body>
</html>
// 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 new store name" / >
  To confirm the modification</a>
  To delete a store</a>
</li>`
  );
  document.querySelector('#root').innerHTML = `
<h1>Shop list:</h1>
<ul class="shop-list">${htmlItems.join('')}</ul>`;
}

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;
    }
  });
}

export async function modifyShopInfo(e) {
  const shopId = e.target.parentElement.dataset.shopId;
  const name = e.target.parentElement.querySelector('input').value;
  await fetch(`/api/shop/${shopId}?name=${encodeURIComponent(name)}`, {
    method: 'PUT',
  });
  await refreshShopList();
}

export async function removeShopInfo(e) {
  const shopId = e.target.parentElement.dataset.shopId;
  const res = await fetch(`/api/shop/${shopId}`, { method: 'DELETE' });
  await refreshShopList();
}

visithttp://localhost:9000/You can experience the store management function:

Build from scratch Node.js  Enterprise web server (1): interface and layering

Source code of this chapter

host1-tech/nodejs-server-examples – 01-api-and-layering

Read more

Build from scratch Node.js Enterprise web server (zero): static services
Build from scratch Node.js Enterprise web server (1): interface and layering
Build from scratch Node.js Enterprise web server (2): Verification
Build from scratch Node.js Enterprise web server (3): Middleware
Build from scratch Node.js Enterprise web server (4): exception handling
Build from scratch Node.js Enterprise web server (5): database access
Build from scratch Node.js Enterprise web server (6): session
Build from scratch Node.js Enterprise web server (7): authentication login
Build from scratch Node.js Enterprise web server (8): network security
Build from scratch Node.js Enterprise web server (9): configuration items
Build from scratch Node.js Enterprise web server (x): log
Build from scratch Node.js Enterprise web server (11): timed tasks
Build from scratch Node.js Enterprise web server (12): remote call
Build from scratch Node.js Enterprise web server (XIII): breakpoint debugging and performance analysis
Build from scratch Node.js Enterprise web server (14): automated testing
Build from scratch Node.js Enterprise web server (XV): summary and Prospect