preface

This article will introduce some of the project construction in KOA’s project practice and some of the solutions. I will introduce the basic knowledge of KOA to better enter the practice of KOA project. Let’s take a look.

Front knowledge

To use KOA in a project, it is necessary to first understand the operation mechanism of KOA and master the implementation principle of middleware. Koa is based on the Onion model. So let’s look at KOA’s onion model.

Koa’s onion model

Onions we all know that one layer is layered on top of another, but instead of looking at the three-dimensional structure, we need to cut the onion, from the point of view of the cut plane:

You can see that in order to go through the center of the onion, you have to go layer by layer through the skin of the onion into the center, and then layer by layer through the skin of the onion from the center.

Here is a feature: as many layers of skin as you enter, you must exit. First through the skin, then through the skin, in line with what we call the stack list, first in, last out principle. And that’s how our Onion model works.

Then back to the Node.js framework, the skin of the onion can be thought of as middleware:

  • Outside-in process is a key word next();
  • And from the inside out is the completion of each middleware, into the next layer of middleware, until the last layer.

Koa’s middleware runs based on the Onion model.

Middleware execution

To understand the onion model and its implementation, let’s use KOA as a framework example to implement a back-end service. There is some preparatory work to be done here, and you can initialize the project by following these steps.

mkdir myapp
cd myapp
npm init
npm install koa --save
touch app.js
Copy the code

The following code, in which the app.use section is four middleware. One of them is asynchronous middleware.

const Koa = require("koa");
const app = new Koa();

/** * middleware 1 */
app.use(async (ctx, next) => {
  console.log("first");
  await next();
  console.log("first end");
});

/** * Middleware 2 */
app.use(async (ctx, next) => {
  console.log("second");
  await next();
  console.log("second end");
});

/** * asynchronous middleware */
app.use(async (ctx, next) => {
  console.log("async");
  await next();
  await new Promise((resolve) = >
    setTimeout(() = > {
      console.log(`wait 1000 ms end`);
      resolve();
    }, 1000));console.log("async end");
});

/** * Middleware 2 */
app.use(async (ctx, next) => {
  console.log("third");
  await next();
  console.log("third end");
});

app.use(async (ctx) => {
  ctx.body = "Hello World";
});

app.listen(3000.() = > console.log(`Example app listening on port 3000! `));
Copy the code

Next we run the following command to start the project.

node app.js
Copy the code

After successful startup, open your browser and enter the following browser address:

http://127.0.0.1:3000/
Copy the code

Then in the command line window, you can see the following printed message:

Example app listening on port 3000!
first
second
async
third
third end
wait 1000 ms end
async end
second end
first end
Copy the code

You’ll notice that KOA follows the onion model exactly from top to bottom, printing first, second, async, and third from inside the onion to outside. Next, print third end, async end, Second end, first end from inside out.

Set up the project

Ok, with the basic principles finished, we will enter the link of building the project. We will introduce the necessary middleware application and process management for koA project construction from the aspects of unified middleware management ==> route management ==> parameter parsing ==> unified data format ==> global capture error handling ==> cross-domain processing ==> log recording ==> request parameter verification, etc.

First, create the project file directory:

├─ Server // Code Folder ├─ Common // Public Resources ├─ Config // Environment Configuration ├─ Middlewares // Middleware Set ├─ Router // Route ├─ schema // Parameter Validation ├─ utils // Toolset ├─ package.json // Package Configuration ├─ app.js // Run fileCopy the code

Note that in the following example code require(‘ XXX ‘), the XXX NPM package does not write NPM references (NPM install XXX –save). Here is a brief explanation.

In the server/app.js file, write the basic startup service code:

const Koa = require("koa");
const app = new Koa();

app.use(async ctx => {
  ctx.body = 'Hello World';
});

