strapi/docs/sql/orm.md
2016-03-22 18:11:11 +01:00

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 the connections 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 to save.

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 to save.

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 to fetch.

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 to fetch.

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 to save.

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 to save.

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 to save.

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 to save.

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 to save.

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 to save.

Returns: Promise.