14 KiB
title |
---|
SQL ORM |
The Strapi philosophy regarding databases and ORMs is very simple: a unique ORM for SQL databases and one selected ORM for each NoSQL database.
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.
With a concise, literate codebase, the Strapi SQL ORM is simple to read, understand, and extend. It 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.
In this document we assume the
connection
is one of theconnections
set in your config.
const connection = strapi.connections.default;
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.