First, egg.js industry background

1.1 What is egg.js

Born for enterprise-level frameworks and applications, egg.js can spawn more upper-layer frameworks to help development teams and developers reduce development and maintenance costs.

2.2 What are the differences with the community framework

Express is a framework widely used by the Node.js community. It is simple and extensible, perfect for personal projects. But the framework itself lacks conventions, and the standard MVC model can be written in all sorts of strange ways. Egg is developed by convention, by convention over configuration, with low teamwork costs.

Like Egg, Sails are a “convention over configuration” framework that also scales well. Compared with Egg, however, Sails support the Blueprint REST API, extensible ORM like WaterLine, front-end integration, WebSocket, and more, but those features are provided by Sails. Egg, on the other hand, does not provide functions directly. Instead, it simply integrates various sails, such as egg-blueprint, egg-waterline, and so on, and uses the losa-Egg framework to combine them instead.

2.3 Relationship between Egg and Koa

Koa is a new Web framework, built by the same people behind Express, that aims to be a smaller, more expressive, and more robust cornerstone of web application and API development. The middleware used is the Onion model:

2.3.1 Egg is inherited from Koa

Koa is an excellent framework, but it is still basic for enterprise applications. Egg chose Koa as its base framework, and made some further enhancements to it based on its model. Extensions and plug-ins are more complete and convenient.

Let’s learn egg.js quickly!

First introduction to egg.js

2.1 Installation, configuration and basic project structure of egg.js

2.1.1 installation

$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i
Copy the code
$ npm run dev
$ open http://localhost:7001
Copy the code
  • An Egg project has been initialized and is ready to run:

2.1.2 Project directory structure

An egg - project ├ ─ ─ package. Json ├ ─ ─ app. Js (optional) ├ ─ ─ agent. The js (optional) ├ ─ ─ app | ├ ─ ─ the router. The js │ ├ ─ ─ controller │ | └ ─ ─ home. Js │ │ ├ ─ ─ service (optional) | └ ─ ─ the user. The js │ ├ ─ ─ middleware (optional) │ | └ ─ ─ response_time. Js │ ├ ─ ─ the schedule (optional) │ | └ ─ ─ my_task. Js │ │ ├ ─ ─ public (optional) | └ ─ ─ reset. CSS │ ├ ─ ─ the view (optional) │ | └ ─ ─ home. TPL │ └ ─ ─ the extend (optional) │ ├ ─ ─ helper. Js (optional) │ ├ ─ ─ Request. Js (optional) │ ├ ─ ─ the response. The js (optional) │ ├ ─ ─ the context, js (optional) │ ├ ─ ─ application. Js (optional) │ └ ─ ─ agent. The js (optional) ├ ─ ─ the config | ├ ─ ─ plugin. Js | ├ ─ ─ config. The default. The js │ ├ ─ ─ config. Prod. Js | ├ ─ ─ config. The test. The js (optional) | ├ ─ ─ config. Local, js (optional) | └ ─ ─ Config. Unittest. Js (optional) └ ─ ─ the test ├ ─ ─ middleware | └ ─ ─ response_time. Test. The js └ ─ ─ controller └ ─ ─ home. Test. JsCopy the code

2.1.3 Built-in CTX objects

  • The CTX object is very important. The parameters of the request and the returned message need to be retrieved or set by CTX object
  • CTX is a context object that inherits from KOA. You can change the returned information by setting ctx.body

2.1.4. Try to write a new route

In the controller

 async list() {
    const { ctx } = this;
    ctx.body = {
      code: 200.data: [{id: '1'.name: 'Joe'}, {id: '2'.name: 'bill'}, {id: '3'.name: 'Cathy'],},msg: 'Operation successful'}; }Copy the code

Matches the newly written route in router.js

router.get('/list', controller.home.list);
Copy the code

Effect:

  • A new route has been created

2.2 Egg.js control layer, params and Query parameter passing, return status code setting

To do a good job, he must sharpen his tools. To use egg shortcuts and code prompts, it is recommended to use the egg plug-in of vscode.

2.2.1 Controller Layer

Configure routes for the new controller

/ / the user controller
  router.get('/user/info', controller.user.info);
Copy the code

2.2.2 Route Params Parameter Transmission Mode

The router accepts the parameter through :id

router.get('/user/findById/:id', controller.user.info);
Copy the code

