当前位置:网站首页>egg. JS learning notes
egg. JS learning notes
2022-07-04 16:55:00 【Liu Chu, Ge Nian】
List of articles
- install egg
- Directory structure
- Routing related
- Redirect
- controller
- template engine
- service ( Model )
- Models and databases
- Configure and create migration files
- Associated operations
- Getters and modifiers
- Model hook
- Inquire about
- Primary key query
- If the search does not exist, create
- Find and count
- Query multiple ( Commonly used )
- Composite filtration / OR / NOT Inquire about
- Use restrictions , The offset , Sequential and grouping operation data sets
- Field filtering
- Where
- Pagination / Limit
- Sort
- `count` - Count the number of occurrences of elements in the database
- `max` - Get the maximum value of a specific attribute in a specific table
- `min` - Get the minimum value of a specific attribute in a specific table
- `sum` - Sum the values of a particular attribute
- Preloading
- newly added
- modify
- Delete
- Overload instance
- Model customization method
- Scopes - Scope ( a key )
- Expand
- middleware
- Form submission
- cookie
- session
- Timing task
- API
- Common plug-ins
install egg
We recommend using scaffolding directly , Just a few simple instructions , You can quickly build projects (npm >=6.1.0
):
mkdir egg-example && cd egg-example
npm init egg --type=simple
npm i
Start project :
npm run dev
open http://localhost:7001
Directory structure
egg-project
├── package.json
├── app.js ( Optional )
├── agent.js ( Optional )
├── app(----------- The core ------------)
| ├── router.js( route )
│ ├── controller( controller )
│ | └── home.js
│ ├── service ( Model )
│ | └── user.js
│ ├── middleware ( middleware )
│ | └── response_time.js
│ ├── schedule ( Optional )
│ | └── my_task.js
│ ├── public ( Static resources )
│ | └── reset.css
│ ├── view ( Template view )
│ | └── home.tpl
│ └── extend ( Expand )
│ ├── 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.js
Routing related
1. get Pass value
// router.js
router.get('/admin/:id', controller.admin.index);
// controller
async index(ctx) {
// Get route get Pass value parameters ( route :id)
ctx.params;
// obtain url Question mark of get Pass value parameters
ctx.query;
}
2. 4 Configuration methods
router.verb('path-match', app.controller.action);
router.verb('router-name', 'path-match', app.controller.action);// The first parameter can be given to name
router.verb('path-match', middleware1, ..., middlewareN, app.controller.action);
router.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.controller.action);
Redirect
1. ctx
async index() {
this.ctx.status = 301; // Change redirection to 301
this.ctx.redirect('/admin/add'); // Default temporary redirection 302
}
2. Route redirection
app.router.redirect('/', '/home/index', 302);
3. Routing grouping
// app/router.js
module.exports = app => {
require('./router/news')(app);
require('./router/admin')(app);
};
// app/router/news.js
module.exports = app => {
app.router.get('/news/list', app.controller.news.list);
app.router.get('/news/detail', app.controller.news.detail);
};
// app/router/admin.js
module.exports = app => {
app.router.get('/admin/user', app.controller.admin.user);
app.router.get('/admin/log', app.controller.admin.log);
};
controller
Customize Controller Base class
// app/core/base_controller.js
const {
Controller } = require('egg');
class BaseController extends Controller {
get user() {
return this.ctx.session.user;
}
success(data) {
this.ctx.body = {
success: true,
data,
};
}
notFound(msg) {
msg = msg || 'not found';
this.ctx.throw(404, msg);
}
}
module.exports = BaseController;
At this time, the Controller when , Inherit BaseController, Use methods on base classes directly :
//app/controller/post.js
const Controller = require('../core/base_controller');
class PostController extends Controller {
async list() {
const posts = await this.service.listByUser(this.user);
this.success(posts);
}
}
template engine
1. Installation and use ejs
(1) install :
npm i egg-view-ejs --save
(2) To configure :/config
config/config.default.js
module.exports = appInfo => {
...
config.view = {
mapping: {
'.html': 'ejs',
},
};
...
};
config/plugin.js
module.exports = {
// To configure ejs
ejs: {
enable: true,
package: 'egg-view-ejs',
}
};
(3) Use
app/controller
async index() {
const {
ctx } = this;
// Render variables
let msg = " Test content ";
let list = [1, 2, 3, 4, 5, 6];
// Apply colours to a drawing template (render Need to add await)
await ctx.render('index', {
msg,
list
});
}
app/view/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!-- Render variables -->
<%=msg%>
<ul>
<% for(var i=0; i < list.length; i++){ %>
<li>
<%=list[i]%>
</li>
<% } %>
</ul>
<!-- load app/public Next resource file -->
<img src="/public/images/find.png">
</body>
</html>
service ( Model )
Controller call home
Model ceshi
Method
await this.service.home.ceshi();
Models call each other ( ditto )
Models and databases
Configure and create migration files
To configure
- Install and configure egg-sequelize plug-in unit ( It will help us define Model Object loaded into app and ctx On ) and mysql2 modular :
npm install --save egg-sequelize mysql2
- stay
config/plugin.js
Introduction in egg-sequelize plug-in unit
exports.sequelize = {
enable: true,
package: 'egg-sequelize',
};
- stay
config/config.default.js
config.sequelize = {
dialect: 'mysql',
host: '127.0.0.1',
username: 'root',
password: 'root',
port: 3306,
database: 'friends',
// China time zone
timezone: '+08:00',
define: {
// Cancels the plural of the data table name
freezeTableName: true,
// Auto write timestamp created_at updated_at
timestamps: true,
// Field generates a soft delete timestamp deleted_at
paranoid: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
deletedAt: 'deleted_at',
// All humps are named
underscored: true
}
};
- sequelize Provides sequelize-cli Tools to implement Migrations, We can also do that egg Introduced in the project sequelize-cli.
npm install --save-dev sequelize-cli
- egg In the project , We want to put all the databases Migrations The relevant content is put in
database
Under the table of contents , So we create a new one in the root directory of the project.sequelizerc
The configuration file :
'use strict';
const path = require('path');
module.exports = {
config: path.join(__dirname, 'database/config.json'),
'migrations-path': path.join(__dirname, 'database/migrations'),
'seeders-path': path.join(__dirname, 'database/seeders'),
'models-path': path.join(__dirname, 'app/model'),
};
- initialization Migrations Configuration files and directories
npx sequelize init:config
npx sequelize init:migrations
// npx sequelize init:models
- After the line is completed, it will generate
database/config.json
Document anddatabase/migrations
Catalog , So let's revise thatdatabase/config.json
The content in , Change it to the database configuration used in our project :
{
"development": {
"username": "root",
"password": null,
"database": "test",
"host": "127.0.0.1",
"dialect": "mysql",
"timezone": "+08:00"
}
}
- Create database
npx sequelize db:create
Create data migration table
npx sequelize migration:generate --name=init-users
1. After executing the order , Will be in database / migrations / Generate data table migration file under Directory , Then define
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
const {
INTEGER, STRING, DATE, ENUM } = Sequelize;
// Create table
await queryInterface.createTable('users', {
id: {
type: INTEGER(20).UNSIGNED, primaryKey: true, autoIncrement: true },
username: {
type: STRING(30), allowNull: false, defaultValue: '', comment: ' User name ', unique: true},
email: {
type: STRING(160), allowNull: false, defaultValue: '', comment: ' User mailbox ', unique: true },
password: {
type: STRING(200), allowNull: false, defaultValue: '' },
avatar_url: {
type: STRING(200), allowNull: true, defaultValue: '' },
mobile: {
type: STRING(20), allowNull: false, defaultValue: '', comment: ' User's mobile phone ', unique: true },
prifix: {
type: STRING(32), allowNull: false, defaultValue: '' },
abstract: {
type: STRING(255), allowNull: true, defaultValue: '' },
role_id:{
type: INTEGER,
// Defining foreign keys ( important )
references: {
model: 'users', // Corresponding table name ( Data table name )
key: 'id' // The primary key of the corresponding table
},
onUpdate: 'restrict', // Operation during update
onDelete: 'cascade' // When deleting
},
gender: {
type: ENUM, values: [' male ',' Woman ',' A secret '], allowNull: true, defaultValue: ' male ', comment: ' User's gender '},
created_at: DATE,
updated_at: DATE
}, {
engine: 'MYISAM' });
// Add index
queryInterface.addIndex('users', ['gender']);
// Add unique index
queryInterface.addIndex('users', {
name: "name", // The index name
unique: true, // unique index
fields: ['name'] // Index corresponding fields
});
},
down: async queryInterface => {
await queryInterface.dropTable('users')
}
};
- perform migrate Make database changes
# Upgrade database
npx sequelize db:migrate
# Rollback if there is a problem , Can pass `db:migrate:undo` Back off a change
# npx sequelize db:migrate:undo
# Can pass `db:migrate:undo:all` Go back to the initial state
# npx sequelize db:migrate:undo:all
New field created
1. Create migration file :
npx sequelize migration:generate --name=user-addcolumn
2. After executing the order , Will be in database / migrations / Generate data table migration file under Directory , Then define
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.addColumn('user', 'role_id', {
type: Sequelize.INTEGER
}, {
transaction: t }),
queryInterface.addColumn('user', 'ceshi', {
type: Sequelize.STRING,
}, {
transaction: t })
])
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.removeColumn('user', 'role_id', {
transaction: t }),
queryInterface.removeColumn('user', 'ceshi', {
transaction: t })
])
})
}
};
3. perform migrate Make database changes
npx sequelize db:migrate
Creating models
// app / model / user.js
'use strict';
module.exports = app => {
const {
STRING, INTEGER, DATE } = app.Sequelize;
// To configure ( important : Be sure to configure details , must do !!!)
const User = app.model.define('user', {
id: {
type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
},{
timestamps: true, // Whether to write timestamp automatically
tableName: 'users', // Custom data table name
});
return User;
};
This Model You can go to Controller and Service Pass through app.model.User
perhaps ctx.model.User
The visit came to , For example, we write app/controller/users.js
:
// app/controller/users.js
const Controller = require('egg').Controller;
function toInt(str) {
if (typeof str === 'number') return str;
if (!str) return str;
return parseInt(str, 10) || 0;
}
class UserController extends Controller {
async index() {
const ctx = this.ctx;
const query = {
limit: toInt(ctx.query.limit), offset: toInt(ctx.query.offset) };
ctx.body = await ctx.model.User.findAll(query);
}
async show() {
const ctx = this.ctx;
ctx.body = await ctx.model.User.findByPk(toInt(ctx.params.id));
}
async create() {
const ctx = this.ctx;
const {
name, age } = ctx.request.body;
const user = await ctx.model.User.create({
name, age });
ctx.status = 201;
ctx.body = user;
}
async update() {
const ctx = this.ctx;
const id = toInt(ctx.params.id);
const user = await ctx.model.User.findByPk(id);
if (!user) {
ctx.status = 404;
return;
}
const {
name, age } = ctx.request.body;
await user.update({
name, age });
ctx.body = user;
}
async destroy() {
const ctx = this.ctx;
const id = toInt(ctx.params.id);
const user = await ctx.model.User.findByPk(id);
if (!user) {
ctx.status = 404;
return;
}
await user.destroy();
ctx.status = 200;
}
}
module.exports = UserController;
Finally, we put this controller Mount to the route :
// app/router.js
module.exports = app => {
const {
router, controller } = app;
router.resources('users', '/users', controller.users);
};
in the light of users
Tabular CURD The operation interface is developed
Other parameters of the model
// To configure ( important )
const User = app.model.define('user', {
id: {
type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
},{
// Custom table name
'freezeTableName': true,
'tableName': 'xyz_users',
// Do you need to add createdAt、updatedAt、deletedAt Field
'timestamps': true,
// Unwanted createdAt Field
'createdAt': false,
// take updatedAt Change the field name
'updatedAt': 'utime',
// take deletedAt Field renaming
// At the same time, you need to set paranoid by true( In this mode , No physical deletion occurs when data is deleted , It's about setting deletedAt For the current time
'deletedAt': 'dtime',
'paranoid': true,
});
sequelize command
command | meaning |
---|---|
sequelize db:migrate | Run the migration file |
sequelize db:migrate:status | List the status of all migrations |
sequelize db:migrate:undo | Isolate database : transfer : undo |
sequelize db:migrate:undo:all | Restore all running migrations |
sequelize db:create | Create the database specified by the configuration |
sequelize db:drop | Delete the database specified by the configuration |
Foreign key constraints ( important )
// The migration file
queryInterface.addConstraint('tableName', ['user_id'], {
type: 'foreign key',
name: 'user_id',
references: {
//Required field
table: 'users',
field: 'id'
},
onDelete: 'cascade',
onUpdate: 'cascade'
});
Create the first seed
Suppose we want to insert some data into several tables by default . If we follow up on the previous example , We can think of it as User
Table create demo user .
To manage all data migrations , You can use seeders
. Seed files are variations of data , Can be used to populate database tables with sample data or test data .
Let's create a seed file , It will add a demo user to our User
In the table .
npx sequelize seed:generate --name demo-user
This command will be in seeders
Create a seed file in the folder . The file name looks like XXXXXXXXXXXXXX-demo-user.js
, It follows the same up/down
semantics , Such as migrating files .
Now we should edit this file , Insert the demo user into User
surface .
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('Users', [{
firstName: 'John',
lastName: 'Doe',
email: '[email protected]',
createdAt: new Date(),
updatedAt: new Date()
}], {
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.bulkDelete('Users', null, {
});
}
};
Run the seed
In the last step , You created a seed file . But it hasn't been saved to the database yet . So , We need to run a simple command .
npx sequelize db:seed:all
This will execute the seed file , You will have a demo user insert User
surface .
Be careful : _ seeders
Execution will not be stored in any use SequelizeMeta
Where tables are migrated . If you want to overwrite this , Please read Storage
part _
Undo the seed
Seeders If any storage is used, it can be undone . There are two commands available
If you want to undo the last seed
npx sequelize db:seed:undo
If you want to undo a particular seed
npx sequelize db:seed:undo --seed name-of-seed-as-in-data
If you want to undo all the seeds
npx sequelize db:seed:undo:all
Associated operations
one-on-one
The model layer :
// One user corresponds to one user profile
// app/model/user.js
module.exports = app => {
const {
STRING, INTEGER, DATE } = app.Sequelize;
const User = app.model.define('user', {
id: {
type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
});
// Connections
User.associate = function(models) {
// Associated user data one-on-one
User.hasOne(app.model.Userinfo);
}
return User;
};
// app/model/userinfo.js
module.exports = app => {
const {
STRING, INTEGER, DATE } = app.Sequelize;
const userinfo = app.model.define('userinfo', {
nickname: STRING,
user_id: INTEGER
}, {
});
// Associated user table
userinfo.associate = function(models) {
app.model.Userinfo.belongsTo(app.model.User);
};
return userinfo;
};
Controller call :
// app/controller/users.js
// Display a single bar
async show() {
// Query according to the primary key Query one with findOne
this.ctx.body = await this.ctx.model.User.findOne({
// Main table query field limit
attributes:['name'],
// Relational query
include: [{
// Model to query
model: this.app.model.Userinfo,
// Fields queried in the secondary table
attributes: ['nickname']
}],
// Main table conditions
where: {
id: 3
}
});
}
One to many
class City extends Model {
}
City.init({
countryCode: Sequelize.STRING }, {
sequelize, modelName: 'city' });
class Country extends Model {
}
Country.init({
isoCode: Sequelize.STRING }, {
sequelize, modelName: 'country' });
// ad locum , We can connect countries and cities according to the country code
Country.hasMany(City, {
foreignKey: 'countryCode', sourceKey: 'isoCode'});
City.belongsTo(Country, {
foreignKey: 'countryCode', targetKey: 'isoCode'});
Many to many
User.belongsToMany(Project, {
as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' })
Project.belongsToMany(User, {
as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' })
Associate common operations
// Get the associated model object ,n There is no need to add... To one s
let userinfo = await user.getUserinfo();
// n It is necessary to add s
await user.getPosts({
attributes: ['title'],
where: {
id: 3
}
});
// Associated operations
// 1: User created articles ( One to many )
await this.ctx.model.Post.create({
title: " First article ",
user_id: user.id
});
// 2. Get all articles of the current user
await user.getPosts();
await user.getPosts({
attributes: ['id'],
where:{
title:" test "
}
});
// 3: Users delete articles ( One to many )
// (1) Get all the articles of the current user
let posts = await user.getPosts({
attributes: ['id']
});
posts = posts.map(v => v.id);
await this.ctx.model.Post.destroy({
where: {
id: posts
}
});
// Scene three : Users focus on topics ( Many to many )
await this.ctx.model.TopicUser.bulkCreate([{
user_id: user.id,
topic_id: 1
},{
user_id: user.id,
topic_id: 2
}]);
// Users focus on topics ( Many to many )
await this.ctx.model.TopicUser.destroy({
where: {
user_id: user.id,
topic_id: [1, 2]
}
});
Getters and modifiers
The model layer
// app/model/user.js
module.exports = app => {
const {
STRING, INTEGER, DATE } = app.Sequelize;
const User = app.model.define('user', {
id: {
type: INTEGER, primaryKey: true, autoIncrement: true },
name: {
type: STRING(30),
// Of individual fields getter, When querying, it will call
// this.getDataValue('name') Get the original value
get() {
const age = this.getDataValue('age');
return this.getDataValue('name') + ' Age :' + age;
}
},
age: {
type: INTEGER,
// Of individual fields setter, Called when adding and updating
// this.setDataValue('name') Set the original value
set(val) {
this.setDataValue('age', val * 10);
}
},
created_at: DATE,
updated_at: DATE,
});
// Associated user data
User.associate = function(models) {
app.model.User.hasOne(app.model.Userinfo);
}
return User;
};
Controller layer
async show() {
// Query according to the primary key
let user = await this.ctx.model.User.findOne({
where: {
id: 3
}
});
// Get the original value user.getDataValue('name')
this.ctx.body = user.getDataValue('name')
}
Model hook
The model layer
module.exports = app => {
...
// hook
// Before query
User.beforeFind((user, option) => {
console.log(' Before query ');
});
// After query
User.afterFind((user, option) => {
console.log(' After query ');
});
// Before adding
User.beforeCreate((user, option) => {
console.log(' Before adding ');
});
// After adding
User.afterCreate((user, option) => {
console.log(' After adding ');
});
// Before the change
User.beforeUpdate((user, option) => {
console.log(' Before the change ');
});
// After modification
User.afterUpdate((user, option) => {
console.log(' After modification '); // Real modification will trigger , The same data will not trigger
});
// Before deleting
User.beforeDestroy((user, option) => {
console.log(' Before deleting ');
});
// After deleting
User.afterDestroy((user, option) => {
console.log(' After deleting ');
});
return User;
};
Inquire about
Primary key query
Model.findByPk(1)
If the search does not exist, create
Method findOrCreate
It can be used to check whether an element already exists in the database . If that's the case , Then the method will generate the corresponding instance . If the element does not exist , Will be created .
If that's the case , This method will result in the corresponding instance . If the element does not exist , Will be created .
Suppose we have an empty database , One User
The model has a username
and job
.
User
.findOrCreate({
where: {
username: 'sdepold'
},
defaults: {
job: 'Technical Lead JavaScript'
}
})
. then(([user, created]) => {
console.log(user.get({
plain: true
}))
console.log(created)
/* findOrCreate Returns an array of objects that have been found or created , Find or create an object and a Boolean value , If you create a new object, it will be true, Otherwise false, like this : [ { username: 'sdepold', job: 'Technical Lead JavaScript', id: 1, createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) }, true ] In the example above , The array in the third row will be divided into 2 part , And pass them as parameters to the callback function , In this case, consider them "user" and "created" .( therefore “user” Will be the index of the returned array 0 The object of , also "created" Will be equal to the "true".) */
})
The code creates a new instance . So when we have an example …
User.create({
username: 'fnord', job: 'omnomnom' })
.then(() => User.findOrCreate({
where: {
username: 'fnord'
},
defaults: {
job: 'something else'
}
}))
.then(([user, created]) => {
console.log(user.get({
plain: true
}))
console.log(created)
/* In this case ,findOrCreate Return an array as follows : [ { username: 'fnord', job: 'omnomnom', id: 2, createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) }, false ] from findOrCreate The returned array is expanded into two parts by the array in the third line , And these parts will serve as 2 Parameters are passed to the callback function , In this case, it is regarded as "user" and "created" .( therefore “user” Will be the index of the returned array 0 The object of , also "created" Will be equal to the "false".) */
})
… Existing entries will not change . See the second user's “job”, And actually the creation operation is fake .
Find and count
findAndCountAll
- Search the database for multiple elements , Return data and total count
This is a convenient method , It is a combination of findAll
and count
( See below ), When processing paging related queries , This is useful , You want to use it? limit
and offset
Retrieving data , But you also need to know how many records the total number matches the query :
A handler that succeeds will always receive an object with two properties :
count
- An integer , The total number of records matches where Statement and other associated filtersrows
- An array object , Recorded in the limit and offset Match in range where Statement and other associated filters ,
Project
.findAndCountAll({
where: {
title: {
[Op.like]: 'foo%'
}
},
offset: 10,
limit: 2
})
.then(result => {
console.log(result.count);
console.log(result.rows);
});
It supports include. Only marked as required
Of include Will be added to the count section :
Suppose you want to find all the users with their personal data :
User.findAndCountAll({
include: [
{
model: Profile, required: true}
],
limit: 3
});
because Profile
Of include Yes required
Set up , This will result in internal connections , And only with profile Of users will be counted . If we're from include Delete in required
, So there are and no profile Of users will be counted . stay include Add a where
Statement will automatically make it required:
User.findAndCountAll({
include: [
{
model: Profile, where: {
active: true }}
],
limit: 3
});
The above query will only be used for queries with active profile Of users , Because I'm going to where Statement added to include when ,required
Is implicitly set to true.
Pass to findAndCountAll
Of options Object and the findAll
identical ( As follows ).
Query multiple ( Commonly used )
// Multiple entries found
Project.findAll().then(projects => {
// projects Will be all Project An array of instances
})
// Search for specific properties - Use hash
Project.findAll({
where: {
name: 'A Project' } }).then(projects => {
// projects Will be a with a specified name Of Project Instance array
})
// Search within a specific range
Project.findAll({
where: {
id: [1,2,3] } }).then(projects => {
// projects Will be a series with id 1,2 or 3 Project
// This is actually doing a IN Inquire about
})
Project.findAll({
where: {
id: {
[Op.and]: {
a: 5}, // And (a = 5)
[Op.or]: [{
a: 5}, {
a: 6}], // (a = 5 or a = 6)
[Op.gt]: 6, // id > 6
[Op.gte]: 6, // id >= 6
[Op.lt]: 10, // id < 10
[Op.lte]: 10, // id <= 10
[Op.ne]: 20, // id != 20
[Op.between]: [6, 10], // stay 6 and 10 Between
[Op.notBetween]: [11, 15], // be not in 11 and 15 Between
[Op.in]: [1, 2], // stay [1, 2] In
[Op.notIn]: [1, 2], // be not in [1, 2] In
[Op.like]: '%hat', // contain '%hat'
[Op.notLike]: '%hat', // It doesn't contain '%hat'
[Op.iLike]: '%hat', // contain '%hat' ( Case insensitive ) ( Limited to PG)
[Op.notILike]: '%hat', // It doesn't contain '%hat' ( Limited to PG)
[Op.overlap]: [1, 2], // && [1, 2] (PG Array overlap operator )
[Op.contains]: [1, 2], // @> [1, 2] (PG Array contains operators )
[Op.contained]: [1, 2], // <@ [1, 2] (PG Arrays are contained in operators )
[Op.any]: [2,3], // Any array [2, 3]::INTEGER ( Limited to PG)
},
status: {
[Op.not]: false, // status Not for FALSE
}
}
})
Composite filtration / OR / NOT Inquire about
You can use multi-level nested AND,OR and NOT The condition carries on a compound where Inquire about . To do this , You can use or
, and
or not
Operator
:
Project.findOne({
where: {
name: 'a project',
[Op.or]: [
{
id: [1,2,3] },
{
id: {
[Op.gt]: 10 } }
]
}
})
Project.findOne({
where: {
name: 'a project',
id: {
[Op.or]: [
[1,2,3],
{
[Op.gt]: 10 }
]
}
}
})
These two pieces of code will generate the following :
SELECT *
FROM `Projects`
WHERE (
`Projects`.`name` = 'a project'
AND (`Projects`.`id` IN (1,2,3) OR `Projects`.`id` > 10)
)
LIMIT 1;
not
Example :
Project.findOne({
where: {
name: 'a project',
[Op.not]: [
{
id: [1,2,3] },
{
array: {
[Op.contains]: [3,4,5] } }
]
}
});
Will generate :
SELECT *
FROM `Projects`
WHERE (
`Projects`.`name` = 'a project'
AND NOT (`Projects`.`id` IN (1,2,3) OR `Projects`.`array` @> ARRAY[3,4,5]::INTEGER[])
)
LIMIT 1;
Use restrictions , The offset , Sequential and grouping operation data sets
To get more relevant data , Restrictions can be used , The offset , Sequence and grouping :
// Limit the results of the query
Project.findAll({
limit: 10 })
// Skip the former 10 Elements
Project.findAll({
offset: 10 })
// Skip the former 10 Elements , And get the 2 individual
Project.findAll({
offset: 10, limit: 2 })
The syntax for grouping and sorting is the same , So let's just use a single example to explain the grouping , The rest are sorted . Everything you see below can also be grouped
Project.findAll({
order: [['title', 'DESC']]})
// Generate ORDER BY title DESC
Project.findAll({
group: 'name'})
// Generate GROUP BY name
Please note that , In the two examples above , The supplied string is inserted verbatim into the query , So the column name will not be escaped . When you turn to order / group When providing a string , Will always be . If you want to escape the column name , You should provide an array of parameters , Even if you just want to go through a single column order / group
something.findOne({
order: [
// Will return `name`
['name'],
// Will return `username` DESC
['username', 'DESC'],
// Will return max(`age`)
sequelize.fn('max', sequelize.col('age')),
// Will return max(`age`) DESC
[sequelize.fn('max', sequelize.col('age')), 'DESC'],
// Will return otherfunction(`col1`, 12, 'lalala') DESC
[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
// Will return otherfunction(awesomefunction(`col`)) DESC, This nesting can be infinite !
[sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC']
]
})
Take a look back. ,order / group The elements of the array can be the following :
- String - Will be quoted
- Array - The first element will be referenced , The second one will be appended word for word
- Object -
- raw Will be added verbatim references
- If not set raw, Everything is ignored , The query will fail
- Sequelize.fn and Sequelize.col Return function and referenced column names
Field filtering
Want to select only certain attributes , have access to attributes
Options . Usually you pass an array :
Model.findAll({
attributes: ['foo', 'bar']
});
SELECT foo, bar …
Properties can be renamed using nested arrays :
Model.findAll({
attributes: ['foo', ['bar', 'baz']]
});
SELECT foo, bar AS baz …
You can also use sequelize.fn
To aggregate :
Model.findAll({
attributes: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']]
});
SELECT COUNT(hats) AS no_hats …
When using the aggregate function , You have to give it an alias , So that it can be accessed from the model . In the example above , You can use instance.get('no_hats')
Get the number of hats .
Sometimes , If you just want to add aggregations , Listing all the attributes of the model can be annoying :
// This is a tiresome way of getting the number of hats...
Model.findAll({
attributes: ['id', 'foo', 'bar', 'baz', 'quz', [sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']]
});
// This is shorter, and less error prone because it still works if you add / remove attributes
Model.findAll({
attributes: {
include: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] }
});
SELECT id, foo, bar, baz, quz, COUNT(hats) AS no_hats ...
Again , It can also exclude some specified table fields :
Model.findAll({
attributes: {
exclude: ['baz'] }
});
SELECT id, foo, bar, quz ...
Where
Whether you are through findAll/find Or in bulk updates/destroys The query , Can pass one where
Object to filter queries .
where
Usually use attribute:value Key value pairs get an object , among value It can be a key value object that matches equality data or other operators .
You can also nest or
and and
Operator
To generate complex AND/OR Conditions .
Basics
const Op = Sequelize.Op;
Post.findAll({
where: {
authorId: 2
}
});
// SELECT * FROM post WHERE authorId = 2
Post.findAll({
where: {
authorId: 12,
status: 'active'
}
});
// SELECT * FROM post WHERE authorId = 12 AND status = 'active';
Post.findAll({
where: {
[Op.or]: [{
authorId: 12}, {
authorId: 13}]
}
});
// SELECT * FROM post WHERE authorId = 12 OR authorId = 13;
Post.findAll({
where: {
authorId: {
[Op.or]: [12, 13]
}
}
});
// SELECT * FROM post WHERE authorId = 12 OR authorId = 13;
Post.destroy({
where: {
status: 'inactive'
}
});
// DELETE FROM post WHERE status = 'inactive';
Post.update({
updatedAt: null,
}, {
where: {
deletedAt: {
[Op.ne]: null
}
}
});
// UPDATE post SET updatedAt = null WHERE deletedAt NOT NULL;
Post.findAll({
where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6)
});
// SELECT * FROM post WHERE char_length(status) = 6;
The operator
Sequelize Symbolic operators that can be used to create more complex comparisons -
const Op = Sequelize.Op
[Op.and]: {
a: 5} // And (a = 5)
[Op.or]: [{
a: 5}, {
a: 6}] // (a = 5 or a = 6)
[Op.gt]: 6, // id > 6
[Op.gte]: 6, // id >= 6
[Op.lt]: 10, // id < 10
[Op.lte]: 10, // id <= 10
[Op.ne]: 20, // id != 20
[Op.eq]: 3, // = 3
[Op.not]: true, // No TRUE
[Op.between]: [6, 10], // stay 6 and 10 Between
[Op.notBetween]: [11, 15], // be not in 11 and 15 Between
[Op.in]: [1, 2], // stay [1, 2] In
[Op.notIn]: [1, 2], // be not in [1, 2] In
[Op.like]: '%hat', // contain '%hat'
[Op.notLike]: '%hat' // It doesn't contain '%hat'
[Op.iLike]: '%hat' // contain '%hat' ( Case insensitive ) ( Limited to PG)
[Op.notILike]: '%hat' // It doesn't contain '%hat' ( Limited to PG)
[Op.startsWith]: 'hat' // similar 'hat%'
[Op.endsWith]: 'hat' // similar '%hat'
[Op.substring]: 'hat' // similar '%hat%'
[Op.regexp]: '^[h|a|t]' // Match regular expression /~ '^[h|a|t]' ( Limited to MySQL/PG)
[Op.notRegexp]: '^[h|a|t]' // Does not match regular expression /!~ '^[h|a|t]' ( Limited to MySQL/PG)
[Op.iRegexp]: '^[h|a|t]' // ~* '^[h|a|t]' ( Limited to PG)
[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' ( Limited to PG)
[Op.like]: {
[Op.any]: ['cat', 'hat']} // Contains any array ['cat', 'hat'] - The same applies iLike and notLike
[Op.overlap]: [1, 2] // && [1, 2] (PG Array overlap operator )
[Op.contains]: [1, 2] // @> [1, 2] (PG Array contains operators )
[Op.contained]: [1, 2] // <@ [1, 2] (PG Arrays are contained in operators )
[Op.any]: [2,3] // Any array [2, 3]::INTEGER ( Limited to PG)
[Op.col]: 'user.organization_id' // = 'user'.'organization_id', Use database language specific column identifiers , This example USES PG
Range options
All operators support supported range type queries .
please remember , The range value provided can also Define the binding inclusion/exclusion.
// Add the following to all the above equal and unequal operators :
[Op.contains]: 2 // @> '2'::integer (PG range contains element operator)
[Op.contains]: [1, 2] // @> [1, 2) (PG range contains range operator)
[Op.contained]: [1, 2] // <@ [1, 2) (PG range is contained by operator)
[Op.overlap]: [1, 2] // && [1, 2) (PG range overlap (have points in common) operator)
[Op.adjacent]: [1, 2] // -|- [1, 2) (PG range is adjacent to operator)
[Op.strictLeft]: [1, 2] // << [1, 2) (PG range strictly left of operator)
[Op.strictRight]: [1, 2] // >> [1, 2) (PG range strictly right of operator)
[Op.noExtendRight]: [1, 2] // &< [1, 2) (PG range does not extend to the right of operator)
[Op.noExtendLeft]: [1, 2] // &> [1, 2) (PG range does not extend to the left of operator)
Combine
{
rank: {
[Op.or]: {
[Op.lt]: 1000,
[Op.eq]: null
}
}
}
// rank < 1000 OR rank IS NULL
{
createdAt: {
[Op.lt]: new Date(),
[Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000)
}
}
// createdAt < [timestamp] AND createdAt > [timestamp]
{
[Op.or]: [
{
title: {
[Op.like]: 'Boat%'
}
},
{
description: {
[Op.like]: '%boat%'
}
}
]
}
// title LIKE 'Boat%' OR description LIKE '%boat%'
Relationship / relation
// Find all that have at least one task Of project, among task.state === project.state
Project.findAll({
include: [{
model: Task,
where: {
state: Sequelize.col('project.state') }
}]
})
Pagination / Limit
// obtain 10 An example / That's ok
Project.findAll({
limit: 10 })
// skip 8 An example / That's ok
Project.findAll({
offset: 8 })
// skip 5 An example , And then take 5 individual
Project.findAll({
offset: 5, limit: 5 })
Sort
order
You need an array of items to sort the query or a sequelize Method . Generally speaking , You are going to use any property of tuple/array, And determine the positive and negative direction of sorting .
Subtask.findAll({
order: [
// Will escape title , And according to the valid direction parameter list, verify DESC
['title', 'DESC'],
// Will be sorted by maximum (age)
sequelize.fn('max', sequelize.col('age')),
// Will be in the maximum order (age) DESC
[sequelize.fn('max', sequelize.col('age')), 'DESC'],
// Will press otherfunction Sort (`col1`, 12, 'lalala') DESC
[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
// Use the model name as the associated name to sort the created_at.
[Task, 'createdAt', 'DESC'],
// Will order through an associated model's created_at using the model names as the associations' names.
[Task, Project, 'createdAt', 'DESC'],
// The name of the association will be used by the created_at Sort .
['Task', 'createdAt', 'DESC'],
// Will order by a nested associated model's created_at using the names of the associations.
['Task', 'Project', 'createdAt', 'DESC'],
// Will order by an associated model's created_at using an association object. ( Preferred method )
[Subtask.associations.Task, 'createdAt', 'DESC'],
// Will order by a nested associated model's created_at using association objects. ( Preferred method )
[Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'],
// Will order by an associated model's created_at using a simple association object.
[{
model: Task, as: 'Task'}, 'createdAt', 'DESC'],
// Nesting associated models created_at Simple associated object sorting
[{
model: Task, as: 'Task'}, {
model: Project, as: 'Project'}, 'createdAt', 'DESC']
]
// Will be sorted in descending order of age maximum
order: sequelize.literal('max(age) DESC')
// In ascending order of the oldest value , When the sorting conditions are omitted, the default is ascending
order: sequelize.fn('max', sequelize.col('age'))
// Sorting in ascending order is the default order to omit sorting conditions
order: sequelize.col('age')
// It will be sorted randomly according to dialect ( instead of fn('RAND') or fn('RANDOM'))
order: sequelize.random()
})
count
- Count the number of occurrences of elements in the database
There is also a method of counting database objects :
Project.count().then(c => {
console.log("There are " + c + " projects!")
})
Project.count({
where: {
'id': {
[Op.gt]: 25}} }).then(c => {
console.log("There are " + c + " projects with an id greater than 25.")
})
max
- Get the maximum value of a specific attribute in a specific table
Here is the method to get the maximum value of the attribute :
/* We assume that 3 Objects with attribute age . The first is 10 year , The second is 5 year , The third is 40 year . */
Project.max('age').then(max => {
// Will return 40
})
Project.max('age', {
where: {
age: {
[Op.lt]: 20 } } }).then(max => {
// Will be 10
})
min
- Get the minimum value of a specific attribute in a specific table
Here is the method to get the minimum value of the attribute :
/* We assume that 3 Objects with attribute age . The first is 10 year , The second is 5 year , The third is 40 year . */
Project.min('age').then(min => {
// Will return 5
})
Project.min('age', {
where: {
age: {
[Op.gt]: 5 } } }).then(min => {
// Will be 10
})
sum
- Sum the values of a particular attribute
To calculate the sum of specific columns of a table , have access to “sum” Method .
/* We assume that 3 Objects with attribute age . The first is 10 year , The second is 5 year , The third is 40 year . */
Project.sum('age').then(sum => {
// Will return 55
})
Project.sum('age', {
where: {
age: {
[Op.gt]: 5 } } }).then(sum => {
// Will be 50
})
Preloading
When you retrieve data from a database , Also want to get the query associated with it , This is called preloading . The basic idea is that when you call find
or findAll
When using include
attribute . Let's assume the following settings :
class User extends Model {
}
User.init({
name: Sequelize.STRING }, {
sequelize, modelName: 'user' })
class Task extends Model {
}
Task.init({
name: Sequelize.STRING }, {
sequelize, modelName: 'task' })
class Tool extends Model {
}
Tool.init({
name: Sequelize.STRING }, {
sequelize, modelName: 'tool' })
Task.belongsTo(User)
User.hasMany(Task)
User.hasMany(Tool, {
as: 'Instruments' })
sequelize.sync().then(() => {
// This is where we continue ...
})
First , Let's use their associations user Load all task.
Task.findAll({
include: [ User ] }).then(tasks => {
console.log(JSON.stringify(tasks))
/* [{ "name": "A Task", "id": 1, "createdAt": "2013-03-20T20:31:40.000Z", "updatedAt": "2013-03-20T20:31:40.000Z", "userId": 1, "user": { "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z" } }] */
})
Please note that , The visitor ( Results... In the example User
attribute ) It's singular , Because the association is one-to-one .
Next thing : Load data with many to one associations !
User.findAll({
include: [ Task ] }).then(users => {
console.log(JSON.stringify(users))
/* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "tasks": [{ "name": "A Task", "id": 1, "createdAt": "2013-03-20T20:31:40.000Z", "updatedAt": "2013-03-20T20:31:40.000Z", "userId": 1 }] }] */
})
Please note that , The visitor ( Results... In the example Tasks
attribute ) It's in the plural , Because the association is many to one .
If the association is an alias ( Use as
Parameters ), This alias must be specified when the model is included . Pay attention to the user's Tool
How to be alias Instruments
. To get the right permissions , You must specify the model to load and the alias :
User.findAll({
include: [{
model: Tool, as: 'Instruments' }] }).then(users => {
console.log(JSON.stringify(users))
/* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }] */
})
You can also include aliases by specifying a string that matches the associated alias :
User.findAll({
include: ['Instruments'] }).then(users => {
console.log(JSON.stringify(users))
/* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }] */
})
User.findAll({
include: [{
association: 'Instruments' }] }).then(users => {
console.log(JSON.stringify(users))
/* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }] */
})
When preloading , We can also use where
Filter the associated model . This returns Tool
All in the model are related to where
Statement to match the row User
.
User.findAll({
include: [{
model: Tool,
as: 'Instruments',
where: {
name: {
[Op.like]: '%ooth%' } }
}]
}).then(users => {
console.log(JSON.stringify(users))
/* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }], [{ "name": "John Smith", "id": 2, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }], */
})
When using include.where
When filtering a preloaded model ,include.required
Is implicitly set to true
. This means that the inner join completion returns the parent model with any matching children .
Use the top level of the preloaded model WHERE
The WHERE
Conditions start from ON
Conditions of the include The schema moves to the top level , You can use '$nested.column$'
grammar :
User.findAll({
where: {
'$Instruments.name$': {
[Op.iLike]: '%ooth%' }
},
include: [{
model: Tool,
as: 'Instruments'
}]
}).then(users => {
console.log(JSON.stringify(users));
/* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }], [{ "name": "John Smith", "id": 2, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }], */
Include all
To include all attributes , You can use all:true
Passing a single object :
User.findAll({
include: [{
all: true }]});
Including soft deleted records
If you want to load soft deleted records , It can be done by putting include.paranoid
Set to false
To achieve
User.findAll({
include: [{
model: Tool,
where: {
name: {
[Op.like]: '%ooth%' } },
paranoid: false // query and loads the soft deleted records
}]
});
Sort preload associations
In the case of a one to many relationship .
Company.findAll({
include: [ Division ], order: [ [ Division, 'name' ] ] });
Company.findAll({
include: [ Division ], order: [ [ Division, 'name', 'DESC' ] ] });
Company.findAll({
include: [ {
model: Division, as: 'Div' } ],
order: [ [ {
model: Division, as: 'Div' }, 'name' ] ]
});
Company.findAll({
include: [ {
model: Division, as: 'Div' } ],
order: [ [ {
model: Division, as: 'Div' }, 'name', 'DESC' ] ]
});
Company.findAll({
include: [ {
model: Division, include: [ Department ] } ],
order: [ [ Division, Department, 'name' ] ]
});
In the case of many to many relationships , You can also sort by attributes in the table .
Company.findAll({
include: [ {
model: Division, include: [ Department ] } ],
order: [ [ Division, DepartmentDivision, 'name' ] ]
});
Nested preload
You can use nested preloading to load all related models of related models :
User.findAll({
include: [
{
model: Tool, as: 'Instruments', include: [
{
model: Teacher, include: [ /* etc */]}
]}
]
}).then(users => {
console.log(JSON.stringify(users))
/* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ // 1:M and N:M association "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1, "Teacher": { // 1:1 association "name": "Jimi Hendrix" } }] }] */
})
This will result in an outer connection . however , On the relevant model where
Statement will create an internal connection , And only return instances with matching submodels . To return all parent instances , You should add required: false
.
User.findAll({
include: [{
model: Tool,
as: 'Instruments',
include: [{
model: Teacher,
where: {
school: "Woodstock Music School"
},
required: false
}]
}]
}).then(users => {
/* ... */
})
The above query will return all users and all their instruments , But it will only return with Woodstock Music School
Relevant teachers .
It also supports nested loading :
User.findAll({
include: [{
all: true, nested: true }]});
newly added
Field restrictions
await User.create({
username: 'barfooz', isAdmin: true }, {
fields: [ 'username' ] });
// Only username It works
User.bulkCreate([
{
username: 'foo' },
{
username: 'bar', admin: true}
], {
fields: ['username'] }).then(() => {
// admin Will not be built
})
Add a single
// create
this.ctx.body = await this.ctx.model.User.create({
name: " Ha ha ha ",
age: 12
});
Batch add
// Batch add bulkCreate
this.ctx.body = await this.ctx.model.User.bulkCreate([
{
name: " first ",
age: 15
},
{
name: " the second ",
age: 15
},
{
name: " Third ",
age: 15
},
]);
modify
Field restrictions
task.title = 'foooo'
task.description = 'baaaaaar'
await task.save({
fields: ['title']});
// title Now it will be “foooo”, and description Same as before
// Use equivalent update Call as follows :
await task.update({
title: 'foooo', description: 'baaaaaar'}, {
fields: ['title']});
// title Now it will be “foooo”, and description Same as before
Single modification
// Find the current record
const user = await this.ctx.model.User.findByPk(1);
await user.update({
name: " I've been modified ",
age: 30
});
Bulk changes
// Bulk changes
await this.ctx.model.User.update({
name: " Bulk changes "
}, {
// Conditions
where: {
name: " first "
}
});
Increasing
// Find the current record increment
const user = await this.ctx.model.User.findByPk(2);
this.ctx.body = await user.increment({
age: 3, // age Each increment 3
other:2 // other Each increment 2
});
Decline
// Find the current record decrement
const user = await this.ctx.model.User.findByPk(2);
this.ctx.body = await user.decrement({
age: 3, // age Decrease each time 3
other:2 // other Decrease each time 2
});
Delete
Soft delete
Configuration in model
// To configure ( important )
const User = app.model.define('user', {
/* bla */},{
// At the same time, you need to set paranoid by true( In this mode , No physical deletion occurs when data is deleted , It's about setting deletedAt For the current time
'paranoid': true
});
Queries include soft delete content
let user = await ctx.model.User.findOne({
include:{
model:ctx.model.Video,
// Including soft deletion
paranoid: false
},
where: {
id: 33
},
// Including soft deletion
paranoid: false
});
Delete completely
If paranoid
The options are true, The object is not deleted , And will be deletedAt
The column is set to the current timestamp . To force the deletion of , Can be force: true
Pass to destroy call :
task.destroy({
force: true })
stay paranoid
After the object is soft deleted in mode , Before forcibly deleting old instances , You will not be able to create new instances with the same primary key .
Restore soft deleted instances
If you use paranoid:true
Soft delete instances of the model , Then you want to undo the deletion , Please use restore
Method :
// Soft delete ...
task.destroy();
// Restore soft delete ...
task.restore();
Conditions delete
await this.ctx.model.User.destroy({
where: {
name: " Bulk changes "
}
});
Batch deletion
await this.ctx.model.Post.destroy({
where: {
id: posts
}
});
Overload instance
If you need to synchronize your instances , You can use reload
Method . It will get the current data from the database , And override the properties of the model that invokes the method .
Person.findOne({
where: {
name: 'john' } }).then(person => {
person.name = 'jane'
console.log(person.name) // 'jane'
person.reload().then(() => {
console.log(person.name) // 'john'
})
})
Model customization method
// Model
// Model customization method
topic_user.ceshi = (param) => {
console.log(' Model customization method ');
console.log(param);
return param;
}
// controller
await this.ctx.model.TopicUser.ceshi(123);
Scopes - Scope ( a key )
Scope allows you to define common queries , For easy use in the future . A scope can include the same as a regular finder where
, include
, limit
And all the same properties .
Definition
Scope is defined in the model definition , It can be finder Object or return finder Object function , In addition to the default scope , The scope can only be one object :
class Project extends Model {
}
Project.init({
// attribute
}, {
defaultScope: {
where: {
active: true
}
},
scopes: {
deleted: {
where: {
deleted: true
}
},
activeUsers: {
include: [
{
model: User, where: {
active: true }}
]
},
random () {
return {
where: {
someNumber: Math.random()
}
}
},
accessLevel (value) {
return {
where: {
accessLevel: {
[Op.gte]: value
}
}
}
}
sequelize,
modelName: 'project'
}
});
By calling addScope
After defining the model , You can also add scopes . This is especially useful for scopes with inclusion , It may not be defined when defining other models include In the model .
Always apply the default scope . It means , Define through the above model ,Project.findAll()
The following query will be created :
SELECT * FROM projects WHERE active = true
You can call .unscoped()
, .scope(null)
Or delete the default scope by calling another scope :
Project.scope('deleted').findAll(); // Delete the default scope
SELECT * FROM projects WHERE deleted = true
You can also include a scope model in the scope definition . This allows you to avoid repetition include
,attributes
or where
Definition .
Use the example above , And call... In the included user model active
Scope ( Not directly in the include Object ):
activeUsers: {
include: [
{
model: User.scope('active')}
]
}
Use
By calling... On the model definition .scope
To apply the scope , Pass the name of one or more scopes . .scope
Return a fully functional model instance , It has all the conventional methods :.findAll
,.update
,.count
,.destroy
wait . You can save this model instance and use it again later :
const DeletedProjects = Project.scope('deleted');
DeletedProjects.findAll();
// After a while
// Let's look for the deleted items again !
DeletedProjects.findAll();
Scope applies to .find
, .findAll
, .count
, .update
, .increment
and .destroy
.
You can call a scope as a function in two ways . If the scope does not have any parameters , It can normally call . If the scope takes parameters , Pass an object :
Project.scope('random', {
method: ['accessLevel', 19]}).findAll();
SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19
Merge
By passing a scope array to .scope
Or by passing the scope as a continuous parameter , Multiple scopes can be applied at the same time .
// These two are equivalent
Project.scope('deleted', 'activeUsers').findAll();
Project.scope(['deleted', 'activeUsers']).findAll();
SELECT * FROM projects
INNER JOIN users ON projects.userId = users.id
WHERE projects.deleted = true
AND users.active = true
If you want to apply another scope with the default scope , Please set the key defaultScope
Pass to .scope
:
Project.scope('defaultScope', 'deleted').findAll();
SELECT * FROM projects WHERE active = true AND deleted = true
When calling multiple scopes , The keys of subsequent scopes will overwrite the previous scopes ( Be similar to Object.assign), except where
and include
, They will be merged . Consider two scopes :
{
scope1: {
where: {
firstName: 'bob',
age: {
[Op.gt]: 20
}
},
limit: 2
},
scope2: {
where: {
age: {
[Op.gt]: 30
}
},
limit: 10
}
}
call .scope('scope1', 'scope2')
The following query will be generated
WHERE firstName = 'bob' AND age > 30 LIMIT 10
Be careful scope2
Will cover limit
and age
, and firstName
Reserved . limit
,offset
,order
,paranoid
,lock
and raw
Fields are overwritten , and where
Merged by shallow layers ( This means that the same key will be overwritten ). include
The merge strategy for will be discussed later .
Please note that , Multiple application scopes attributes
Keys merge in this way , That is, always keep attributes.exclude
. This allows you to merge multiple scopes , And never disclose sensitive fields in the final scope .
Pass the lookup object directly to... On the scope model findAll
( And similar finders ) when , Apply the same consolidation logic :
Project.scope('deleted').findAll({
where: {
firstName: 'john'
}
})
WHERE deleted = true AND firstName = 'john'
there deleted
Scope and finder Merge . If we want to where: { firstName: 'john', deleted: false }
Pass to finder, that deleted
Scope will be overwritten .
Merge include
Include Is recursively merged according to the contained model . This is a very powerful merger , stay v5 Add , And better understand through examples .
Consider four models :Foo,Bar,Baz and Qux, It has the following associations :
class Foo extends Model {
}
class Bar extends Model {
}
class Baz extends Model {
}
class Qux extends Model {
}
Foo.init({
name: Sequelize.STRING }, {
sequelize });
Bar.init({
name: Sequelize.STRING }, {
sequelize });
Baz.init({
name: Sequelize.STRING }, {
sequelize });
Qux.init({
name: Sequelize.STRING }, {
sequelize });
Foo.hasMany(Bar, {
foreignKey: 'fooId' });
Bar.hasMany(Baz, {
foreignKey: 'barId' });
Baz.hasMany(Qux, {
foreignKey: 'bazId' });
Now? , consider Foo The following four scopes defined on :
{
includeEverything: {
include: {
model: this.Bar,
include: [{
model: this.Baz,
include: this.Qux
}]
}
},
limitedBars: {
include: [{
model: this.Bar,
limit: 2
}]
},
limitedBazs: {
include: [{
model: this.Bar,
include: [{
model: this.Baz,
limit: 2
}]
}]
},
excludeBazName: {
include: [{
model: this.Bar,
include: [{
model: this.Baz,
attributes: {
exclude: ['name']
}
}]
}]
}
}
These four scopes can easily be deeply merged , For example, by calling Foo.scope('includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName').findAll()
, This is exactly the same as calling the following :
Foo.findAll({
include: {
model: this.Bar,
limit: 2,
include: [{
model: this.Baz,
limit: 2,
attributes: {
exclude: ['name']
},
include: this.Qux
}]
}
});
Observe how the four scopes are merged into one . Merge scoped based on the contained model include. If a scope includes a model A The other scope includes the model B, The merged result will include the model A and B. On the other hand , If both scopes include the same model A, But with different parameters ( For example, nesting include Or other properties ) , These will be merged recursively , As shown above .
Regardless of the order applied to the scope , The merges described above all work in exactly the same way . If a parameter is set by two different scopes , Then only the order will be different - This is not the case with the above example , Because each scope does different things .
The way this merge strategy works and passes on to .findAll
,.findOne
And so on .
relation
Sequelize There are two different but related scope concepts for and associations . Differences are subtle but important :
- Associated scope Allows you to specify default properties when getting and setting associations - Useful in implementing polymorphic associations . When using
get
,set
,add
andcreate
Associated model functions , This scope is invoked only on the association between the two models - Scope on the association model Allows you to apply default and other scopes when getting associations , It also allows you to pass the scope model when creating associations . These scopes are applicable to both regular lookup on the model and lookup through Association .
for instance , Think about the model Post and Comment. Comment With several other models ( Images , Video etc. ) Related to ,Comment Associations with other models are polymorphic , This means that in addition to foreign keys commentable_id
outside , Comments also store a commentable
Column .
have access to association scope To implement polymorphic Association :
this.Post.hasMany(this.Comment, {
foreignKey: 'commentable_id',
scope: {
commentable: 'post'
}
});
When calling post.getComments()
when , This will automatically add WHERE commentable = 'post'
. Similarly , When adding a new comment to a post ,commentable
It is automatically set to 'post'
. The associated scope is to survive in the background , No programmers need not worry - It cannot be disabled . For a more complete example of polymorphism , see also Associated scope
Then consider that Post The default scope of shows only active posts :where: { active: true }
. The scope exists in the associated model (Post) On , Not like it commentable
Scope as in relation to . It's like calling Post.findAll()
The default scope is also applied , When calling User.getPosts()
when , It will also be applied - This will only return the active posts of the user .
To disable the default scope , take scope: null
Pass to getter: User.getPosts({ scope: null })
. Again , If you want to apply other scopes , Please look like this :
User.getPosts({
scope: ['scope1', 'scope2']});
If you want to create a shortcut for a scope on the associated model , The scope model can be passed to the association . Consider a shortcut to get all the deleted posts of the user :
class Post extends Model {
}
Post.init(attributes, {
defaultScope: {
where: {
active: true
}
},
scopes: {
deleted: {
where: {
deleted: true
}
}
},
sequelize,
});
User.hasMany(Post); // routine getPosts relation
User.hasMany(Post.scope('deleted'), {
as: 'deletedPosts' });
User.getPosts(); // WHERE active = true
User.getDeletedPosts(); // WHERE deleted = true
Expand
extend/helper.js
// app/extend/helper.js
module.exports = {
// Extend a format date method
formatTime(val) {
let d = new Date(val * 1000);
return d.getFullYear();
},
};
Invocation in template
<%=helper.formatTime(dateline)%>
Call from other places
this.ctx.helper.formatTime(dateline)
middleware
1. Definition
app/middleware/getIp.js
/* options: Configuration item of Middleware , The framework will app.config[${middlewareName}] Pass it on . app: Current application Application Example . */
module.exports = (option, app) => {
// Returns an asynchronous method
return async function(ctx, next) {
// adopt option Pass in extra parameters
console.log(option);
console.log(ctx.request.ip);
await next();
}
};
2. To configure
config/config.default.js( Configure global middleware , All routes call )
module.exports = appInfo => {
...
// Configure global middleware
config.middleware = ['getIp']; // Pay attention to the hump style , If the middleware is a_bc.js, It should be written as aBc
// Configure middleware parameters
config.getIp = {
ceshi: 123,
// General configuration ( Here's the point )
enable:true, // Control whether middleware is enabled .
match:'/news', // Setting only requests that meet certain rules will go through this middleware ( Matching routing )
ignore:'/shop' // Setting up requests that conform to certain rules does not go through this middleware .
/** Be careful : 1. match and ignore Simultaneous configuration not allowed 2. for example :match:'/news', Just include /news Any page of the is valid **/
// match and ignore Support multiple types of configuration methods : character string 、 Regular 、 function ( recommend )
match(ctx) {
// Only ios Device on
const reg = /iphone|ipad|ipod/i;
return reg.test(ctx.get('user-agent'));
},
};
...
};
3. Use
Routing uses
app/router.js
module.exports = app => {
// Local middleware ( If you only need local calls , You don't need to be in config.default.js Middle configuration )
router.get('/admin/:id', app.middleware.getIp({
ceshi: " I am a admin"
}), controller.admin.index);
};
Use Koa Middleware (gzip Compress )
Greatly improve the access speed of the website ( Very effective )
With koa-compress For example , stay Koa When used in :
const koa = require('koa');
const compress = require('koa-compress');
const app = koa();
const options = {
threshold: 2048 };
app.use(compress(options));
We load this in the application according to the specification of the framework Koa Middleware :
// app/middleware/compress.js
// koa-compress Exposed interface (`(options) => middleware`) Consistent with the middleware requirements of the framework
module.exports = require('koa-compress');
// config/config.default.js
module.exports = {
middleware: [ 'compress' ],
compress: {
threshold: 2048,
},
};
Form submission
post
app/controller/home.js
async addInput(ctx) {
await ctx.render('post');
}
async add(ctx) {
// adopt ctx.request.body obtain post Submit data
console.log(ctx.request.body);
}
app/view/post.html
<!-- Need to define :?_csrf=<%=ctx.csrf%> -->
<form action="/add?_csrf=<%=ctx.csrf%>" method="post">
<input type="text" name="username" id="username">
<input type="password" name="password" id="password">
<input type="submit" value=" Submit ">
</form>
app/router.js
router.get('/post', controller.home.addInput);
router.post('/add', controller.home.add);
cookie
// 1. Set up
ctx.cookies.set('username', 'ceshi');
// 2. obtain
ctx.cookies.get('username');
// 3. Set up Chinese ( Encryption operation encrypt: true)
// 4. Set up ( Other parameter configuration )
ctx.cookies.set('username', 'ceshi', {
maxAge: 1000 * 3600 * 24, // Storage 24 Hours , Unit millisecond , Close the browser cookie There is still
httpOnly: true, // Set whether key value pairs can be js visit , The default is true, It is not allowed to be js visit .
signed: true, // Signature , Prevent users from modifying the foreground
encrypt: true // encryption , Be careful :get Decryption is required when obtaining
});
// 5. Decrypt when getting
ctx.cookies.get('username',{
encrypt: true
});
// 6. eliminate cookie
ctx.cookies.set('username', null);
session
// 1. Set up
ctx.session.username = ' test ';
// 2. obtain
ctx.session.username
// 3. The default configuration ( Global configuration ,config/config.default.js)
exports.session = {
key: 'EGG_SESS', // Set up cookies Of key value
maxAge: 24 * 3600 * 1000, // 1 God , Expiration time
httpOnly: true, // Set whether key value pairs can be js visit , The default is true, It is not allowed to be js visit .
encrypt: true,// encryption
renew:true // Every refresh of the page will be delayed
};
// 4. Dynamic configuration
ctx.session.maxAge = 5000; // 5 Seconds past due
ctx.session.username = ' test ';
// 5. Empty session
ctx.session.username = null;
Timing task
// app/schedule/ceshi.js
var i = 1;
module.exports = {
// Set the execution interval of scheduled tasks and other configurations
schedule: {
interval: '5s', // Every time 5 Once per second
type: 'all' // Designate all worker All need to be carried out
},
// Mission
async task(ctx) {
++i;
console.log(i);
}
};
API
1. context
curl
async ceshi() {
// adopt ctx Medium curl Method to get data
let r = await this.ctx.curl('http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1');
// take buffer Type data to json type
let {
result } = JSON.parse(r.data)
return result;
}
Common plug-ins
cache
https://www.npmjs.com/package/egg-cache
https://www.npmjs.com/package/egg-redis
verification
https://github.com/temool/egg-validate-plus
encryption
https://www.npmjs.com/package/egg-jwt
Front end access :header Head add :
// Authorization:"Bearer token value "
Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6MTIzLCJpYXQiOjE1NzkxOTQxNTN9.Ml5B02ZPfYo78QwJic-Jdp2LUi2_AU0RGNgPhhJH--o"
边栏推荐
- 2021 Google vulnerability reward program review
- Talking about Net core how to use efcore to inject multiple instances of a context annotation type for connecting to the master-slave database
- Research Report on market supply and demand and strategy of China's well completion equipment industry
- Statistical learning: logistic regression and cross entropy loss (pytoch Implementation)
- Go语言循环语句(第10课下)
- .Net 应用考虑x64生成
- China Indonesia adhesive market trend report, technological innovation and market forecast
- 嵌入式软件架构设计-函数调用
- Array filter fliter in JS
- Transformer中position encoding实践
猜你喜欢
How can programmers improve the speed of code writing?
Object. Usage of keys()
Statistical learning: logistic regression and cross entropy loss (pytoch Implementation)
Readis configuration and optimization of NoSQL (final chapter)
Opencv learning -- geometric transformation of image processing
Overview of convolutional neural network structure optimization
Go development: how to use go singleton mode to ensure the security of high concurrency of streaming media?
Visual Studio 2019 (LocalDB)MSSQLLocalDB SQL Server 2014 数据库版本为852无法打开,此服务器支持782
[North Asia data recovery] a database data recovery case where the disk on which the database is located is unrecognized due to the RAID disk failure of HP DL380 server
Preliminary practice of niuke.com (10)
随机推荐
How to decrypt worksheet protection password in Excel file
Hash table
Market trend report, technical innovation and market forecast of China's hair repair therapeutic apparatus
How to implicitly pass values when transferring forms
Go语言循环语句(第10课下)
Integration of ongdb graph database and spark
Detailed process of DC-2 range construction and penetration practice (DC range Series)
程序员怎么才能提高代码编写速度?
Research Report on market supply and demand and strategy of surgical stapler industry in China
Software Engineer vs Hardware Engineer
表单传递时,如何隐式将值传过去
ECCV 2022 released: 1629 papers were selected, and the employment rate was less than 20%
Understand asp Net core - Authentication Based on jwtbearer
Laravel simply realizes Alibaba cloud storage + Baidu AI Cloud image review
Final consistency of MESI cache in CPU -- why does CPU need cache
NoSQL之readis配置与优化(终章)
Vscode prompt Please install clang or check configuration 'clang executable‘
Firebird experience summary
Redis: SDS source code analysis
Filtered off site request to