introduce
Eggjs is a framework based on Koajs, so it should be a framework on top of the framework. It inherits the high-performance advantages of Koa, and at the same time adds some constraints and development specifications to avoid the problem that Koajs framework itself has too high development freedom.
Koajs is a relatively basic framework in NodeJS. It has very high degree of freedom without too many constraints and specifications. Every developer has his or her own “operations” when implementing his or her own services. In order to adapt to enterprise development, Egg added some development norms and constraints to solve the problem that Koajs is not suitable for enterprise use due to its high degree of freedom. Egg was born under this background.
An Egg is an “Egg” created by the Alibaba team. Eggs have infinite possibilities, and eggs hatched by chickens are chickens, and eggs hatched by dinosaurs are dinosaurs, which better embodies one of the biggest highlights of Egg “plug-in mechanism”, where every company, every team, and even individual developers can hatch the most suitable framework. For example, different departments within Ali have incubated their own suitable egg frames, such as ant chair, UC Nut, aliyun-egg, etc., as can be seen in the following picture.
features
- Provides the ability to customize the upper-layer framework based on Egg
- Highly extensible plug-in mechanism
- Built-in multi-process management
- Based on Koa development, excellent performance
- The framework is stable and the test coverage is high
- Incremental development
Environment setup, creation, and operation
$ npm i egg-init -g $ mkdir egg-example && cd egg-example $ npm init egg --type=simple $ npm i Copy the code
Start the project
$ npm run dev $ gooopen http://localhost:7001 Copy the code
Directory Structure
An egg - project ├ ─ ─ package. Json ├ ─ ─ app. Js (optional) ├ ─ ─ agent. The js (optional) ├ ─ ─ app (project directory) | ├ ─ ─ the router, js (used to configure the URL routing rules) │ ├ ─ ─ Controller (used to parse user input, Return the corresponding results after processing) │ | └ ─ ─ home. Js │ ├ ─ ─ service (for writing business logic layer) │ | └ ─ ─ the user. The js │ ├ ─ ─ middleware (for writing middleware) │ | └ ─ ─ Response_time. Js │ ├ ─ ─ the schedule (optional) │ | └ ─ ─ my_task. Js │ ├ ─ ─ public (for static resources) │ | └ ─ ─ reset. CSS │ ├ ─ ─ the view (optional) │ | └ ─ ─ home. TPL │ └ ─ ─ the extend (for the expansion of the framework) │ ├ ─ ─ helper. Js (optional) │ ├ ─ ─ request. Js (optional) │ ├ ─ ─ the response. The js (optional) │ ├ ─ ─ the context, js (optional) │ ├ ─ ─ application. Js (optional) │ └ ─ ─ agent. The js (optional) ├ ─ ─ the config (used to write configuration files) | ├ ─ ─ the plugin, js (used in configuration needs to be loaded plug-ins) | ├ ─ ─ Config. Default. Js │ ├ ─ ─ config. Prod. Js | ├ ─ ─ config. The test. The js (optional) | ├ ─ ─ config. Local, js (optional) | └ ─ ─ config. The unittest. Js (optional) └ ─ ─ test (for a unit test) ├ ─ ─ middleware | └ ─ ─ response_time. Test. The js └ ─ ─ controller └ ─ ─ home. Test. JsCopy the code
Main Content Introduction
What is the MVC
The design of egg fully conforms to the better MVC design pattern
- Model – A Model represents an object that accesses data. It can also have logic to update the controller as the data changes.
- View – A View represents a visualization of the data contained in the model.
- Controller (Controller) – Controllers act on models and views. It controls the flow of data to model objects and updates the view as the data changes. It separates the view from the model.
Controller (the controller)
Controller is implemented in the app/ Controller directory
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
const { ctx } = this;
ctx.body = 'hi, egg'; }}module.exports = HomeController;
Copy the code
Services (service)
'use strict';
const Service = require('egg').Service;
class HomeService extends Service {
async index() {
return {ok:1}}}module.exports = HomeService;
Copy the code
Modify the controller/home. Js
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
const {
ctx,
service
} = this;
const res = await service.home.index();
ctx.body = res
}
}
module.exports = HomeController;
Copy the code
Router (routes)
'use strict';
/** * @param {Egg.Application} app - egg application */
module.exports = app= > {
const { router, controller } = app;
router.get('/', controller.home.index);
};
Copy the code
Visit: http://locoalhost:7001
Actual project demonstration
Add, delete, change and check the user table
The case is based on mongoose non-relational database
Use egg-Mongoose to link the database
download
npm i egg-mongoose -S
Copy the code
configuration
config/plugin.js
exports.mongoose = {
enable: true.package: 'egg-mongoose'};Copy the code
config/config.default.js
config.mongoose = {
url: "Mongo: / / 127.0.0.1:27017 / egg - test".options: {useUnifiedTopology: true.useCreateIndex:true}}Copy the code
Creating a user model
model/user.js
module.exports = app= > {
const mongoose = app.mongoose;
const UserSchema = new mongoose.Schema({
username: {
type: String.unique: true.required: true
},
password: {
type: String.required: true
},
avatar: {
type: String.default: 'https://1.gravatar.com/avatar/a3e54af3cb6e157e496ae430aed4f4a3?s=96&d=mm'
},
createdAt: {
type: Date.default: Date.now
}
})
return mongoose.model('User', UserSchema);
}
Copy the code
Create a user
router.js
// Create a user
router.post('/api/user',controller.user.create);
Copy the code
controller/user.js
// Create a user
async create() {
const {
ctx,
service
} = this;
const payLoad = ctx.request.body || {};
const res = await service.user.create(payLoad);
ctx.body = {res};
}
Copy the code
service/user.js
async create(payload) {
const {
ctx
} = this;
return ctx.model.User.create(payload);
}
Copy the code
Get all Users
router.js
router.get('/api/user',controller.user.index);
Copy the code
controller/user.js
// Get all users
async index() {
const {
ctx,
service
} = this;
const res = await service.user.index();
ctx.body = res;
}
Copy the code
service/user.js
async index() {
const {
ctx
} = this;
return ctx.model.User.find();
}
Copy the code
Obtain user details by ID
router.js
// Get user details by ID
router.get('/api/user/:id',controller.user.detail);
Copy the code
controller/user.js
async detail() {
const id = this.ctx.params.id;
const res = await this.service.user.detail(id);
ctx.body = res;
}
Copy the code
service/user.js
async detail(id){
return this.ctx.model.User.findById({_id:id})
}
Copy the code
Update user
router.js
// Modify the user
router.put('/api/user/:id',controller.user.update);
Copy the code
controller/user.js
async update() {
const id = this.ctx.params.id;
const payLoad = this.ctx.request.body;
// Call Service for business processing
await this.service.user.update(id, payLoad);
// Set the response content and response status code
ctx.body = {msg:'User modified successfully'};
}
Copy the code
service/user.js
async update(_id, payLoad) {
return this.ctx.model.User.findByIdAndUpdate(_id,payLoad);
}
Copy the code
Delete user
router.js
// Delete the user
router.delete('/api/user/:id',controller.user.delete);
Copy the code
controller/user.js
async delete() {
const id = this.ctx.params.id;
// Call Service for business processing
await this.service.user.delete(id);
// Set the response content and response status code
ctx.body = {msg:"User deleted successfully"};
}
Copy the code
service/user.js
async delete(_id){
return this.ctx.model.User.findByIdAndDelete(_id);
}
Copy the code
The middleware
configuration
Typically, middleware also has its own configuration. In the framework, a complete middleware contains configuration processing. We agreed that a middleware is a separate file placed in the app/middleware directory that requires exports a normal function that takes two arguments:
- Options: middleware configuration items that the framework will use
app.config[${middlewareName}]
Pass it in. - App: instance of the current Application Application.
module.exports = (option, app) = > {
return async function (ctx, next) {
try {
await next();
} catch (err) {
// All exceptions raise an error event on the app, and the framework logs an error
app.emit('error', err, this);
const status = err.status || 500;
// The details of the 500 errors in the build environment are not returned to the client because they may contain sensitive information
const error = status === 500 && app.config.env === 'prod' ? 'Internal Server Error' : err.message
// Read each property from the error object and set it into the response
ctx.body = {
code: status, // Server's own processing logic error (including frame error 500 and custom business logic error 533 start) client request parameter error (4xx start), set different status code
error:error
}
if(status === 422){
ctx.body.detail = err.errors;
}
ctx.status = 200}}}Copy the code
Using middleware
After the middleware is written, we also need to manually mount it. The following methods are supported:
In an application, we can load custom middleware and determine their order entirely through configuration.
If we need to load the above error_handler middleware, add the following configuration to config.default.js to complete the middleware startup and configuration:
// add your middleware config here
config.middleware = ['errorHandler'];
Copy the code
The plug-in
The plug-in mechanism is a major feature of our framework. It can not only ensure that the core of the framework is concise, stable and efficient, but also promote the reuse of business logic and the formation of the ecosystem. One might ask
- Koa already has middleware mechanisms, why do they need plug-ins?
- What are the relationships and differences between middleware, plug-ins, and applications?
- How do I use a plugin?
- How to write a plug-in?
- .
Let’s talk about each of them
Why use plug-ins
We found the following issues in using Koa middleware:
- In fact, the middleware load order, but the middleware itself can not manage the order, only to the user. This is actually very unfriendly and can make all the difference if the order is out of order.
- Middleware is positioned to intercept user requests and do things around them, such as authentication, security checks, access logging, and so on. However, the reality is that some functions are independent of the request, such as scheduled tasks, message subscriptions, background logic, and so on.
- Some features have very complex initialization logic that needs to be done at application startup time. This is obviously not a good place to implement in middleware.
In summary, we need a more powerful mechanism to manage and orchestrate the relatively independent business logic.
Relationships between middleware, plug-ins, and applications
A plug-in is essentially a “mini app”, almost the same as an app:
- It includes services, middleware, configuration, framework extensions, and so on.
- It has no separate Router and Controller.
- It has no
plugin.js
, can only declare dependencies with other plug-ins, andCan’t decideWhether to enable other plug-ins.
Their relationship is:
- Applications can be imported directly into Koa’s middleware.
- In the case of scheduled tasks, message subscription, and background logic, the application needs to introduce plug-ins.
- The plug-in itself can contain middleware.
- Multiple plug-ins can be wrapped as a single upper-layer framework.
The use of plug-in
The egg-Mongoose we used above is a plugin.
Plug-ins are typically reused as NPM modules:
npm i egg-validate -S
Copy the code
You then need to declare this in the config/plugin.js of your application or framework:
exports.validate = {
enable: true.package: 'egg-validate'};Copy the code
You can directly use the functionality provided by the plug-in:
controller/user.js
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
constructor(props) {
super(props);
this.UserCreateRule = {
username: {
type: 'string'.required: true.allowEmpty: false.// The username must contain 3 to 10 letters, underscores (_), at signs (@), and cannot start with a number
format: / ^ [A - Za - z_ @.] {3, 10} /
},
password: {
type: 'password'.require: true.allowEmpty: false.min: 6}}}async create() {
const {
ctx,
service
} = this;
// Check parameters
ctx.validate(this.UserCreateRule)
const payLoad = ctx.request.body || {};
const res = await service.user.create(payLoad);
this.ctx.helper.success({
ctx: this.ctx, res }); }}module.exports = UserController;
Copy the code
Framework extension
The Helper functions are used to provide utility functions.
What it does is it allows you to separate common actions from helper.js into separate functions, so that you can write complex logic in JavaScript without the logic being scattered around. Another benefit is that Helper, a simple function, makes it easier to write test cases.
There are some common Helper functions built into the framework. We can also write custom Helper functions.
The framework merges the objects defined in app/extend/helper.js with the prototype object of the built-in helper and generates helper objects based on the extended Prototype when processing requests.
For example, add a helper.success() method:
extend/helper.js
module.exports = {
success:function({res=null,msg='Request successful'}) {
// This is a helper object from which other helper methods can be called
// this.ctx =>context object
// this.app => Application object
this.ctx.body = {
code:200.data:res,
msg
}
this.ctx.status = 200; }}Copy the code
controller/user.js
async index() {
const res = await this.service.user.index();
this.ctx.helper.success({
res
});
}
Copy the code
Timing task
Although the HTTP Server we developed through the framework is request-response model, there are still many scenarios that require some scheduled tasks, such as:
- Report application status periodically. (Order time-out feedback, order details processing, etc.)
- Periodically update the local cache from the remote interface.
- Periodically cut files and delete temporary files.
The framework provides a mechanism to make the writing and maintenance of scheduled tasks more elegant
Writing a scheduled Task
All scheduled tasks are stored in the APP /schedule directory. Each file is an independent scheduled task. You can configure the attributes and execution methods of scheduled tasks.
As a simple example, we can create an update_cache.js file in the app/schedule directory by defining a scheduled task to update remote data to the in-memory cache
const Subscription = require('egg').Subscription;
class UpdateCache extends Subscription {
// Use the schedule attribute to set the execution interval of scheduled tasks
static get schedule() {
return {
interval: '5s'.// 1 minute interval
type: 'all'.// specify that all workers need to be executed
};
}
// subscribe is the function that is run when the actual scheduled task is executed
async subscribe() {
console.log("Task execution:" + new Date().toString());
// const res = await this.ctx.curl('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976', {
// dataType: 'json',
// })
// this.ctx.app.cache = res.data;}}module.exports = UpdateCache;
Copy the code
Can be abbreviated
module.exports = {
schedule: {
interval: '1m'.// 1 minute interval
type: 'all'.// specify that all workers need to be executed
},
async task(ctx) {
const res = await ctx.curl('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976', {
dataType: 'json'}); ctx.app.cache = res.data; }};Copy the code
This scheduled task will be executed every 1 minute on each Worker process to mount the remote data request back to app.cache.
Regular way
Timing tasks can be set to interval or CRon.
interval
Interval specifies the execution time of a scheduled task. The scheduled task will be executed at a specified interval. Interval can be configured as
- A number in milliseconds, for example
5000
. - The character type will passmsConvert to milliseconds, for example
5s
.
Module. exports = {schedule: {// execute interval every 10 seconds:'10s',}};Copy the code
cron
The schedule.cron parameter is used to set the execution time of scheduled tasks. The scheduled tasks will be executed at a specific point in time based on the CRON expression. Cron expressions are parsed by cron-parser.
Note: Cron-Parser supports optional seconds (Linux crontab does not).
* * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ | │ │ │ │ │ └ day of week (0 to 7) (0 or 7 is Sun │ │ │ │ └ ─ ─ ─ ─ ─ the month (1-12) │ │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ day of the month (1-31) │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ the adrenaline-charged (0-23) │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ minute (0-59) └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ the second (0 to 59, optional)Copy the code
Module. exports = {schedule: {// run a crON every 3 hours:'0 0 */3 * * *',}};Copy the code
type
By default, the framework supports two types of scheduled tasks, worker and All. Both workers and all support the above two timing modes, but when it comes to execution time, different workers will execute the scheduled task:
worker
Type: Only one worker on each machine will perform the scheduled task, and the selection of the worker to perform the scheduled task is random.all
Type: Each worker on each machine will perform this scheduled task.
The other parameters
In addition to the parameters just described, scheduled tasks also support these parameters:
cronOptions
For details, see configuring the crON time zonecron-parserThe documentimmediate
: If this parameter is set to true, the scheduled task will be executed immediately after the application is started and ready.disable
: If this parameter is set to true, the scheduled task will not be started.env
: array. The scheduled task is started only in the specified environment.
Dynamically configure scheduled tasks
config/config.default.js
config.cacheTick = {
interval: '5s'.// 1 minute interval
type: 'all'.// specify that all workers need to be executed
immediate: true.// If this parameter is set to true, the scheduled task will be executed immediately after the application is started and ready
// disable: true. // True indicates that the scheduled task will not be started
};
Copy the code
schedule/update_cache.js
module.exports = app= > {
return {
schedule: app.config.cacheTick,
async task(ctx) {
console.log("Task execution:" + new Date().toString()); }}};Copy the code
Start the project and view the console output.
Like old tie, add the following, blog posts will be updated regularly