In controller, get the parameters through Params

  async findById() {
    const { ctx, params } = this;
    const userId = params;
    const userlist = [
      {
        id: '1'.name: 'Joe'}, {id: '2'.name: 'bill'}, {id: '3'.name: 'Cathy',},];const result = userlist.find(v= > v.id === userId);
    ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

The parameter is passed in the form of 2.2.3 query

  async findById2() {
    const { ctx } = this;
    const userId = ctx.query.id;
    const userlist = [
      {
        id: '1'.name: 'Joe'}, {id: '2'.name: 'bill'}, {id: '3'.name: 'Cathy',},];const result = userlist.find(v= > v.id === userId);
    ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

2.2.4 Modifying the Returned status code

2.3. Post Requests

  • In actual business, for the security of interface request, and multi-data request parameters, multi-type request, attachment, picture upload and so on, post request is mostly adopted.
  • How do YOU use post requests in an Egg

2.3.1 Configuring CSRF cross-domain Configurations

Install cross-domain plug-ins

npm i egg-cors --save
Copy the code

Configure plugin.js under config

'use strict';

/ * *@type Egg.EggPlugin */
module.exports = {
  // had enabled by egg
  // static: {
  // enable: true,
  // }
  cors: {
    enable: true.package: 'egg-cors',}};Copy the code

Configure config.default.js under config

// Disable CRSF and enable cross-domain
  config.security = {
    csrf: {
      enable: false,},domainWhiteList: [],};// Allow cross-domain methods
  config.cors = {
    origin: The '*'.allowMethods: 'GET, PUT, POST, DELETE, PATCH'};Copy the code

Test to create a POST request

async createUser() {
    const { ctx } = this;
    console.log(ctx.request.body, 'bodyield... 1 ');
    const result = {
      username: ctx.request.body.username,
      age: ctx.request.body.age,
    };
    ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

  • At this point, the environment for the POST request is ready

2.4 Route grouping of egg.js

  • When you write many controllers and need to configure many routes, you write all the routing files in one file, which makes the routing files redundant and difficult to read. At this time, we need to divide the route into modules and introduce it in the total route.

2.4.1 Routing Groups

Create a router directory and remove routes by module

Import it in router.js via require

Egg.js

3.1 Configuring the MySQL Database

  • The previous articles are static data, and to complete persistent real data, we need to use the database. Here we use MySQL, the most frequently used database in our work. By using egg-Sequelize plug-in, we can add, delete, change, check and semantic operations like operating some object attributes. Instead of writing SQL manually.

3.2. Database migration

3.2.1 Preparations

Install and configure the egg-Sequelize plugin (which will assist us in loading defined Model objects into app and CTX) and mysql2 modules:

npm install --save egg-sequelize mysql2
Copy the code

In 3.2.2.config/plugin.jsTo introduce the egg-Sequelize plug-in

exports.sequelize = {
  enable: true,
  package: 'egg-sequelize',
};
Copy the code

3.2.3.config/config.default.js

Config. sequelize = {dialect: 'mysql', host: '127.0.0.1', username: 'root', password: 'root', port: 3306, database: 'eggAPI ', // Timezone: '+08:00', define: {freezeTableName: True, // Automatically writes timestamp created_at updated_at timestamps: true, // Field creates soft delete timestamp deleted_at paranoid: true, createdAt: paranoid 'created_at', updatedAt: 'updated_at', deletedAt: 'deleted_at', // all hump naming formatting: true}};Copy the code

3.2.4. Sequelize providessequelize-cliTools to implementMigrationsWe can also introduce sequelize-CLI in the Egg project.

npm install --save-dev sequelize-cli
Copy the code

3.2.5. In the egg project, we want to put all database migrations-related content in the egg projectdatabaseDirectory, so we will create a new one under the project root directory.sequelizercConfiguration file:

'use strict';
​
const path = require('path');
​
module.exports = {
  config: path.join(__dirname, 'database/config.json'),
  'migrations-path': path.join(__dirname, 'database/migrations'),
  'seeders-path': path.join(__dirname, 'database/seeders'),
  'models-path': path.join(__dirname, 'app/model'),
};
Copy the code

3.2.6. Initialize Migrations configuration files and directories

npx sequelize init:config
​
// npx sequelize init:models
Copy the code

Ps: Pay attention to prepare PHP integrated environment, Windows recommended WAMP, MAC recommended MAMP

3.2.7. Generated after the line is finisheddatabase/config.jsonFiles anddatabase/migrationsThe catalog, let’s change itdatabase/config.jsonChange it to the database configuration used in our project:

{ "development": { "username": "root", "password": "root", "port": "8889" "database": "eggapi", "host": "127.0.0.1" and "the dialect", "mysql", "timezone" : "+ 08:00}}"Copy the code

3.2.8. Creating a database

npx sequelize db:create
Copy the code

Database created successfully

3.3 Creating data tables and Rolling Back Data

3.3.1 Creating a Data Migration table

npx sequelize migration:generate --name=init-user
Copy the code

1. After the command is executed, a data table migration file is generated in the database/migrations/directory and defined

'use strict'; module.exports = { up: async (queryInterface, Sequelize) => { const { INTEGER, STRING, DATE, ENUM } = Sequelize; / / create a table await queryInterface. CreateTable (' user '{id: {type: INTEGER (20). UNSIGNED, primaryKey: true, autoIncrement: True}, username: {type: STRING(30), allowNull: false, defaultValue: '', comment: '', unique: true}, password: { type: STRING(200), allowNull: false, defaultValue: '' }, avatar_url: { type: STRING(200), allowNull: True, defaultValue: ''}, sex: {type: ENUM, values: [' male ',' female ',' private '], allowNull: true, defaultValue: 'male ', comment: 'User gender '}, created_at: DATE, updated_at: DATE}); }, down: async queryInterface => { await queryInterface.dropTable('user') } };Copy the code

3.3.2 Run migrate to change the database

NPX sequelize db:migrate You can use 'db:migrate:undo' to roll back a change # NPX sequelize db:migrate:undo # NPX sequelize db:migrate:undo:all 'to roll back a change # NPX sequelize db:migrate:undo:allCopy the code

Four, egg.js practical operation

  • The above three steps have completed the basic shelf from controller to data and back again. In ordinary work, egg.js is used to add, delete, modify, and search. Here are the detailed practices for each part:

4.1 Added related interfaces and chestnuts 🌰

4.1.1 Adding a User

// app/controller/user.js
 async createUser() {
    const { ctx } = this;
    const { username, password } = ctx.request.body;
    const result = await ctx.model.User.create({ username, password });
    ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

Insert a new user data into the database

4.1.2 Registering Multiple Users

// Create multiple users
  async createUserList() {
    const { ctx } = this;
    const { data } = ctx.request.body;
    const result = await ctx.model.User.bulkCreate(data);
    console.log(data, '1111');
    ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

4.1.3 Skillfully use set method to encrypt and store passwords

  • Change the password to an encrypted password using the set method in the model

Install the md5

npm install md5
Copy the code

Modify the model/user.js model using the set method

'use strict';
const md5 = require('md5');

module.exports = app= > {
  const { STRING, INTEGER, DATE, ENUM } = app.Sequelize;
  // Configure (important: be sure to configure detailed, be sure to!!)
  const User = app.model.define('user', {
    id: { type: INTEGER(20).UNSIGNED, primaryKey: true.autoIncrement: true },
    username: { type: STRING(30), allowNull: false.defaultValue: ' '.comment: 'User name'.unique: true },
    password: { type: STRING(200), allowNull: false.defaultValue: ' '.set(value) {
        const enCrpty = md5(value + 'secret');
        this.setDataValue('password', enCrpty); }},avatar_url: { type: STRING(200), allowNull: true.defaultValue: ' ' },
    sex: { type: ENUM, values: [ 'male'.'woman'.'secret'].allowNull: true.defaultValue: 'male'.comment: 'User gender' },
    created_at: DATE,
    updated_at: DATE,
  }, {
    timestamps: true.// Whether to write the timestamp automatically
    tableName: 'user'.// Create a custom table name
  });

  return User;
};
Copy the code

The back of the password is encrypted

/ / check

4.2 Querying related interfaces and chestnuts 🌰

Query is basically some details and lists of business, business is different, check the way is also diverse.

4.2.1 Querying a findByPk method

 / / a single
  async findUserByUserId() {
    const id = parseInt(this.ctx.params.id);
    console.log(id);
    const user = await this.ctx.model.User.findByPk(id);
    if(! user) {this.ctx.body = {
        code: 200.data: null.msg: 'Operation successful'};return;
    }
    this.ctx.body = {
      code: 200.data: user,
      msg: 'Operation successful'}; }Copy the code

4.2.2 Where Condition Query

  • Conditions for a single findOne method, written in where
  // findOne condition check
  async findUserByCondition() {
    const user = await this.ctx.model.User.findOne({
      where: {
        id: 2.sex: 'woman',}});if(! user) {this.ctx.body = {
        code: 200.data: null.msg: 'Operation successful'};return;
    }
    this.ctx.body = {
      code: 200.data: user,
      msg: 'Operation successful'}; }Copy the code

4.2.3 List Query

  • Find multiple, using the findAll method
// Query multiple
  async findAll() {
    const result = await this.ctx.model.User.findAll();
    if(! result) {this.ctx.body = {
        code: 200.data: null.msg: 'Operation successful'};return;
    }
    this.ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

4.2.4 List and Count Query

  • Find more than one and count them using findAndCountAll
 // Query multiple and count them
  async findAndCountAll() {
    const result = await this.ctx.model.User.findAndCountAll();
    if(! result) {this.ctx.body = {
        code: 200.data: null.msg: 'Operation successful'};return;
    }
    this.ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

4.2.5 Skillfully use get method to process the queried value twice

  • The get method modifies the queried value
// model/user.js
 created_at: {
      type: DATE,
      get() {
        const val = this.getDataValue('created_at');
        return 'zh'+ val; }},Copy the code

4.2.6 Use of WHERE in findAll

  // Check multiple conditions
  async findAllCondition() {
    const Op = this.app.Sequelize.Op;
    const result = await this.ctx.model.User.findAll({
      where: {
        // Id is greater than 3
        id: {
          [Op.gt]: 3,},/ / only male
        sex: 'male'.// Blur search for user name
        username: {
          [Op.like]: '% 3%',}}});if(! result) {this.ctx.body = {
        code: 200.data: null.msg: 'Operation successful'};return;
    }
    this.ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

Where also has the following common conditions

where: {

    id: {

      [Op.and]: {a: 5},           // and (a = 5)

      [Op.or]: [{a: 5}, {a: 6}].// (a = 5 or a = 6)

      [Op.gt]: 6.// id > 6

      [Op.gte]: 6.// id >= 6

      [Op.lt]: 10.// id < 10

      [Op.lte]: 10.// id <= 10

      [Op.ne]: 20.// id ! = 20

      [Op.between]: [6.10].// Between 6 and 10

      [Op.notBetween]: [11.15].// Not between 11 and 15

      [Op.in]: [1.2].// in [1, 2]

      [Op.notIn]: [1.2].// Not in [1, 2]

      [Op.like]: '%hat'./ / contains' % hat '

      [Op.notLike]: '%hat'.// does not contain '%hat'

      [Op.iLike]: '%hat'.// contains '%hat' (case insensitive) (PG only)

      [Op.notILike]: '%hat'.// does not contain '%hat' (PG only)

      [Op.overlap]: [1.2].// && [1, 2] (PG array overlap operator)

      [Op.contains]: [1.2].// @> [1, 2] (PG array contains operators)

      [Op.contained]: [1.2].// <@ [1, 2] (PG array contained in operator)

      [Op.any]: [2.3].// Any array [2, 3]::INTEGER (PG only)

    },

    status: {

      [Op.not]: false.// status is not FALSE

    }
Copy the code

4.2.7 Querying Only Certain Required Fields

  • Limit field Attributes
// Restrict field
  async findAllLimtColumn() {
    const result = await this.ctx.model.User.findAll({
      attributes: [ 'id'.'username'.'password']});if(! result) {this.ctx.body = {
        code: 200.data: null.msg: 'Operation successful'};return;
    }
    this.ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

  • Attributes /exclude all other attributes
 const result = await this.ctx.model.User.findAll({
      attributes: {
        exclude: [ 'password'],}});Copy the code

4.2.8 Sorting the Query structure

  • Ordering order, and Attributes are sibling positions
 order: [
        [ 'updated_at'.'DESC' ],
        [ 'id'.'DESC']],Copy the code

4.2.9 Paging Query

  • Paging query. Offset starts from that and limit limits a number of entries
// paging query
  async findAllByPage() {
    const { query } = this.ctx;
    const limit = 5;
    const offset = (query.page - 1) * limit;
    const result = await this.ctx.model.User.findAll({
      attributes: {
        exclude: [ 'password'],},order: [['updated_at'.'DESC' ],
        [ 'id'.'DESC' ],
      ],
      offset,
      limit,
    });
    if(! result) {this.ctx.body = {
        code: 200.data: null.msg: 'Operation successful'};return;
    }
    this.ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

4.3 Update related interfaces and chestnuts 🌰

For example, if a user modifies his personal information, we need to update the data in the database

  • update
 // Update the user
  async updateUserInfo() {
    const id = this.ctx.params.id ? parseInt(this.ctx.params.id) : 0;
    const result = await this.ctx.model.User.findByPk(id);
    if(! result) {this.ctx.body = {
        code: 200.data: null.msg: 'User not found'};return;
    }
    result.username = this.ctx.request.body.username;
    const updateResult = result.save();

    this.ctx.body = {
      code: 200.data: updateResult,
      msg: 'Operation successful'}; }Copy the code

Update allows you to update multiple fields at a time. Fileds allows you to restrict which fields can be updated to increase security

const updateParams = this.ctx.request.body;
    const updateResult = await result.update(updateParams, {
      fields: [ 'username']});Copy the code

4.4 Deleting Related Interfaces and chestnuts 🌰

4.4.1 Deleting a Single Vm

Removing a single

  / / delete
  async destroy() {
    const id = this.ctx.params.id ? parseInt(this.ctx.params.id) : 0;
    const result = await this.ctx.model.User.findByPk(id);
    if(! result) {this.ctx.body = {
        code: 200.data: null.msg: 'User not found'};return;
    }

    const destroyResult = await result.destroy();
    this.ctx.body = {
      code: 200.data: destroyResult,
      msg: 'Operation successful'}; }Copy the code

4.1.2 Deleting files in Batches

  • Batch delete

Using conditional matching in Op, anything less than 7 is deleted

  const Op = this.app.model.Sequelize.Op;
    const destroyResult = await this.app.model.User.destroy({
      where: {
        id: {
          [Op.lte]: 7,}}});Copy the code

Five, egg.js advanced

5.1 Exception Handling

Exceptions are not handled in the same way in Egg, and if an error is reported, it is an error HTML page returned from the Egg framework, which is neither friendly nor for whatever reason. Exception handling

  • An exception is thrown
this.ctx.throw(500.'Custom error message');
Copy the code

5.2 Middleware, same exception handling

5.2.1 Write error handling middleware first

5.2.2 Reconfiguring the middleware

5.2.3 First use the same exception processing logic, which can be adjusted freely according to actual needs

Copy the code

module.exports = () = > {
  return async function errrorHanlder(ctx, next) {
    try {
      await next();
    } catch (error) {
      // Log
      ctx.app.emit('error', error, ctx);
      // Same exception returns
      ctx.status = error.status;
      ctx.body = {
        msg: 'fail'.data: error.message, }; }}; };Copy the code

5.2.4 Configuring the Exception Switch to filter some abnormal routes

  // flag to enable your hanlder
  config.errorHanlder = {
    enable: true.// match: '/user/findUserByUserId',
    ignore: '/user/findUserByUserId'};Copy the code

5.3 Verifying Parameters

It is necessary to verify some client parameters to ensure system security and data reliability

// install egg-valparams
npm i egg-valparams --save
/ / 2. Configuration
// config/plugin.js
valparams : {
  enable : true.package: 'egg-valparams'
},
// config/config.default.js
config.valparams = {
    locale    : 'zh-cn'.throwError: true
};
/ / 3.
  // Create a user
  async createUser() {
    const { ctx } = this;
    ctx.validate({
      username: { type: 'string'.required: true.desc: 'Username' },
      password: { type: 'string'.required: true.desc: 'password' },
      sex: { type: 'string'.required: false.defValue: 'male'.desc: 'gender'}});const { username, password } = ctx.request.body;

    const result = await ctx.model.User.create({ username, password });
    ctx.body = {
      code: 200.data: result,
      msg: 'Operation successful'}; }Copy the code

It can be seen that the above error is in English, we need the Chinese error message, to write in the error middleware, 422 status code, special handling exceptions

 if (error.status === 422) {
        ctx.body = {
          msg: 'fail'.data: error.message,
        };
      }
Copy the code

Postscript: Ok, see here, first of all to thank the patience to read their own; Whether you put this article in your favorites folder, or follow through the project, I believe you have some goods! Favorite friends like + attention oh! Here is the project source address oh:

Git address: github.com/liujun8892/…