使用 egg.js 简单配置即可实现一个 Web 服务器

环境准备

egg.js 支持 macOS、Linux、Windows,Node.js 最低 8.x

初始化

初始化 egg.js 可以使用官方推荐的脚手架

  1. $ mkdir egg-example && cd egg-example
  2. $ npm init egg --type=simple
  3. $ npm i

npm init egg 相当于执行 npx create-egg ,npx 在 npm 5.2.0 后支持,可以在不安装包的情况下执行其二进制文件

脚手架支持多种类型 egg.js 项目创建,对于 Web 服务器可以使用 simple

  • simple - Simple egg app boilerplate
  • microservice - Microservice app boilerplate based on egg
  • sequelize - egg app with sequelize
  • ts - Simple egg && typescript app boilerplate
  • empty - Empty egg app boilerplate
  • plugin - egg plugin boilerplate
  • framework - egg framework boilerplate

    目录结构

    初始化后可以得到主要文件如下的目录结构
    1. egg-example
    2. ├── package.json
    3. ├── app
    4. | ├── router.js
    5. ├── controller
    6. | └── home.js
    7. ├── config
    8. | ├── plugin.js
    9. | ├── config.default.js
    10. └── test
    11. └── controller
    12. └── app
    13. └── home.test.js

    路由设置

    app/router.js 是用户请求的起点,脚手架初始化了一个首页的路由
    1. module.exports = app => {
    2. const { router, controller } = app;
    3. router.get('/', controller.home.index);
    4. };
    controller.home.index 的含义是 app/controller/home.js 文件的 index 函数,这是 egg.js 约定的一部分,按照约定路由还可以写成如下形式
    1. module.exports = app => {
    2. app.get('/', 'home.index');
    3. };
    这样的书写更为简单,约定了所有的 Controller 都在 app/controller 目录下

    MVP 模式

    在示例代码中所有的页面生成逻辑都写在了 Controller(虽然文件夹名字是 controller,但实际作用是 MVP 模式中的 presenter) 中,按照 egg.js 目录约定可以添加几个文件,轻松实现 MVP 模式
    1. egg-example
    2. ├── package.json
    3. ├── app
    4. | ├── router.js
    5. ├── controller
    6. | └── home.js
    7. ├── service
    8. | └── home.js
    9. ├── view
    10. | └── home.hbs
    11. ├── config
    12. | ├── plugin.js
    13. | ├── config.default.js
    14. └── test
    15. └── controller
    16. └── app
    17. └── home.test.js
  1. app/view 目录可以存放 View 相关的模板文件
  2. app/service 目录存放 Model 相关的 js 文件

    service 实现

    修改 app/service/home.js 为 ```javascript ‘use strict’;

const Service = require(‘egg’).Service;

class HomeService extends Service { async getUser() { return ‘Sunluyong’; } }

module.exports = HomeService;

  1. 文件 export 的类继承 `require('egg').Service` 即可实现一个 Model,内部定义 Presenter 会用到的数据处理方法,支持 async/await 的写法
  2. 和具体业务逻辑无关的数据处理,比如数据库查询、文件读写等可以定义在 service
  3. <a name="87MFZ"></a>
  4. ### view 实现
  5. View 的渲染不变使用模板引擎,egg.js 本身并不包含模板引擎的实现,而是通过[渲染插件](https://www.npmjs.com/search?q=egg-view-)来实现,我们选择使用 [egg-view-handlebars](https://www.npmjs.com/package/egg-view-handlebars) 示例
  6. > egg.js 支持的模板引擎:[https://github.com/topics/egg-view](https://github.com/topics/egg-view)
  7. <a name="5iB5v"></a>
  8. #### 1. 安装插件
  9. ```bash
  10. npm i egg-view-handlebars --save

2. 启用插件

插件安装后需要显式的启用声明才会正常工作,修改文件 /config/plugins.js

  1. module.exports = {
  2. handlebars: {
  3. enable: true,
  4. package: 'egg-view-handlebars',
  5. },
  6. };

