preface

The company is planning to build node intermediate layer recently, and the solutions that can be considered are Express Koa Nest egg. Both Express and KOA are too light to be used by enterprises. There are too few Chinese documents of Nest, and it is not easy to find relevant solutions if there are problems during development. So the final alternative is eggJs. Eggjs is an enterprise-class Node framework produced by Ali. It follows convention rather than configuration, and all development is based on convention to reduce team communication costs. Another important point is that Egg has a complete log system, which is very convenient for bug location. This article documents some of the problems encountered in using EGGJS development.

How do I get the parameters passed in from the front end

NPM init egg –type=simple NPM init egg –type=simple

├ ─ ─ app │ ├ ─ ─ controller │ │ └ ─ ─ home. Js │ └ ─ ─ the router, jsCopy the code

The code to open router.js is as follows:

module.exports = app= > {
  const { router, controller } = app;
  router.get('/', controller.home.index);
};
Copy the code

As you can probably guess, the index method of the home.js file in the Controller folder is invoked when accessing the/interface, and the/interface is a GET request.

Open the home.js file as follows:

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    ctx.body = 'hi, egg'; }}module.exports = HomeController;
Copy the code

As you can see, the index method deconstructs a CTX from this, which is the context of the request, assigns a hi, egg to ctx.body, and runs the NPM run dev project. Visit http://localhost:7001 and you can see that the page displays he, egg, so ctx.body is the information returned to the client. The parameters of the GET request can be obtained through ctx.query. The parameters of the POST request need to do something else, which we’ll talk about later. There is also a path argument, such as /1. The path argument for this 1 can be ctx.params.

A get request

Use the API: ctx.query

When we visit http://localhost:7001? Id =101, we can print out the parameters in Controller

Official documentation: eggjs.org/zh-cn/basic…

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    const {query} = ctx;
    console.log(query);
    ctx.body = 'hi, egg'; }}module.exports = HomeController;
Copy the code

In the console we can see that the output is an object with id 123

Console output
Copy the code

Get path parameters

Use the API: ctx.params

For path parameters, we need to add the corresponding definition when defining the route

// app/router.js
module.exports = app= > {
  const { router, controller } = app;
  router.get('/:id', controller.home.index);
};
Copy the code

And then when we visit http://localhost:7001/123, by CTX. Params will into print.

Official documentation: eggjs.org/zh-cn/basic…

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    const {params} = ctx;
    console.log(params);
    ctx.body = 'hi, egg'; }}module.exports = HomeController;
Copy the code

You can see the output on the console when you access it, no more textures

POST request error

When we use the GET request, everything is fine, but when we define a POST request, we find that the console reported a CSRF error with the following error:

Console error content2020-04-07 17:31:24,844 WARN 10854 [-/::1/-/48ms POST/POST] CSRF token.see https://eggjs.org/zh-cn/core/security.html# Security threat CSRF prevention2020-04-07 17:31:24,855 WARN 10854 [-/::1/-/55ms POST/POST] nodejs.forbiddenError: missing csrf token at Object.throw (/Users/mac/Documents/newWork/node-Middleware/node_modules/koa/lib/context.js:97:11) at Object.assertCsrf (/Users/mac/Documents/newWork/node-Middleware/node_modules/egg-security/app/extend/context.js:153:19) at csrf (/Users/mac/Documents/newWork/node-Middleware/node_modules/egg-security/lib/middlewares/csrf.js:33:9) at dispatch (/Users/mac/Documents/newWork/node-Middleware/node_modules/egg-security/node_modules/koa-compose/index.js:42:32) at /Users/mac/Documents/newWork/node-Middleware/node_modules/egg-security/node_modules/koa-compose/index.js:34:12 at dispatch (/Users/mac/Documents/newWork/node-Middleware/node_modules/koa/node_modules/koa-compose/index.js:42:32) at session (/Users/mac/Documents/newWork/node-Middleware/node_modules/koa-session/index.js:41:13) at dispatch (/Users/mac/Documents/newWork/node-Middleware/node_modules/koa/node_modules/koa-compose/index.js:42:32) at overrideMethod (/Users/mac/Documents/newWork/node-Middleware/node_modules/koa-override/index.js:38:12) at dispatch (/Users/mac/Documents/newWork/node-Middleware/node_modules/koa/node_modules/koa-compose/index.js:42:32) message:"missing csrf token"
pid: 10854
Copy the code

