Using Koa2 to build mockserver

preface

This article is mainly for the front-end development engineers to explain how to use Node.js Framework Koa2 and mockjs build a set of data simulation services to provide simulation data for front-end services. Simple operation can greatly improve development efficiency.

Problems encountered in actual combat

  1. Scene 1: schoolmate A is happily writing CSS, and suddenly the interface is all 404. When he asks schoolmate B, he finds that schoolmate C secretly deploys the code of back-end branch C to the development environment. The branch A code of the original development environment is covered. Only the code of back-end branch A (small B) can provide the docking interface for students of small A.
  2. Scenario 2: little A wrote JS logic happily according to the data returned by little B. suddenly, the interface reported 500 and told the internal exception, and returned it to the back-end little B. little B calmly replied, "let me have A look.". The logical thinking just now was interrupted by the 500.
  3. Scene 3: little A gets A list rendering interface and returns everything is normal, just "list: []". So little A asked little B to add some data to the interface. Little B saw that each record had to add more than 30 data. There were more than 10 interfaces and more than 10 bug s that had not been changed. The most important thing was that it was only 5 minutes before 6:00. So little B replied to an expression pack




Little A has no choice but to send the expression bag below to solve the problem

All the above scenarios can be solved through mockserver. Let's get straight to the point.

Server introduction

The Server is built with Koa2. First look at the project structure

|-- app
    |-- controller        // controller
    |-- route             // route
    |-- validation        // Parameter verification with joi
|-- config
    |-- config.js         // Project configuration
|-- logs                  // journal
|-- middleware            // middleware 
|-- util                  // Public tools library
|-- app.js                // Project start up file
|-- package.json          // node project file description

Then let's pick a few key points

const Koa = require('koa');
const app = new Koa();
const config = require('./config/config.js');
var cors = require('koa-cors');
app.use(cors());


/**
 * Add message resolution Middleware
 */
const bodyParser = require('koa-bodyparser');
const xmlParser = require('koa-xml-body');
app.use(xmlParser());
app.use(bodyParser());

/**
 * Middleware for processing input message
 */
app.use(require('./middleware/input.js'));

/**
 * Handle header Middleware
 */
app.use(require('./middleware/header.js'));

/**
 * Request callback Processing Middleware
 */
app.use(require('./middleware/requestError.js'));

/**
 * Load route
 * Route configuration in config/config.js In routes array
 */
let routes = config.routes;
for (let key in routes) {
    if (routes.hasOwnProperty(key)) {
        let element = routes[key];
        let router = require(element)();
        app.use(router.routes());
    }
}


app.listen(config.port);
module.exports = app;

Koa2 introduces itself as an onion model. It uses context to package Request parameters, processes data through a layer of middleware, and then returns response data. Let me introduce the focus of this framework.

route

Koa router middleware is used in routing. An example folder is created in the route folder, corresponding to a front-end project. And then config.js Plus configuration. In this way, multiple projects can be managed in the form of folders.

// config/config.js
/**
 * Apply route configuration to load
 */
"routes": [
    './app/route/example/index.js'
]

// app.js
/**
 * Load route
 * Route configuration in config/config.js In routes array
 */
let routes = config.routes;
for (let key in routes) {
    if (routes.hasOwnProperty(key)) {
        let element = routes[key];
        let router = require(element)();
        app.use(router.routes());
    }
}

When we need to add a corresponding mock interface, the following code

// app/route/example/index.js
const Router = require('koa-router');
const activityController = require('../../controller/example/activity.js');

module.exports = function() {
    let router = new Router();

    // Get record list
    router.get('/activity-parcel-service/parcel/lottery/winners/red', activityController.getResultList);
}

router's methods follow the restful api, mainly including the following
get, post, put, delete
router takes two parameters: path (url) and multiple Middleware

activityController is a controller. getDefaultActivity is a method in the controller, and it is also a middleware. The controller will be mentioned later

controller

I created a new example project in the controller folder. In this project, I created a new project named activity.js In this file, a method named getResultList is written.

const response = require('../../../util/response.js');
const validator = require('../../../util/requestValidator.js');
const activityValidation = require('../../validation/example/activity.js');
const Mock = require('mockjs');

module.exports = {
    /**
    * @methods Get results page
    * @param {Object} ctx koa context
    **/
    getResultList: async function(ctx, next) {
        // Judge whether the interface has an id
        await validator.validate(
            ctx.input,
            activityValidation.getResultList.schema,
            activityValidation.getResultList.options
        )
        const Random = Mock.Random;
        Random.word()
        Random.name()
        let data = Mock.mock({
            'describeList|1-5': ['@word'],
            'parcelRecordRedWinnerVoList|1-10': [{
                'avatarUrl': Random.image('100x100'), // If you want to specify a size for image
                'bonusAmount|1-2.1-2': 1,
                'nickname': '@name'
            }]
        })
        return response.map(ctx, data);
    }
}

