Egg.js 简介

插件与扩展

理解 Egg.js 的插件机制,学会安装配置 egg-validate 做参数校验,并用 extend 封装统一响应格式。

🎯 引言

学完这篇文章,你能说清楚 Egg 的插件机制是怎么回事,会用 egg-validate 插件给接口加上参数校验,还能通过 extend 给框架对象扩展方法,封装出统一的响应格式。


🧱 插件机制是什么

Egg 本身非常精简,很多能力都是以插件的形式组装的:你前面遇到的 bodyParser、安全校验,其实都是内置插件。

插件就是一个可复用的功能包:别人写好后发布到 npm,我们安装并在配置里启用,就能直接使用它提供的能力。整个流程固定为两步:

  1. npm install 安装插件包。
  2. config/plugin.js 中声明启用。

接下来用一个高频场景来体验:参数校验。


🛠 用 egg-validate 做参数校验

接口收到请求体后,第一件事是检查参数是否完整、类型是否正确。手写一堆 if 判断很繁琐,egg-validate 插件可以用声明式的规则完成校验。

以下基于 Node.js v22 LTS、egg 3.x、egg-validate 2.0.2 验证通过。先安装:

npm install egg-validate

然后在 config/plugin.js 中启用:

config/plugin.js
module.exports = {
    validate: {
        enable: true,
        package: 'egg-validate',
    },
};

启用后,ctx 上就多了一个 validate 方法。在 Controller 里声明校验规则:

app/controller/user.js
class UserController extends Controller {
    async create() {
        const { ctx } = this;

        // 默认校验 ctx.request.body:name 必须是字符串,age 必须是数字
        ctx.validate({
            name: { type: 'string', required: true },
            age: { type: 'number', required: true },
        });

        const user = await ctx.service.user.create(ctx.request.body);
        ctx.status = 201;
        ctx.body = user;
    }
}

用 curl 发一个缺少 age 的请求试试:

curl -X POST http://127.0.0.1:7001/users \
    -H 'Content-Type: application/json' \
    -d '{"name":"小刚"}'

校验不通过时会抛出 422 错误,后面的代码不会执行。想返回自定义的错误格式,用 try/catch 接住:

app/controller/user.js
async create() {
    const { ctx } = this;

    try {
        ctx.validate({
            name: { type: 'string', required: true },
            age: { type: 'number', required: true },
        });
    } catch (err) {
        ctx.status = 422;
        ctx.body = { message: '参数不完整或格式不正确' };
        return;
    }

    // 校验通过后的逻辑
}

🧱 extend 扩展是什么

插件是引入别人的能力,extend 则是自己动手给框架的内置对象添加方法。Egg 允许我们在 app/extend 目录下扩展这些常用对象:

文件扩展的对象典型用途
application.jsapp全局共享的方法或数据
context.jsctx每个请求可用的工具方法
helper.jsctx.helper视图和响应相关的辅助函数
request.jsctx.request请求相关的扩展
response.jsctx.response响应相关的扩展

其中比较常用的是 helper.js,我们用它来解决一个实际问题:统一响应格式。


🛠 封装统一响应格式

一个项目里,所有接口的返回格式应该统一,前端处理起来才省心。常见的约定是:

{ "code": 0, "data": {}, "message": "ok" }

code 为 0 表示成功,非 0 表示失败。如果每个接口都手写这个结构太重复,把它封装成 helper 方法:

app/extend/helper.js
module.exports = {
    // 成功响应
    success(data = null, message = 'ok') {
        this.ctx.body = { code: 0, data, message };
    },

    // 失败响应
    fail(message = '操作失败', code = 1) {
        this.ctx.body = { code, data: null, message };
    },
};

Controller 里调用起来非常简洁:

app/controller/user.js
class UserController extends Controller {
    async show() {
        const { ctx } = this;
        const user = await ctx.service.user.findById(ctx.params.id);

        if (!user) {
            ctx.status = 404;
            ctx.helper.fail('用户不存在');
            return;
        }

        ctx.helper.success(user);
    }
}
在 extend 文件的方法里,this 指向被扩展的对象本身。helper 中通过 this.ctx 就能拿到上下文,设置响应、读取配置都没问题。

🛠 扩展 context 示例

再给 ctx 扩展一个实用方法:判断当前请求是不是 Ajax 请求。新建 app/extend/context.js

app/extend/context.js
module.exports = {
    // getter 写法:使用时像访问属性一样 ctx.isAjax
    get isAjax() {
        return this.get('X-Requested-With') === 'XMLHttpRequest';
    },
};

之后在任何 Controller、Service 里都能直接用 ctx.isAjax。可以看到,extend 的写法就是导出一个普通对象,Egg 会把它合并到对应对象上。


🧾 小节总结

  • Egg 的能力大量通过插件提供,安装后在 config/plugin.js 中声明启用。
  • egg-validate 提供 ctx.validate 方法,用声明式规则校验参数,失败抛出 422,可用 try/catch 自定义错误返回。
  • extend 用于给框架内置对象扩展方法,文件放在 app/extend 下,按文件名对应扩展对象。
  • app/extend/helper.js 封装 success / fail,可以让全项目的响应格式统一。
  • extend 方法中的 this 指向被扩展的对象,helper 里用 this.ctx 访问上下文。

❓ 知识问答

Q1:插件和中间件有什么区别?

中间件只处理请求链条上的一环;插件是一个完整的功能包,可以包含中间件、extend、配置、定时任务等多种内容。可以理解为:插件是更大的组织单位。

Q2:ctx.validate 校验的是哪份数据?

默认校验 ctx.request.body。想校验其他数据可以传第二个参数,例如 ctx.validate(rules, ctx.query)

Q3:extend 定义的方法名会和框架自带方法冲突吗?

会的,同名会覆盖框架原有方法,容易引发隐蔽问题。起名时尽量用业务相关的名字,如 successfail,避开 bodystatus 这类已有属性。

Q4:helper 和 Service 都能封装逻辑,怎么选?

Service 封装的是业务逻辑(和数据、规则相关),helper 封装的是和请求响应相关的工具方法(如格式化输出、统一响应)。拿不准时,业务归 Service,展示辅助归 helper。


🧪 小练习

  1. 在 helper 中新增一个 page(list, total) 方法,返回 { code: 0, data: { list, total }, message: 'ok' } 的分页格式。
app/extend/helper.js
module.exports = {
    // success、fail 保持原有实现

    page(list, total) {
        // 请在这里编写代码
    },
};
  1. create 接口加上校验规则:name 长度在 2 到 10 之间(提示:规则支持 minmax)。
app/controller/user.js
ctx.validate({
    // 请在这里编写代码
});

🎉 恭喜你已经掌握插件与扩展的用法啦!下一篇是综合实战:用前面学过的全部知识,实现一个完整的用户管理 RESTful 接口。