This error occurs because the official built-in egg-Security plugin performs CSRF check on all “insecure” methods, such as POST, PUT and DELETE, by default. When we request, the server will send a token through the Cookie, requiring us to initiate the request. Send the token to the server by placing it in a Query, body, or header.

However, in the actual development, our interface is not only used for Web. For non-browser clients, Cookie is not successfully issued. In addition, we prefer to implement token by ourselves rather than by the framework, so we need to close the CSRF authentication of egg-Security

After the project is created, there will be a config folder under the project, which contains the following files:

├─ config │ ├─ config.default.js │ ├─ ├.jsCopy the code

Config.default. js is the application configuration file, plugin.js is the plug-in configuration file, we need to close egg-security in config.default.js, open the config.default.js file, It reads as follows (with the comments removed) :

'use strict';
module.exports = appInfo= > {
  const config = exports = {};

  config.keys = appInfo.name + '_1586250824909_3576';

  config.middleware = [];

  const userConfig = {};

  return{... config, ... userConfig, }; };Copy the code

As you can see, this file exports a function that returns an object. Close egg-Security and configure the configuration in this object. Add the following:

'use strict';
module.exports = appInfo= > {
  const config = exports = {};

  config.keys = appInfo.name + '_1586250824909_3576';

  config.middleware = [];

  const userConfig = {};

  return{... config, ... userConfig,security: {
      csrf: {
        enable: false.// Turn off the framework default CSRF plugin,}}}; };Copy the code

After closing, restart the project.

Reference Documents:

Why -csrf- error

Official document: Security Threat – CSRF – Prevention

How do I get the parameters of a POST request

Use the API: ctx.request.body

The parameters of the POST request can also be printed in the Controller and are received with ctx.request.body

Let’s start by defining a POST request interface in router.js

// app/router.js
module.exports = app= > {
  const { router, controller } = app;
  router.post('/post', controller.home.post);
};
Copy the code

Then add a post method to home

// app/controller/home
const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    const {params} = ctx;
    console.log(params);
    ctx.body = 'hi, egg';
  }
  async post() {
    const { ctx } = this;
    const { request: { body } } = ctx;
    console.log(body);
    ctx.body = 'hi, post'; }}module.exports = HomeController;
Copy the code

Then use Postman to test:

The interface successfully returned “hi, post”, the console also successfully printed the entry parameter, the console I do not map.

How to send a request

Use API: ctx.curl

Being a middle tier means making a request to a real business service, taking the data, reorganizing it and returning it to the client. According to the directory convention of egg, there can also be a service layer under app directory, so our business content is written in service. Create a service folder under app, and then create a home.js file under service. The file structure under app is as follows:

├ ─ ─ app │ ├ ─ ─ controller │ │ └ ─ ─ home. Js │ ├ ─ ─ service │ │ └ ─ ─ home. Js │ └ ─ ─ the router, jsCopy the code

The code inside home.js is as follows:

const Service = require('egg').Service;

class HomeService extends Service {
  async post() {
    return 'service returned successfully '}}module.exports = HomeService;
Copy the code

Then call service in controller:

// app/controller/home
const Controller = require('egg').Controller;

class HomeController extends Controller {
  async post() {
    const { ctx } = this;
    const { request: { body } } = ctx;
    console.log(body);
    const res = await ctx.service.home.post(); / / call service
    console.log(res);
    ctx.body = 'hi, post'; }}module.exports = HomeController;
Copy the code

The console returns a successful output of service, indicating that the service call was successful, and then we can request the interface in the service

const Service = require('egg').Service;

class HomeService extends Service {
  async post() {
    const {data} = await this.ctx.curl('https://api.weixin.qq.com/cgi-bin/ticket/getticket', {data: {'Easy to lose'.type: 'jsapi'}, dataType: 'json'});
    return Promise.resolve(data); }}module.exports = HomeService;
Copy the code

The above code we requested a Tencent interface, the parameters are casually passed. Interface can be seen by the above requests that we use is CTX curl API, to the API and the two parameters, the first is the request address, there is a transfer of the second parameter and so on, we must be familiar with what is the second parameter can be passed, here I give the commonly used parameters, complete parameter can look at the official documentation

parameter type instructions
method String Request method, defaultGETsupportGET, POST, PUT, DELETE, and PATCH
data Object To participate,GET POSTAll methods pass arguments through this field
dataType String Response format; By default, buffer is returned directlytextjsonTwo formats.
contentType String Request data format, default isundefined, HttpClient will automatically base ondatacontentParameters are automatically set.dataThe default for object isform. supportjsonFormat.
headers Object Custom request headers

Reference Documents:

Official document: HttpClient

How to solve cross-domain (middleware solution)

To use it for the Web, there must be cross-domain, and egg also supports middleware, so here we will write a middleware, learn from the middleware of egg, and then solve a cross-domain.

The implementation of egg is based on KOA middleware. The form of egg middleware is actually a layer of KOA middleware package, so we first write koA cross-domain middleware:

const cors = async (ctx, next) => {
  // Since we set the response header when we return to the browser, we call next() first and set the header when we return
  await next()
  // Specify the source domain that the server side allows for cross-domain resource access. The wildcard * can be used to indicate that JavaScript from any domain is allowed to access the resource, but the specific domain must be specified when responding to an HTTP request carrying Credential information, and the wildcard cannot be used
  ctx.set("Access-Control-Allow-Origin"."*");
 
  // Specifies the list of request methods that the server allows to access resources across domains, typically in response to precheck requests
  ctx.set("Access-Control-Allow-Methods"."OPTIONS,POST,GET,HEAD,DELETE,PUT");
  
  / / necessary. This parameter specifies the list of request headers that the server allows to access resources across domains. This parameter is generally used in response to precheck requests. The client needs to carry a token in the header when requesting the interface, so you need to set this parameter to allow
  ctx.set("Access-Control-Allow-Headers"."x-requested-with, accept, origin, content-type, token");
  
  // Tell the client the MIME type of the returned data. This is just an identifier, not part of the actual data file
  ctx.set("Content-Type"."application/json; charset=utf-8");
  
  // This parameter is optional, in seconds. The browser does not need to send the precheck request for negotiation. When the request method is a special method such as PUT or DELETE or the content-Type field is of application/ JSON, the server sends a request in advance for validation
  ctx.set("Access-Control-Max-Age".300);

  / / is optional. Its value is a Boolean that indicates whether clients are allowed to carry identity information (cookies or HTTP authentication information) with cross-domain requests. By default, cookies are not included in CORS requests. If cookies are allowed for requests, ensure that "access-Control-allow-origin" is the domain name of the server, not "*". If this value is not set, the browser ignores the response.
  ctx.set("Access-Control-Allow-Credentials".true);

  / / is optional. For cross-domain requests, the client XHR object's getResponseHeader() method only gets six basic fields: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, and Pragma. To get other fields, using access-Control-expose-headers, xhr.getresponseHeader ('myData') returns the values we want
  ctx.set("Access-Control-Expose-Headers"."myData");
  if (ctx.request.method === "OPTIONS") {
    ctx.response.status = 204}}Copy the code

The egg middleware needs a function that returns the KOA middleware. This function also takes two parameters, a plug-in configuration options and an application instance app, so modify the code above:

const cors = async (ctx, next) => {
  await next()
  ctx.set("Access-Control-Allow-Origin"."*");

  ctx.set("Access-Control-Allow-Methods"."OPTIONS,POST,GET,HEAD,DELETE,PUT");
  
  ctx.set("Access-Control-Allow-Headers"."x-requested-with, accept, origin, content-type, token");
  
  ctx.set("Content-Type"."application/json; charset=utf-8");
  
  ctx.set("Access-Control-Max-Age".300);

  ctx.set("Access-Control-Allow-Credentials".true);

  ctx.set("Access-Control-Expose-Headers"."myData");
  if (ctx.request.method === "OPTIONS") {
    ctx.response.status = 204}}// This is the function of the egg middleware package
module.exports = (options,app) = > {
  return cors;
}
Copy the code

An egg middleware we finished, then I have to go to mount to the application, the configuration of middleware in the/config/config. Default. In js:

'use strict';
module.exports = appInfo= > {
  const config = exports = {};

  config.keys = appInfo.name + '_1586250824909_3576';

  config.middleware = [];

  const userConfig = {};

  return{... config, ... userConfig,security: {
      csrf: {
        enable: false.// Turn off the framework default CSRF plugin}},// Configure the required middleware. The array order is the loading order of the middleware
    middleware: [ 'cors'].cors: {},// Middleware configuration
  };
};
Copy the code

How do I output interface documents

After the interface is written, it must be provided to the client for use, so the method of input and output parameter request must have a detailed interface document, and timely update. If the interface document is written alone, it needs considerable energy, so it is more hoped that the interface document can be automatically generated based on annotations. Swagger and apidoc can be used here. Finally, I choose to use APidoc, because swagger is troublesome to use in node, and some configuration needs to be added. To complete the use of Swagger in Node, I need to build a small project. Most importantly, Swagger’s interface is not pretty, so I chose apidoc in the end.

First install apidOC in your project:

npm i apidoc -D
Copy the code

Then add the apidOC field to the project package.json to configure the APidOC

"Apidoc ": {"title": "url": "https://notes.jindll.com", "template": {"forceLanguage": "zh_cn"}}Copy the code

The title is the title of the page, the URL is the interface prefix, which is spelled before the sample interface, template is the configuration of the interface template, forceLanguage specifies the page language, and more about apidoc. We will publish an article about apidoc later, or you can also see the official documentation: apidocjs.com/

Then write comments in Contriller according to apidOC requirements. Here is an example:

// app/controller/home
const Controller = require('egg').Controller;

class HomeController extends Controller {
    /** * @api {POST} / POST POST interface name * @apidescription POST interface description * @apiVersion 1.0.0 * @apiname/POST * @apigroup HOME * @apiparam {String} userName userName * @apiparam {Number} userId userId * @apiparamexample {json} input parameter example: * {* "userName": "UserId ", * "userId": 0910923212 *} * @apisuccess {Object} data body, Name User name * @apisuccess {Number} data.id User ID * @apisuccess {String} data.token Token * @apisuccess {Number} code Request status 1 The request succeeds, 0 the request fails * @apisuccess {String} MSG Error message * @apisuccessexample {json} Example parameters: * {" code ": 1," MSG ":" ", "data" : {" name ":" zhang ", "id", 1585977255, "token" : "wxdfsdgsd1d1b6684282dac4b"}} * /
  async post() {
    const { ctx } = this;
    ctx.body = 'hi, post'; }}module.exports = HomeController;
Copy the code

The comments are finished, and then we add a docs command to the project package.json to generate the document:

"script": {
  "docs": "npx apidoc -i ./app/controller/ -o app/public/docs"
}
Copy the code

After configuration, we run NPM run docs, then generate interface document in app/public/docs, rerun the project. Visit http://127.0.0.1:7001/public/docs/index.html can see output interface document

Let’s go back to the screenshot and explain what the comments above mean

annotation instructions
@api {POST} / POST Indicates the name of the POST interface {POST}Request mode, that is, the blue TAB on the top page

/postThat is, the full interface address under the blue label, and the domain name in front of it is frompackage.jsonfileapidoc.url

Post Interface nameThat is, the interface name and displayed in the sidebar of the pageName of the home-post interface
@apidescription Post Indicates the description of the interface The interface description is displayed on the corresponding page
@ apiVersion 1.0.0 Interface Version number
@apiName /post Interface name. Used with the version, you can define the same name for comparison between different versions
@apiparam {String} userName userName {String}The data type

userNameThe name and

The user nameDescription of fields

Refer to the table of parameters
@apiParamExample {json} Input parameter example: The value is in JSON format
@apisuccess {Object} data Message body, the request failure message body is null Refer to Table Success 200 for interface return instructions

{Object}The data type

dataOut of the field

The body of the request failure message is NULLFields that
@apisuccessexample {json} Example parameters: The value is in JSON format

Reference Documents:

Official document: apidocjs.com/

How do I obtain uploaded files

The default file upload mode of the framework is stream mode. You can fetch the uploaded file stream in Controller via ctx.getFileStream and write the stream to a file, or upload it to OSS:

// app/controller/home
const Controller = require('egg').Controller;

class HomeController extends Controller {
  async post() {
    const { ctx } = this;
    const fileStream = await ctx.getFileStream();
    console.log(fileStream);
    ctx.body = 'hi, post'; }}module.exports = HomeController;
Copy the code

If you are not familiar with the file stream, the framework also provides a file mode, first configure the file mode in config, and then modify the file size allowed to upload:

// config/config.default.js
'use strict';
module.exports = appInfo= > {
  const config = exports = {};

  config.keys = appInfo.name + '_1586250824909_3576';

  config.middleware = [];

  const userConfig = {};

  return{... config, ... userConfig,multipart: {
      mode: 'file'.// Configure the file upload mode
      fileSize: '1024mb' // Set the file size that can be uploaded}}; };Copy the code

Then you can get a list of uploaded files from ctx.request.files in Controller:

// app/controller/home
const Controller = require('egg').Controller;

class HomeController extends Controller {
  async post() {
    const { ctx } = this;
    const file = ctx.request.files[0];
    console.log(file);
    ctx.body = 'hi, post'; }}module.exports = HomeController;
Copy the code

This is because the framework only allows uploads of the following types of files by default:

// images
'.jpg'.'.jpeg', // image/jpeg
'.png', // image/png, image/x-png
'.gif', // image/gif
'.bmp', // image/bmp
'.wbmp', // image/vnd.wap.wbmp
'.webp'.'.tif'.'.psd',
// text
'.svg'.'.js'.'.jsx'.'.json'.'.css'.'.less'.'.html'.'.htm'.'.xml',
// tar
'.zip'.'.gz'.'.tgz'.'.gzip',
// video
'.mp3'.'.mp4'.'.avi'.Copy the code

Therefore, we need to expand the file types allowed to upload under config:

// config/config.default.js
'use strict';
module.exports = appInfo= > {
  const config = exports = {};

  config.keys = appInfo.name + '_1586250824909_3576';

  config.middleware = [];

  const userConfig = {};

  return{... config, ... userConfig,multipart: {
      mode: 'file'.// Configure the file upload mode
      fileSize: '1024mb'.// Set the file size that can be uploaded
      fileExtensions: [ '.exe'.'.zip' ] // Add support for other types of files}}; };Copy the code

Restart the service after the configuration is complete.

Reference Documents:

Official documentation: eggjs.org/zh-cn/basic…

How to forward a request

For the middle layer, most of the interfaces may be directly forwarded to the real interface without secondary filtering, so we need to directly forward a certain type of interface. Here we can directly use the plug-in egg-proxy, NPM I egg-proxy in the project, and then enable the plug-in in the configuration:

// config/plugin.js
'use strict';
exports.proxy = {
  enable: true.package: 'egg-proxy'};Copy the code
// config/config.default.js
'use strict';
module.exports = appInfo= > {
  const config = exports = {};

  config.keys = appInfo.name + '_1586250824909_3576';

  config.middleware = [];

  const userConfig = {};

  return{... config, ... userConfig,proxy: {
      host: 'https://www.baidu.com'.// Forward the request to this address
      match: /^(\/api)/.// Forward domain names starting with/API
      map(path) {
        const finalPath = path.replace(/^(\/api)/.' '); // Delete the/API when forwarding
        // Return the modified request path
        returnfinalPath; ,}}}; };Copy the code

The forwarding request has also been configured. At this point, the teaching of eggJS novice village has been basically completed. The remaining content is to add, delete, modify and check according to actual needs.

series

Customize a set of vuE-CLI project templates for your own development

Iframe architecture micro front-end combat

Nginx common configuration

Structural design of large front-end projects

Git management scheme for large front-end projects

Installing nginx on Linux

How to implement a message prompt component (Vue) using JS calls

How to write a universal throttling and anti – shake function