As you can see from the code, we reference mockjs to the controller layer. After accessing the MockServer interface, first go to the controller layer through routing, and then process the logic return information.

The usage of mockjs is explained below.

The main purpose of the validator is to verify the parameters brought by the front-end request, because the interface itself needs to pass a parameter "id", and then a layer of processing is done through the validator. If the id is not legal, an exception will be thrown, indicating the following information.

<img src='https://images.yatsenglobal.com/uploads/20200610/339204068389621.png' />

Global exception capture

The reason why parameter errors can pop up above is that there is a middleware for global exception capture

/**
 * Request callback Processing Middleware
 */
app.use(require('./middleware/requestError.js'));
const response = require('../util/response.js');

module.exports = async function (ctx, next) {
  try {
    await next();
  } catch(e) {
    ctx.status = status;
    let errCode = e.code || 1;
    let msg = e.message || e;
    if (!(parseInt(errCode) > 0)) {
        errCode = 1;
    }
    return response.output(ctx, {}, errCode, msg, status);
  }
}

When we throw an exception, it will be caught and processed by the middleware, and the error information we need to throw will be returned to the front end.

Mockjs

Official website address: http://mockjs.com

advantage

Directly intercept the official website map
<img src='https://images.yatsenglobal.com/uploads/20200609/339202893984459.png' />

usage

Because of laziness, here I only talk about a few daily uses. For details, please see the official website

character string

'name|min-max': string

Mock.mock({
  "string|1-10": "str"
})
// "str" will be copied 1-10 times
{
  string: "strstrstr"
}

number

'name|min-max': number

Mock.mock({
  "number|1-10": 1
})
// Returns the random number of objects 1-10
{
  number: 9
}

picture

Random.image( size?, background?, foreground?, format?, text? )

Size: image size, such as "100x100"
Background: background color
 foreground: font color
 Format: format png|jpg
 text: text

object

'name|min-max': object

Mock.mock({
  "data|2-4": {
    "id": 1,
    "id": 2,
    "id": 3,
    "id": 4
  }
})
// Return 2-4 defined elements in the object
{
  id: 1,
  id: 2
}

array

'name|min-max': array

Mock.mock({
  "array|1-4": [
    {
      "name|+1": [
        "Hello",
        "Mock.js",
        "!"
      ]
    }
  ]
})
// Return 1-4 elements defined in the array
{
  "array": [
    {
      "name": "Hello"
    },
    {
      "name": "Mock.js"
    }
  ]
}

The above briefly introduces several types of mockjs, and then explains how to use mockserver.

How to use in a project

mockserver start

Step 1: download the project mockserver

https://github.com/FEA-Dven/m...

Step 2: install pm2 globally. pm2 is mainly used to manage node processes

cnpm install pm2 -g or npm install pm2 -g

Step 3:

cd mockserver & npm install

Step 4:
Add the URL in the routing file. The URL is the URL used in the actual project. Here I use the URL of the red packet project

const Router = require('koa-router');
const activityController = require('../controller/example/activity.js');

module.exports = function() {
    let router = new Router();
    // Get default activity configuration
    router.get('/activity-parcel-service/parcel/lottery/getDefaultActivityId', activityController.getDefaultActivity);
}

Step 5:
Add a file in the controller, and each file corresponds to the front-end project. Here I use example instead. I create a new example folder in the controller, and add a activity.js Documents of

const response = require('../../../util/response.js');
const Mock = require('mockjs');

module.exports = {
    /**
    * @methods Get default activity
    */
    getDefaultActivity: async function(ctx, next) {
        // Parameters passed by the front end can be verified
        Mock.Random.id();
        let data = Mock.mock({
            'dataStatus|0-1': 0,
            'id': '@id',
            'styleType': 1
        })
        return response.map(ctx, data);
    }
}

Step 6:
Return to the root directory and use pm2 to turn on listening mode

pm2 start app.js --watch

pm2 usage

  • Open pm2 process list

pm2 list

  • View service log

PM2 logs 0 / / 0 represents the process ID

Using postman to request our URL will return MOCK data
<img src='https://images.yatsenglobal.com/uploads/20200610/339203858674326.png' />

Now that the service is set up, it needs to be connected with the front-end service

Apply to daily development

Let's take a look at our original encapsulation request function

// utils.request.js
/**
 * @function Wechat request method encapsulation
 * @param {string} method Request type
 * @param {string} host Domain name address
 * @param {string||array} url Interface address
 * @param {object||array} data Parameter data
 * @param {boolean} showModal Display error pop-up
 * @return {object} Requested data
 */
