Mastering EggJS: From Reusable Code to Scalable Plugins and Custom Frameworks
This article walks through EggJS’s core design principles, demonstrates building a simple GitHub aggregation API, shows how to evolve reusable code into plugins and custom frameworks, and explains progressive development practices for large‑scale enterprise applications, highlighting configuration, plugin management, and framework layering.
Mastering EggJS: Design, Plugins, and Custom Frameworks
This article, originally shared by Ant Group frontend engineer Tianzhu, introduces EggJS’s design philosophy and demonstrates how a reusable code segment can continuously evolve during development.
Core concept: Application logic → incubate plugin prototype → independent plugin → built into a custom framework.
Example Application
Initialize the project with the scaffold:
$ npm init egg --type=simple showcase
./showcase
├── app
│ ├── controller (controller)
│ │ └── home.js
│ ├── service (business logic)
│ │ └── github.js
│ └── router.js (routing)
├── config (configuration)
│ ├── config.default.js
│ ├── config.prod.js
│ └── plugin.js
├── test
├── README.md
└── package.jsonRouter and Controller
Controller logic:
const { Controller } = require('egg');
class HomeController extends Controller {
async listReposByOrg() {
// get query param
const org = this.ctx.query.org || 'eggjs';
// call service
const result = await this.ctx.service.github.listReposByOrg(org);
// render response
this.ctx.body = result;
}
}
module.exports = HomeController;Router mapping:
module.exports = app => {
// register route
app.router.get('/api/repos', app.controller.home.listReposByOrg);
};Service Logic
const { Service } = require('egg');
class GithubService extends Service {
async listReposByOrg(org) {
const { ctx, config } = this;
const { endpoint, pageCount } = config.github;
const repos = await ctx.curl(`${endpoint}/orgs/${org}/repos`, {
data: { per_page: pageCount },
dataType: 'json',
});
if (repos.status !== 200) return [];
return repos.data.map(repo => repo.name);
}
}
module.exports = GithubService;Configuration
// config/config.default.js
exports.github = {
endpoint: 'https://api.github.com',
pageCount: 5,
};
// config/config.prod.js
exports.github = {
pageCount: 50,
};Running the Application
$ npm run dev
[master] egg started on http://127.0.0.1:7001 (2216ms)
$ curl localhost:7001/api/repos
["eslint-config-egg","egg","egg-bin","egg-mock","egg-logger"]
$ curl localhost:7001/api/repos?org=vuejs
["vue","vue-router","vuejs.org","vue-touch","Discussion"]Plugins
Egg plugins are a core capability that can encapsulate functionality beyond request middleware, such as scheduled tasks, initialization, or application‑wide services.
Install a view plugin: $ npm i --save egg-view-nunjucks Configure the plugin and view engine:
// config/plugin.js
exports.nunjucks = {
enable: true,
package: 'egg-view-nunjucks',
};
// config/config.default.js
exports.view = {
defaultViewEngine: 'nunjucks',
mapping: {
'.tpl': 'nunjucks',
},
};Update the controller to render a template:
class HomeController extends Controller {
async listReposByOrg() {
const org = this.ctx.query.org || 'eggjs';
const result = await this.ctx.service.github.listReposByOrg(org);
await this.ctx.render('repos.tpl', { org, result });
}
}Template (app/view/repos.tpl):
<h1>{{ org }}</h1>
<ul>
{% for item in result %}
<li>{{ item }}</li>
{% endfor %}
</ul>Progressive Development – Plugin Evolution
EggJS supports a progressive evolution pattern:
Reusable code → In‑app plugin prototype → Independent plugin → Integrated into custom frameworkExample: move the GitHub service and its configuration into a plugin directory plugin/egg-github and mount it via plugin configuration:
// config/plugin.js
exports.github = {
enable: true,
path: path.join(__dirname, '../plugin/egg-github'),
};Later the plugin can be published as an npm package and reused by other applications:
$ npm i --save egg-github
// config/plugin.js
exports.github = {
enable: true,
package: 'egg-github',
};Custom Frameworks
When multiple plugins become stable, they can be packaged into a higher‑level framework (e.g., yadan) that provides a unified API and conventions for a specific business domain.
Frameworks share the same directory structure as applications and support multi‑level inheritance, allowing application logic to be seamlessly sunk into the framework.
Custom loader example to auto‑load utilities:
// config/config.default.js
exports.customLoader = {
utils: {
directory: 'app/utils',
inject: 'app',
},
};Such conventions enable a consistent development experience across teams while allowing differentiated implementations (e.g., different template engines or user models) behind stable APIs like ctx.render() or ctx.user.
Conclusion
EggJS provides a “framework of frameworks” that helps architects build scalable, customizable solutions.
The progressive development flow—from reusable code to plugins and finally to custom frameworks—requires only minimal configuration changes.
This approach supports enterprise‑level multi‑application management, a consistent developer experience, and sustainable maintenance.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alipay Experience Technology
Exploring ultimate user experience and best engineering practices
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
