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, defaultGET supportGET, POST, PUT, DELETE, and PATCH 等 |
data | Object | To participate,GET POST All methods pass arguments through this field |
dataType | String | Response format; By default, buffer is returned directlytext 和 json Two formats. |
contentType | String | Request data format, default isundefined , HttpClient will automatically base ondata 和 content Parameters are automatically set.data The default for object isform . supportjson Format. |
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/post That is, the full interface address under the blue label, and the domain name in front of it is frompackage.json fileapidoc.url Post Interface name That 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 typeuserName The name andThe user name Description of fieldsRefer 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 typedata Out of the fieldThe body of the request failure message is NULL Fields 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