const PORT = 3000;
app.listen(PORT, () = > {
  console.log('Started successfully,The ${"http://127.0.0.1"}:${PORT}`);
});
Copy the code

Middleware merge

First, we used KoA-compose to centralize all middleware processing and make it easy to access.

In server/middlewares/index. Js file middleware under concentrated processing, the output list middleware

module.exports = [
  ...,
];
Copy the code

Then use it in the server/app.js file.

const compose = require("koa-compose");
const middlewares = require("./server/middlewares");
app.use(compose(middlewares));
Copy the code

Routing management

Use @koa/ Router to manage routes. Here we need to separate the routes and the corresponding processing functions of each route. Do not write them together to avoid difficult maintenance.

Create the following file:

  • server/router/routes.jsRouting list file;
  • server/router/index.jsRoute processing export file;
  • server/contronllers/index.jsUnified business processing;
  • server/controllers/home.jsBusiness processing documents;

Server/Contronllers folder to unify the code logic for business processing. The following server/controllers/home. Js is business processing files:

const home = async (ctx) => {
  ctx.body = "hello world";
};

module.exports = { home };
Copy the code

Export business processing files in server/ Contronllers folder:

const homeRouter = require("./home");
module.exports = {
  homeRouter,
};
Copy the code

In the server/router/routes.js file, the service route list is processed in a unified manner.

const { homeRouter } = require(".. /controllers");
const routes = [
  {
    method: "get".path: "/".controller: homeRouter.home,
  },
];

module.exports = routes;
Copy the code

In the server/router/index.js file, loop the service route list and export the route processing file.

const router = require("@koa/router") ();const routeList = require("./routes");

routeList.forEach((item) = > {
  const { method, path, controller } = item;
  router[method](path, controller);
});

module.exports = router;
Copy the code

Argument parsing

Processing of request packets. Use koA-BodyParser to parse. Server/middlewares/index. Js file write:

const koaBodyParser = require("koa-bodyparser");
const myKoaBodyParser = koaBodyParser();
module.exports = [
  ...,
  myKoaBodyParser,
]
Copy the code

Unified return data format

Do you have the problem that every time you return data, you write the following repeat logic:

 ctx.body = {
        code,
        data,
        msg: msg || "fail"};Copy the code

Here, we use an intermediary processing unified return data format, in the server/middlewares/response. The js file write:

const response = () = > {
  return async (ctx, next) => {
    ctx.res.fail = ({ code, data, msg }) = > {
      ctx.body = {
        code,
        data,
        msg: msg || "fail"}; }; ctx.res.success =(msg) = > {
      ctx.body = {
        code: 0.data: ctx.body,
        msg: msg || "success"}; };await next();
  };
};

module.exports = response;
Copy the code

Server/middlewares/index. Js file write:

const response = require("./response");
const myResHandler = response();
module.exports = [
  ...,
  myResHandler,
]
Copy the code

I wrote the above code. We also need the error-handling middleware to trigger the uniform return data Format middleware. And then down.

Error handling

Based on the unified return data format processing, the onion model is used to build a middleware, global unified error processing. Server/middlewares/error. Js file write:

const error = () = > {
  return async (ctx, next) => {
    try {
      await next();
      if (ctx.status === 200) { ctx.res.success(); }}catch (err) {
      if (err.code) {
        // Error thrown by oneself
        ctx.res.fail({ code: err.code, msg: err.message });
      } else {
        // An error occurred while the program was running
        ctx.app.emit("error", err, ctx); }}}; };module.exports = error;
Copy the code

Server/middlewares/index. Js file write:

const error = require("./error");
const myErrorHandler = error();
module.exports = [
  ...,
  myErrorHandler,
]
Copy the code

Combined with the error-handling middleware to trigger the uniform return data format middleware, we simply write the following code and the middleware can automatically supplement the return data format.

 ctx.body = data
Copy the code

Cross domain processing

Use @KOA/CORS to handle cross-domain issues. Server/middlewares/index. Js file write:

const koaCors = require("@koa/cors");
const myKoaCors = koaCors({
  origin: "*".credentials: true.allowMethods: ["GET"."HEAD"."PUT"."POST"."DELETE"."PATCH"]});module.exports = [
 ...,
  myKoaCors,
]
Copy the code

logging

Use the KOA-Logger to handle simple logging problems. Server/middlewares/index. Js file write:

const logger = require("koa-logger");
const myLogger = logger();
module.exports = [
  ...,
  myLogger,
]
Copy the code

Request parameter verification

Some solutions in actual projects

File Uploading Scheme

const koaBody = require('koa-body');   // File upload
app.use(koaBody({
    multipart: true.formidable: {
        maxFileSize: config.LIMIT.UPLOAD_IMG_SIZE    // Set the maximum size of a file to be uploaded. The default size is 2 MB
    }
Copy the code

Koa2 sets global variables

Using the app. The context [XXX].

const Koa =  require('koa');
const app = new Koa();

// Functions as app.locals = {// XXX} in Express
app.context.state = Object.assign(app.context.state, {key1 : value1, key2: value2});
Copy the code

A method that requests an external interface

  1. One is node native Request. The request has been abandoned and is not updated.
  2. Koa2-request middleware, based on request to do a package, internal source code reference.
  3. Bent HTTP client with async/await node.js and browser functionality.
  4. Got is a friendly and powerful HTTP request library for Node.js.
  5. More references:

Image from alternative library list.

Proposal:

As a Node.js developer who has been using Request for a while, Bent is definitely an easy transition – 💖 is highly recommended

Concurrent request limit

Solution:

  • The promise.race-based feature works with promise.all to limit the number of parallel requests and keep the number of requests.
  • Based on the first-in-first-out (FIFO) feature of the queue, the asynchronous microtask queue is constructed with Promise to limit the number of parallel requests and keep the number of requests.

Specific reference:

  • Js asynchronous concurrency control, limit the number of requests to resolve confusion.

Multi-process solution

Using PM2 is the daemon manager;

CPU overload protection design

To be added.