3. 配置 View 渲染选项

修改应用配置文件 /config/config.default.js 中关于 View 渲染相关的设置,把 handlebars 配置为默认的渲染引擎

  1. config.view = {
  2. defaultViewEngine: 'handlebars',
  3. defaultExtension: '.hbs',
  4. mapping: {
  5. '.hbs': 'handlebars',
  6. },
  7. };

4. 修改模板文件

修改 app/view/home.hbs

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>{{title}}</title>
  7. </head>
  8. <body>
  9. Hi {{user}}, welcom to use egg.js.
  10. </body>
  11. </html>

presenter

presenter 用于接收用户请求进行业务逻辑处理,获取数据后驱动视图渲染,对 app/controller/home.js 的 index 方法做调整,渲染用户名称

  1. const Controller = require('egg').Controller;
  2. class HomeController extends Controller {
  3. async index() {
  4. const { ctx } = this;
  5. const user = await ctx.service.home.getUser();
  6. await this.ctx.render('home', {
  7. title: 'Egg.js demo homepage',
  8. user,
  9. });
  10. }
  11. }
  12. module.exports = HomeController;

和 Koa 非常类似,egg.js 也把请求级对象挂载到了 this.ctx ,应用全局设置挂载到了 this.app ,在代码中使用了 egg.js 的几个约定

  1. app/service 下文件 export 的 class 会被挂载到 this.ctx.service.fileName ,文件名会自动转成驼峰格式,无需手工配置
  2. await this.ctx.render() 等同于 ctx.body = await this.ctx.render() ,用于设置 HTTP response body

    官方建议

    其实在 egg.js 官方建议中

  3. controller 用来解析用户输入,处理返回结果

  4. service 用来处理业务逻辑
  5. model 才是用来处理业务无关的数据处理(ORM)

但在普通规模的业务实现中按照上述目录结构,大部分方法需要层层调用,比较繁琐,在普通项目中可以按照上文介绍的方式书写代码,涉及 ORM 时候可以引入 model 层

程序启动

脚手架在 package.json 中设置了几个开发、调试的 scripts

  1. "scripts": {
  2. "start": "egg-scripts start --daemon --title=egg-server-example",
  3. "stop": "egg-scripts stop --title=egg-server-example",
  4. "dev": "egg-bin dev",
  5. "debug": "egg-bin debug",
  6. "test": "npm run lint -- --fix && npm run test-local",
  7. "test-local": "egg-bin test",
  8. "cov": "egg-bin cov",
  9. "lint": "eslint .",
  10. "ci": "npm run lint && npm run cov",
  11. "autod": "autod"
  12. }

在日常开发时候可以使用 npm run dev 进行开发,默认应用会在 7001 端口启动,文件修改时应用会自动重新启动

  1. [egg-ts-helper] create typings/app/controller/index.d.ts (2ms)
  2. [egg-ts-helper] create typings/config/index.d.ts (14ms)
  3. [egg-ts-helper] create typings/config/plugin.d.ts (1ms)
  4. [egg-ts-helper] create typings/app/service/index.d.ts (1ms)
  5. [egg-ts-helper] create typings/app/index.d.ts (0ms)
  6. 2020-07-12 19:18:14,138 INFO 605 [master] node version v14.5.0
  7. 2020-07-12 19:18:14,138 INFO 605 [master] egg version 2.27.0
  8. 2020-07-12 19:18:14,875 INFO 605 [master] agent_worker#1:607 started (733ms)
  9. 2020-07-12 19:18:15,753 INFO 605 [master] egg started on http://127.0.0.1:7001 (1615ms)

调试

egg.js 的调试也非常方便,执行 npm run debug 就可进入调试流程,具体操作和普通 Node.js 调试没区别

官方文档还介绍了使用 debug 模块、WebStorm、VSCode 的调试方法,具体可以参考 Egg.js 本地开发

总结

这样就实现了一个简单的 MVP 模式 Web 应用,完整代码参考 https://github.com/Samaritan89/egg-demo/tree/v1

官方快速入门教程