Egg Learning Notes (1) - Directory Structure and Built-in Basic Objects
Directory Structure
In the quick start, you should have gained a preliminary impression of the framework. Next, let's briefly understand the directory convention specifications.
Original document address: Directory Structure
egg-project
├── package.json
├── app.js (optional)
├── agent.js (optional)
├── app
| ├── router.js
│ ├── controller
│ | └── home.js
│ ├── service (optional)
│ | └── user.js
│ ├── middleware (optional)
│ | └── response_time.js
│ ├── schedule (optional)
│ | └── my_task.js
│ ├── public (optional)
│ | └── reset.css
│ ├── view (optional)
│ | └── home.tpl
│ └── extend (optional)
│ ├── helper.js (optional)
│ ├── request.js (optional)
│ ├── response.js (optional)
│ ├── context.js (optional)
│ ├── application.js (optional)
│ └── agent.js (optional)
├── config
| ├── plugin.js
| ├── config.default.js
│ ├── config.prod.js
| ├── config.test.js (optional)
| ├── config.local.js (optional)
| └── config.unittest.js (optional)
└── test
├── middleware
| └── response_time.test.js
└── controller
└── home.test.jsBuilt-in Basic Objects
This includes the 4 objects inherited from Koa (Application, Context, Request, Response) as well as some objects extended by the framework (Controller, Service, Helper, Config, Logger).
Original document address: Framework Built-in Basic Objects
Application
The Application is the global application object. In an application, it will only be instantiated once. It inherits from Koa.Application, and we can mount some global methods and objects onto it. We can easily extend the Application object in plugins or the application.
Events
During framework runtime, some events will be triggered on the Application instance. Application developers or plugin developers can listen to these events to perform operations. As application developers, we generally listen in a custom startup script.
server: This event is triggered only once per worker process. After the HTTP server finishes starting, the HTTP server is exposed to developers through this event.error: When any exception during runtime is caught by theonerrorplugin, anerrorevent will be triggered. It exposes the error object and the associated context (if any) to developers, allowing custom error logging and reporting.requestandresponse: When the application receives a request and responds to a request, it triggersrequestandresponseevents, respectively, and exposes the current request context. Developers can listen to these two events for logging.
// app.js
module.exports = (app) => {
app.once("server", (server) => {
// websocket
});
app.on("error", (err, ctx) => {
// report error
});
app.on("request", (ctx) => {
// log receive request
});
app.on("response", (ctx) => {
// ctx.starttime is set by framework
const used = Date.now() - ctx.starttime;
// log total cost
});
};Access Methods
The Application object can be accessed from almost anywhere when writing an application. Here are some commonly used access methods:
Almost all files loaded by the framework Loader (Controller, Service, Schedule, etc.) can export a function. This function will be called by the Loader and uses app as an argument:
// app.js
module.exports = (app) => {
app.cache = new Cache();
};Controller file:
// app/controller/user.js
class UserController extends Controller {
async fetch() {
this.ctx.body = this.app.cache.get(this.ctx.query.id);
}
}Or, you can access the Application object via ctx.app:
// app/controller/user.js
class UserController extends Controller {
async fetch() {
this.ctx.body = this.ctx.app.cache.get(this.ctx.query.id);
}
}Context
Context is a request-level object that inherits from Koa.Context. Every time a user request is received, the framework instantiates a Context object. This object encapsulates the information of this user request and provides many convenient methods to obtain request parameters or set response information. The framework will mount all Services onto the Context instance, and some plugins will also mount other methods and objects onto it (e.g., egg-sequelize will mount all models onto Context).
Access Methods
The most common way to access the Context instance is in Middleware, Controller, and Service. The access method in Controller has already been shown in the examples above. The access method in Service is the same as in Controller. Getting the Context instance in Middleware is identical to how the Koa framework accesses the Context object in middleware.
The framework's Middleware also supports Koa v2 middleware syntax:
// Koa v2
async function middleware(ctx, next) {
// ctx is instance of Context
console.log(ctx.query);
}Request & Response
Request is a request-level object inheriting from Koa.Request. It encapsulates Node.js's native HTTP Request object and provides a series of auxiliary methods to obtain common HTTP request parameters.
Response is a request-level object inheriting from Koa.Response. It encapsulates Node.js's native HTTP Response object and provides a series of auxiliary methods to set HTTP responses.
Access Methods
You can get the current request's Request (ctx.request) and Response (ctx.response) instances on the Context instance.
// app/controller/user.js
class UserController extends Controller {
async fetch() {
const { app, ctx } = this;
const id = ctx.request.query.id;
ctx.response.body = app.cache.get(id);
}
}Koa proxies some methods and properties of Request and Response onto Context. See Koa.Context.
For example, ctx.request.query.id and ctx.query.id in the example above are equivalent, and ctx.response.body= and ctx.body= are equivalent.
Note that you should use ctx.request.body instead of ctx.body to get the body of a POST request.
Controller
The framework provides a Controller base class and recommends that all Controllers inherit from this base class. This Controller base class has the following properties:
ctx- TheContextinstance of the current request.app- TheApplicationinstance of the application.config- The application's configuration.service- All services of the application.logger- The logger object encapsulated for the current controller.
In the Controller file, you can reference 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;
// Get from the app instance
module.exports = (app) => {
return class UserController extends app.Controller {
// implement
};
};Service
The framework provides a Service base class and recommends that all Services inherit from this base class.
The properties of the Service base class are identical to those of the Controller base class, and the access method is similar:
// app/service/user.js
// Get from egg (Recommended)
const Service = require("egg").Service;
class UserService extends Service {
// implement
}
module.exports = UserService;
// Get from the app instance
module.exports = (app) => {
return class UserService extends app.Service {
// implement
};
};Helper
Helper is used to provide some practical utility functions. Its purpose is that we can extract some commonly used actions into helper.js as independent functions. This allows us to write complex logic in JavaScript, preventing logic from being scattered everywhere, while also making it easier to write test cases.Helper itself is a class with the same properties as the Controller base class. It will also be instantiated on every request, so all functions on Helper can also access the context information related to the current request.
Access Methods
You can get the current request's Helper (ctx.helper) instance on the Context instance.
// app/controller/user.js
class UserController extends Controller {
async fetch() {
const { app, ctx } = this;
const id = ctx.query.id;
const user = app.cache.get(id);
ctx.body = ctx.helper.formatUser(user);
}
}In addition, the Helper instance can also be accessed in templates. For example, you can get the shtml method provided by the security plugin in the template.
// app/view/home.nj
{
{
helper.shtml(value);
}
}Custom Helper Methods
During application development, we might often need to customize some helper methods, such as formatUser in the example above. We can customize helper methods through framework extension.
// app/extend/helper.js
module.exports = {
formatUser(user) {
return only(user, ["name", "phone"]);
},
};Config
We recommend that application development follows the principle of separating configuration and code, placing business configurations that require hardcoding into the configuration file. Meanwhile, the configuration file supports using different configurations in various runtime environments, which is highly convenient.
Access Methods
We can get the config object from the Application instance via app.config, or through this.config on the Controller, Service, and Helper instances.
Framework, plugin, and application-level configurations can all be obtained through the Config object. For more detailed information on framework configuration, please read the Config Configuration chapter.
Logger
The framework has powerful built-in logging capabilities that can easily print various levels of logs to corresponding log files. Every logger object provides methods for 4 levels:
logger.debug()logger.info()logger.warn()logger.error()
The framework provides multiple Logger objects. Below is a brief introduction to the access methods and use cases for each Logger object.
App Logger
We can get it via app.logger. If we want to perform application-level logging, such as recording some data information during the startup phase or recording business information unrelated to the request, we can use the App Logger.
App CoreLogger
We can get it via app.coreLogger. Generally, when developing an application, we shouldn't use CoreLogger to print logs. Instead, the framework and plugins need to use it to print application-level logs so that logs printed by the application and the framework can be more clearly distinguished. Logs printed by CoreLogger will be placed in a different file than those from Logger.
Context Logger
We can get it from the Context instance via ctx.logger. From the access method, we can see that the Context Logger is definitely related to the request. The logs it prints will be prefixed with some information related to the current request (like [$userId/$ip/$traceId/${cost}ms $method $url]). Through this information, we can quickly locate the request from the logs and link all the logs within a single request.
Context CoreLogger
We can get it via ctx.coreLogger. The difference between it and the Context Logger is that generally, only plugins and the framework will use it to record logs.
Controller Logger & Service Logger
We can get them on Controller and Service instances via this.logger. They are essentially Context Loggers, but when printing logs, they will also append the file path to make it easier to locate where the log was printed.
Subscription
The subscription model is a relatively common development pattern, such as message middleware consumers or scheduled tasks. Therefore, we provide a Subscription base class to standardize this pattern.
You can reference the Subscription base class as follows:
const Subscription = require("egg").Subscription;
class Schedule extends Subscription {
// This method needs to be implemented
// subscribe can be an async function or a generator function
async subscribe() {}
}Plugin developers can customize subscription specifications based on it according to their needs. For example, Scheduled Tasks are implemented using this specification.
AI Translation | AI 翻译
This article was translated from Chinese to English by AI. If there are any inaccuracies, please refer to the original Chinese version.
本文由 AI 辅助从中文翻译为英文。如遇不准确之处,请以中文原版为准。