export const requestFn = ({
  method = 'GET',
  host = wx.$CONFIG.API_URL,
  url = '',
  data = {},
  token = true,
  header = {
    'content-type': 'application/json'
  },
  showLoading = false, // Chrysanthemum or not
  showModal = false // Pop up or not
}) => {
  return new Promise((resolve, reject) => {
    // Show loading animation
    if (showLoading) wx.showLoading();
    // token verification
    if (token) {
      let _token = wx.$USER.getToken();
      if (_token) {
        header[wx.$CONFIG.TOKEN_CACHE] = _token
        
      } else {
        return;
      }
    }

    // Start request
    wx.request({
      method: method.toUpperCase(),
      url: host + url,  // Global variable host
      header,
      data,
      success: res=> {
        resolve(res.data);
      },
      fail: err => {}
    })
  })
}

We see that there is a variable host in it. As long as you change the variable host to point to our local mock domain name, you can finish work.

Of course, I lied to you

In general, small and medium-sized projects, there will be dozens of interfaces, thousands of large-scale interfaces. If the host is directly changed to our Mock domain name, wouldn't our mockserver route also need to write hundreds of routing interfaces? This is an anti human operation. So we need to optimize the changes

Front end requests encapsulation changes

When requesting a service interface, we all have an api file. First, look at the original code

/**
 * Get default activity id
 */
function getDefaultActivityId (data = {}) {
  return requestFn ({
    url: '/activity-parcel-service/parcel/lottery/getDefaultActivityId',
    method: 'get',
    data,
    showLoading: false,
    showModal: false
  })
}

Here we need to add a parameter to tell the encapsulation request file that the api needs to mock data, so we add a "isMock" variable

/**
 * Get default activity id
 */
function getDefaultActivityId (data = {}, isMock) {
  return requestFn ({
    url: '/activity-parcel-service/parcel/lottery/getDefaultActivityId',
    method: 'get',
    data,
    showLoading: false,
    showModal: false,
    isMock
  })
}

Wechat request method encapsulation file should also be changed

// utils.request.js
// Head definition MOCK_HOST
const MOCK_HOST = 'http://10.154.68.180:8080';
/**
 * @function Wechat request method encapsulation
 * @param {string} method Request type
 * @param {string} host Domain name address
 * @param {string||array} url Interface address
 * @param {object||array} data Parameter data
 * @param {boolean} showModal Display error pop-up
 * @return {object} Requested data
 */
export const requestFn = ({
  method = 'GET',
  host = wx.$CONFIG.API_URL,
  url = '',
  data = {},
  token = true,
  header = {
    'content-type': 'application/json'
  },
  showLoading = false, // Chrysanthemum or not
  showModal = false, // Pop up or not
  isMock
}) => {
  return new Promise((resolve, reject) => {
    // Show loading animation
    if (showLoading) wx.showLoading();
    // token verification
    if (token) {
      let _token = wx.$USER.getToken();
      if (_token) {
        header[wx.$CONFIG.TOKEN_CACHE] = _token
        
      } else {
        return;
      }
    }
    // Judge whether it is necessary to turn on the analog parameter form to return data
    host = `${isMock ? MOCK_HOST : host}`;
    // Start request
    wx.request({
      method: method.toUpperCase(),
      url: host + url,
      header,
      data,
      success: res=> {
        resolve(res.data);
      },
      fail: err => {}
    })
  })
}

When calling the API, the parameter isMock is passed as true

wx.$API.getDefaultActivityId({ ...otherParams, isMock: true })

Look at the effect:
<img src='https://images.yatsenglobal.com/uploads/20200610/344271727146515.png' />

Comparison of other schemes

YApi

YApi, as a powerful API document management tool, can also use mock data naturally. If you want to use YApi for mock data, the simplest method is intranet deployment

The address is as follows

https://hellosean1025.github....

In my opinion, if you only need to use mock, YApi is too large and cumbersome, and you need to rely on mongodb environment, as well as account login. If you use your own built mockserver, you only need nodejs installation dependency to run.

Other students have better plans welcome to exchange

malpractice

After all, there are still some disadvantages of mockserver. Let's summarize.

  1. Need to wait for back-end students to provide return data fields and types
  2. I need to know nodejs to some extent, but I believe that all of you are good students who love learning.

summary

The front-end engineer builds a set of mockserver by himself. After defining the return data structure, he can develop in parallel with the back-end, and simulate the whole process by himself. In the process, he can find and solve problems in advance, complete the front-end and back-end joint debugging faster, and speed up the measurement and online.

Tags: Javascript mockjs JSON github npm

Posted on Thu, 11 Jun 2020 04:36:04 -0400 by mesh2005