14 KiB
Executable File
SQL ORM
The SQL ORM aims to provide a simple library for common tasks when querying databases in JavaScript, and forming relations between these objects, taking a lot of ideas from the the Data Mapper Pattern.
The Strapi SQL ORM doesn't force you to use any specific validation scheme, provides flexible and efficient relation/nested-relation loading, and first class transaction support.
It's a lean Object Relational Mapper, allowing you to drop down to the raw interface whenever you need a custom query that doesn't quite fit with the stock conventions.
Promises
The Strapi SQL ORM uses its own copy of the "bluebird" promise library. That means methods are chainables and return promises.
User.forge()
.where('reviews', '>', 100)
.limit(10)
.offset(30)
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
Query language basics
Fetch a record
Example:
User.forge({id: 123})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
The console.log()
will print out:
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20
}
It is also possible to eager loading any specified relations named on the model:
User.forge({id: 123})
.fetch({
withRelated: ['friends', 'likes']
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
The console.log()
will print out:
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"friends": [{
"id": 99,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}, {
"id": 48,
"firstname": "Andrea",
"lastname": "Nelson",
"age": 32
}],
"likes": [{
"post_id": 1829,
"date": "2016-04-22T06:00:00Z"
}, {
"post_id": 7849,
"date": "2016-03-22T07:32:45Z"
}]
}
Fetch records
Example:
User.forge()
.fetchAll()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
The console.log()
will print out:
[{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20
}, {
"id": 99,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}, {
...
}]
It is also possible to eager loading any specified relations named on the model:
User.forge()
.fetchAll({
withRelated: ['friends', 'likes']
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
Create a record
Example:
User.forge({
'firstname': 'John',
'lastname': 'Doe',
'age': 20
})
.save()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
The console.log()
will print out:
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20
}
Create a record with one-to-one/one-to-many relationship
The only difference between the two relationship cases will be on the database constraints layer:
- one-to-one: it will allow only one user with the sponsor ID
849
- one-to-many: it will allow more than one user with the sponsor ID
849
Example:
User.forge({
'firstname': 'John',
'lastname': 'Doe',
'age': 20,
'sponsor': 849
})
.save()
.then(function (model) {
return model.fetch({
withRelated: 'sponsor'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
The console.log()
will print out:
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"sponsor": {
"id": 849,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}
}
Create a record with many-to-many relationship
const friend1 = User.forge({id: 1});
const friend2 = User.forge({id: 2});
User.forge({
'firstname': 'John',
'lastname': 'Doe',
'age': 20
})
.save()
.tap(function (model) {
return model.friends().attach([friend1, friend2]);
})
.then(function (model) {
return model.fetch({
withRelated: 'friends'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
The console.log()
will print out:
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"friends": [{
"id": 1,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}, {
"id": 2,
"firstname": "Andrea",
"lastname": "Nelson",
"age": 32
}]
}
Update a record
User.forge({
id: 123
})
.save({
'lastname': 'Does',
'age': 40
}, {path: true})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function(err) {
reject(err);
});
The console.log()
will print out:
{
"id": 123,
"firstname": "John",
"lastname": "Does",
"age": 40,
}
Update a record with a one-to-one/one-to-many relationship
This is exactly the same as update a record without relationship.
User.forge({
id: 123
})
.save({
'sponsor': 756
}, {path: true})
.then(function (model) {
return model.fetch({
withRelated: 'sponsor'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function(err) {
reject(err);
});
The console.log()
will print out:
{
"id": 123,
"firstname": "John",
"lastname": "Does",
"age": 40,
"sponsor": {
"id": 756,
"firstname": "Martin",
"lastname": "Terry",
"age": 37
}
}
Update a record with a many-to-many relationship
This is also exactly the same as update a record without relationship. However, there are two cases:
- Add new relationship(s) to a record
- Remove current(s) relationship(s) to the record
Add new relationship(s) to a record
const friend3 = User.forge({id: 3});
User.forge({
id: 123
})
.friends()
.attach([friend3])
.then(function (model) {
return model.fetch({
withRelated: 'friends'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
The console.log()
will print out:
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"friends": [{
"id": 1,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}, {
"id": 2,
"firstname": "Andrea",
"lastname": "Nelson",
"age": 32
}, {
"id": 3,
"firstname": "Paul",
"lastname": "Thomas",
"age": 78
}]
}
Remove current(s) relationship(s) to a record
const friend2 = User.forge({id: 2});
const friend3 = User.forge({id: 3});
User.forge({
id: 123
})
.friends()
.detach([friend2, friend3])
.then(function (model) {
return model.fetch({
withRelated: 'friends'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
The console.log()
will print out:
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"friends": [{
"id": 1,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}]
}
Delete a record
User.forge({
id: 123
})
.destroy()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function(err) {
console.log(err);
});
The console.log()
will print out:
null
Query options
raw
User.forge({
id: 123
})
.query(function (qb) {
qb.offset(0).limit(10);
}))
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
})
count
Count the entries for a query:
User.forge()
.where({
'age': 20
})
.count()
.then(function (count) {
console.log(count);
})
.catch(function (err) {
console.log(err);
})
where
Adds a where
clause to the query:
User.forge()
.where({
'firstname': 'John',
'age': 20
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
orderBy
Adds an orderBy
clause to the query.
Using the object syntax:
User.forge()
.query({
'orderBy': 'firstname'
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
This syntax doesn't allow to update ASC
/ DESC
parameter. To do so, you have to use the function syntax below:
User.forge()
.query(function (qb) {
qb.orderBy('firstname', 'desc');
}))
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
groupBy
Adds an groupBy
clause to the query:
User.forge()
.query({
'groupBy': 'age'
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
limit
Adds an limit
clause to the query:
User.forge()
.query({
'limit': 20
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
offset
Adds an offset
clause to the query:
User.forge()
.query({
'offset': 10
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
Lifecycle events
Lifecycle callbacks are functions you can define to run at certain times in a query. They are hooks that you can tap into in order to change data. Strapi exposes a handful of lifecycle callbacks by default.
Lifecycle events must be placed in your JavaScript file of your models.
Common callbacks
beforeSave
Fired before an insert
or update
query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will cancel the save.
Code:
beforeSave: (model, attrs, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.attrs
: Attributes that will be inserted or updated.options
: Options object passed tosave
.
Returns: Promise.
afterSave
Fired after an insert
or update
query.
Code:
afterSave: (model, response, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.response
: The database response.options
: Options object passed tosave
.
Returns: Promise.
Callbacks on fetch
beforeFetch
Fired before a fetch
operation. A promise may be returned from the event handler for async behavior.
Code:
beforeFetch: (model, columns, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.columns
: The columns to be retrieved by the query.options
: Options object passed tofetch
.
Returns: Promise.
afterFetch
Fired after a fetch
operation. A promise may be returned from the event handler for async behavior.
Code:
afterFetch: (model, response, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.response
: SQL query response.options
: Options object passed tofetch
.
Returns: Promise. If the handler returns a promise, fetch
will wait for it to be resolved.
Callbacks on create
beforeCreate
Fired before insert
query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will cancel the save operation.
Code:
beforeCreate: (model, attrs, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.attrs
: Attributes that will be inserted.options
: Options object passed tosave
.
Returns: Promise.
afterCreate
Fired after an insert
query.
Code:
afterCreate: (model, attrs, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.attrs
: Attributes inserted.options
: Options object passed tosave
.
Returns: Promise.
Callbacks on update
beforeUpdate
Fired before an update
query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will cancel the save operation.
Code:
beforeUpdate: (model, attrs, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.attrs
: Attributes that will be updated.options
: Options object passed tosave
.
Returns: Promise.
afterUpdate
Fired after an update
query.
Code:
afterUpdate: (model, attrs, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.attrs
: Attributes updated.options
: Options object passed tosave
.
Returns: Promise.
Callbacks on destroy
beforeDestroy
Fired before a delete
query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will reject the promise and cancel the deletion.
Code:
beforeDestroy: (model, attrs, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.attrs
: Attributes that will be destroyed.options
: Options object passed tosave
.
Returns: Promise.
afterDestroy
Fired before a delete
query. A promise may be returned from the event handler for async behavior.
Code:
afterDestroy: (model, attrs, options) => {
return new Promise();
},
Parameters:
model
: The model firing the event.attrs
: Attributes destroyed.options
: Options object passed tosave
.
Returns: Promise.