egg-sequelize-next
v7.0.0
Published
Sequelize plugin for Egg 4, written in TypeScript/ESM
Maintainers
Readme
egg-sequelize-next
Sequelize plugin for Egg 4, written in TypeScript
and shipped as ESM. It is the modernized successor of
egg-sequelize, which stops at Egg 2/3.
This plugin only integrates Sequelize into Egg, please read the Sequelize documentation for the ORM itself.
- Loads every model under
app/modelintoapp.modelandctx.model. - Model loading is asynchronous on Egg 4, so it is wired into the boot hooks instead of running when the plugin file is parsed.
- Works with Egg's manifest and bundle based startups.
- Ships types for
app.model/ctx.modeland for your own models.
Install
$ npm i --save egg-sequelize-next
$ npm i --save mysql2 # for the mysql and mariadb dialects
# or use another database backend
$ npm i --save pg pg-hstore # PostgreSQL
$ npm i --save sqlite3 # SQLite
$ npm i --save tedious # MSSQLRequirements: Node.js >= 22.18.0 and Egg >= 4.
Usage & configuration
- Enable the plugin in
config/plugin.ts:
import sequelizePlugin from 'egg-sequelize-next';
export default {
...sequelizePlugin(),
};The traditional declaration keeps working too, for example in a CommonJS config/plugin.js:
exports.sequelize = {
enable: true,
package: 'egg-sequelize-next',
};- Edit your own configuration in
config/config.{env}.ts:
export default {
sequelize: {
dialect: 'mysql', // support: mysql, mariadb, postgres, mssql, sqlite
database: 'test',
host: 'localhost',
port: 3306,
username: 'root',
password: '',
// delegate: 'myModel', // load all models to `app[delegate]` and `ctx[delegate]`, default to `model`
// baseDir: 'my_model', // load all files in `app/${baseDir}` as models, default to `model`
// exclude: 'index.js', // ignore `app/${baseDir}/index.js` when loading models, support glob and array
// more sequelize options
},
};You can also configure the connection with a full URI:
export default {
sequelize: {
// the dialect and the database are taken from the uri
connectionUri: 'mysql://root:password@localhost:3306/test',
},
};The plugin merges your configuration over these defaults:
{
delegate: 'model',
baseDir: 'model',
logging(sql, timing) {
// the elapsed time is only passed when `benchmark` is enabled
const used = typeof timing === 'number' ? `(${timing}ms)` : '';
app.logger.info('[egg-sequelize-next]%s %s', used, sql);
},
host: 'localhost',
port: 3306,
username: 'root',
benchmark: true,
define: {
freezeTableName: false,
underscored: true,
},
}Model files
Models live in app/model by default, and every file is loaded as a factory that receives the
app and the sequelize instance of its datasource:
// app/model/user.js
module.exports = app => {
const { STRING, INTEGER, DATE } = app.Sequelize;
const User = app.model.define('user', {
login: STRING,
name: STRING(30),
age: INTEGER,
last_sign_in_at: DATE,
created_at: DATE,
updated_at: DATE,
});
User.findByLogin = async function(login) {
return this.findOne({ where: { login } });
};
// don't use an arrow function here
User.prototype.logSignin = async function() {
return this.update({ last_sign_in_at: new Date() });
};
return User;
};app.model is a sequelize instance, so app.model.sync(), app.model.query(), ... are all
available. In a controller, use the per-request ctx.model:
// app/controller/user.js
class UserController extends Controller {
async index() {
this.ctx.body = await this.ctx.model.User.findAll();
}
async show() {
const user = await this.ctx.model.User.findByLogin(this.ctx.params.login);
await user.logSignin();
this.ctx.body = user;
}
}Conventions
| model file | property |
| ----------------- | ------------------------ |
| user.js | app.model.User |
| person.js | app.model.Person |
| user_group.js | app.model.UserGroup |
| user/profile.js | app.model.User.Profile |
- Tables always have the timestamp fields
created_atandupdated_at. - Use the underscore style for column names, e.g.
user_id,comments_count. - Files that do not export a factory function (e.g.
module.exports = {}) are skipped.
Associate
Declare every association in Model.associate(): the plugin calls it after all models of the
datasource are loaded, so the other models are guaranteed to be available.
Multiple datasources
config.sequelize.datasources loads several databases independently:
// config/config.default.js
exports.sequelize = {
datasources: [
{
delegate: 'model', // load all models to app.model and ctx.model
baseDir: 'model', // load models from `app/model/*.js`
database: 'biz',
// other sequelize configurations
},
{
delegate: 'adminModel', // load all models to app.adminModel and ctx.adminModel
baseDir: 'admin_model', // load models from `app/admin_model/*.js`
database: 'admin',
// other sequelize configurations
},
],
};A model file that is loaded for several datasources gets the sequelize instance of the datasource as second argument:
// app/model/user.js, loaded once per datasource
module.exports = (app, model) => {
const { STRING, INTEGER } = app.Sequelize;
const User = model.define('user', {
name: STRING(30),
age: INTEGER,
});
return User;
};delegate may contain dots, e.g. subproperty.a, which groups datasources under one property
(app.subproperty.a, ctx.subproperty.a).
TypeScript
app.model and ctx.model are typed, and an application can declare its own models by augmenting
IModel:
// typings/index.d.ts
import 'egg';
import 'egg-sequelize-next';
import User from '../app/model/user';
declare module 'egg' {
interface IModel {
User: ReturnType<typeof User>;
}
}Upgrading from egg-sequelize
The configuration (delegate, baseDir, exclude, connectionUri, datasources,
Sequelize, ...) is unchanged, only the package name and the loading life cycle changed:
- Replace
egg-sequelizewithegg-sequelize-nextinpackage.jsonandconfig/plugin.js. - Models are attached to
app.modelwhen thedidLoadboot hook runs instead of when the boot file is parsed. Useapp.beforeStart()or later if your code needs the models. config.sequelize.ignoreis deprecated, useconfig.sequelize.exclude.- The
excludepatterns are matched by the plugin itself. The excluded files are still imported, but they are not registered as models. @types/sequelizeis not needed anymore, sequelize 6 ships its own types.
