Egg inherits from Koa, and has chosen Koa as its base framework, with some further enhancements to its model.

Perform process analysis

Koa’s middleware chose the onion ring model.

All requests are executed twice through one middleware, and Koa’s model makes it very easy to implement post-processing logic in contrast to express-style middleware, where execution is front-to-back and back-to-front.

Middleware execution sequence diagram

Directory structure analysis

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) ├ ─test├ ─ ─ middleware | └ ─ ─ response_time. Test. The js └ ─ ─ controller └ ─ ─ home. Test. JsCopy the code

The directory specified by the framework:

  • app/router.jsThis command is used to configure URL routing rules
  • app/controller/**Used to parse user input and return corresponding results after processing
  • app/service/**Used to write the business logic layer, optional
  • app/middleware/**Used to write middleware, optional
  • app/public/**This is optional for placing static resources
  • app/extend/**For extensions to the framework, optional
  • config/config.{env}.jsUsed to write configuration files
  • config/plugin.jsUsed to configure plug-ins that need to be loaded
  • test/**For unit testing
  • app.jsagent.jsIt is used for custom initialization at startup. Optional

Directories agreed by the built-in plug-in:

  • app/public/**This is optional for placing static resources
  • app/schedule/**Scheduled task

File loading sequence

Egg refers to applications, frameworks, and plug-ins as loadUnits

file application The framework The plug-in
package.json ✔ ︎ ︎ ✔
config/plugin.{env}.js ✔ ︎ ✔ ︎
config/config.{env}.js ✔ ︎ ✔ ︎ ✔ ︎
app/extend/application.js ✔ ︎ ︎ ✔ ︎
app/extend/request.js ✔ ︎ ✔ ︎ ✔ ︎
app/extend/response.js ✔ ︎ ✔ ︎ ✔ ︎
app/extend/context.js ✔ ︎ ✔ ︎ ✔ ︎
app/extend/helper.js ✔ ︎ ✔ ︎ ✔ ︎
agent.js ✔ ︎ ✔ ︎ ✔ ︎
app.js ✔ ︎ ✔ ︎ ✔ ︎
app/service ✔ ︎ ✔ ︎ ✔ ︎
app/middleware ✔ ︎ ✔ ︎ ✔ ︎
app/controller ✔ ︎
app/router.js ✔ ︎

The files are loaded from top to bottom in the order listed in the table, and the Egg iterates through all loadUnits to load the above files (applications, frameworks, plug-ins vary) with a certain priority.

  • Click Plug-in => Framework => Applications to load
  • The order of plug-ins is determined by the dependency relationship. The dependent party loads plug-ins first, and the non-dependent plug-ins load plug-ins in the order of object key configuration
  • Frameworks are loaded in inheritance order, starting at the bottom

The life cycle

  • The configuration file is about to load, and this is the last time to dynamically modify the configuration (configWillLoad)
  • The configuration file is loaded (configDidLoad)
  • The file is loaded (didLoad)
  • Plug-in started (willReady)
  • The worker is ready (didRead)
  • Application startup completed (serverDidReady)
  • Application is about to close (beforeClose)

The definition is as follows:

// app.js or agent.js
class AppBootHook {
  constructor(app) {
    this.app = app;
  }
  configWillLoad() {
    // Ready to call configDidLoad,
    // Config, plugin files are referred,
    // this is the last chance to modify the config.
    console.log('configWillLoad');
  }
  configDidLoad() {
    // Config, plugin files have been loaded.
    console.log('configDidLoad');
  }
  async didLoad() {
    // All files have loaded, start plugin here.
    console.log('didLoad');
  }
  async willReady() {
    // All plugins have started, can do some thing before app ready
    console.log('willReady');
  }
  async didReady() {
    // Worker is ready, can do some things
    // don't need to block the app boot.
    console.log('didReady');
  }
  async serverDidReady() {
    // Server is listening.
    console.log('serverDidReady');
  }
  async beforeClose() {
    // Do some thing before app close.
    console.log('configWillLoad'); }}module.exports = AppBootHook;
Copy the code

The order of execution at startup

Frame built-in base object *

doc

Some of the base objects built into the framework include four objects inherited from Koa

  • Application
  • Context
  • Request
  • Response

And some objects for the framework extension

  • Controller
  • Service
  • Helper
  • Config
  • Logger

Application

Application is a global Application object. Within an Application, only one is instantiated. It inherits from KOa. Application, on which we can mount global methods and objects.

The event

  • serverThis event will only be triggered once by a worker process. After the HTTP service is started, the HTTP server will be exposed to developers through this event
  • errorAn error event is raised when the onError plug-in catches any exceptions at runtime
  • requestresponseWhen an application receives and responds to a request, the Request and Response events are triggered, respectively
// app.js
module.exports = app= > {
  app.once('server', server => {
    // websocket
    console.log('server', server);
    
  });
  app.on('error', (err, ctx) => {
    // report error
    console.log('error', err);
  });
  app.on('request', ctx => {
    // log receive request
    console.log('request');
  });
  app.on('response', ctx => {
    // ctx.starttime is set by framework
    const used = Date.now() - ctx.starttime;
    // log total cost
    console.log('used', used);
  });
};

// The initialization will print server server
// request
// used 6
// request
// used 1
Copy the code

access

Almost all files loaded by the framework Loader (Controller, Service, Schedule, etc.) can export a function that is called by the Loader and takes APP as an argument

  • Start a custom script
// app.js
module.exports = app= > {
  app.cache = new Cache();
};
Copy the code
  • Controller file
// app/controller/user.js
class UserController extends Controller {
  async fetch() {
    this.ctx.body = this.app.cache.get(this.ctx.query.id); }}Copy the code

As with Koa, on the Context object, the Application object can be accessed through ctx.app. That is to say,

// app/controller/user.js
class UserController extends Controller {
  async fetch() {
    this.ctx.body = this.ctx.app.cache.get(this.ctx.query.id); }}Copy the code

Context

Context is a request-level object, inherited from koa.context

Each time a user request is received, the framework instantiates a Context object, which encapsulates the information about the request and provides a number of convenient methods to retrieve request parameters or set response information

access


// Koa v1
function* middleware(next) {
  // this is instance of Context
  console.log(this.query);
  yield next;
}

// Koa v2
async function middleware(ctx, next) {
  // ctx is instance of Context
  console.log(ctx.query);
}
Copy the code

In addition to retrieving Context instances on request, there are scenarios where we need to access objects on Context instances such as Service/Model for non-user requests. Can use the Application. The createAnonymousContext () method to create an anonymous Context instance.

// app.js
module.exports = app= > {
  app.beforeStart(async() = > {const ctx = app.createAnonymousContext();
    // preload before app start
    await ctx.service.posts.load();
  });
}
Copy the code

Each task in the scheduled task takes a Context instance as an argument

// app/schedule/refresh.js
exports.task = async ctx => {
  await ctx.service.posts.refresh();
};
Copy the code

Request & Response

  • RequestIs a request-level object inherited fromKoa.Request. Encapsulates the node.js native HTTP Request object and provides a series of auxiliary methods to obtain common HTTP Request parameters
  • ResponseIs a request-level object inherited fromKoa.Response. Encapsulates the node.js native HTTP Response object and provides a series of helper methods to set the HTTP Response

access

Request(ctx.Request) and Response(ctx.Response) instances of the current Request can be obtained on Context instances.

// app/controller/user.js
class UserController extends Controller {
  async fetch() {
    const { app, ctx } = this;
    constid = ctx.request.query.id; ctx.response.body = app.cache.get(id); }}Copy the code
  • Koa proxies a portion of the Request and Response methods and properties on the Context. See koa.context
  • As in the example abovectx.request.query.idctx.query.idIs equivalent to,ctx.response.body=ctx.body=Are equivalent
  • Note that the body that gets the POST should be usedctx.request.bodyRather thanctx.body

Controller

The framework provides a Controller base class and recommends that all Controllers inherit from that base class implementation.

The Controller base class has the following properties:

  • ctx– Context instance of the current request
  • app– Application instance of the Application
  • config– Application configuration
  • service– Apply all services
  • logger– The logger object encapsulated for the current controller

In Controller files, you can refer to the Controller base class in two ways:

// app/controller/user.js

// Get from egg (recommended)
const Controller = require('egg').Controller;
class UserController extends Controller {
  // implement
}
module.exports = UserController;

// From the app instance
module.exports = app= > {
  return class UserController extends app.Controller {
    // implement
  };
};
Copy the code

Service

The framework provides a Service base class and recommends that all services inherit from this base class implementation.

The Service base class has the same properties as the Controller base class, and the access is similar:

// app/service/user.js

// Get from egg (recommended)
const Service = require('egg').Service;
class UserService extends Service {
  // implement
}
module.exports = UserService;

// From the app instance
module.exports = app= > {
  return class UserService extends app.Service {
    // implement
  };
};
Copy the code

Helper

Helper is used to provide utility functions. What it does is it allows you to separate common actions from helper.js into a separate function. This allows you to write complex logic in JavaScript, avoid the logic being scattered around, and make it easier to write test cases.

The Helper itself is a class that has the same properties as the Controller base class and is instantiated on each request, so all functions on the Helper also get context information about the current request.

access

An instance of the currently requested Helper(ctx.helper) is available on an instance of the Context.

// app/controller/user.js
class UserController extends Controller {
  async fetch() {
    const { app, ctx } = this;
    const id = ctx.query.id;
    constuser = app.cache.get(id); ctx.body = ctx.helper.formatUser(user); }}Copy the code

Customize helper methods

// app/extend/helper.js
module.exports = {
  formatUser(user) {
    return only(user, [ 'name'.'phone']); }};Copy the code

Config

Recommended application development, follow the principle of separation of configuration and code will need some hard-coded business configuration in configuration files, configuration files at the same time support each different running environment using different configuration, use rise very convenient also, all frameworks, plug-in and the application level of access to the configuration can be through the Config object.

access

The config object can be obtained from the Application instance via app.config or from this. Config on Controller, Service, or Helper instances.

Logger

Each Logger object provides four levels of methods:

  • logger.debug()
  • logger.info()
  • logger.warn()
  • logger.error()

access

  • app.loggerIf we want to do some application level logging, such as recording some data information during the startup phase, recording some business irrelevant information, we can do it through the App Logger
  • app.coreLoggerThe CoreLogger should not be used to print application-level logs during application development. Frameworks and plug-ins need to use CoreLogger to print application-level logs. In this way, the logs printed by the CoreLogger are placed in a different file from the Logger
  • ctx.loggerFetching it from the Context instance, the Context Logger must be request-specific, and the logs it prints are preceded by information about the current request (e.g[$userId/$ip/$traceId/${cost}ms $method $url])
  • this.loggerThis can be done on Controller and Service instancesthis.loggerGet them, they’re essentially a Context Logger, but they add an extra file path when they print the log