From df822ddbe682eed3fb68c79577dcf921a73cccf8 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Mon, 12 Aug 2019 16:58:18 +0200 Subject: [PATCH 1/3] Update docs and init new Queries doc --- docs/3.0.0-beta.x/api-reference/reference.md | 11 +- docs/3.0.0-beta.x/guides/controllers.md | 16 +- docs/3.0.0-beta.x/guides/queries.md | 596 +++++++------------ docs/3.0.0-beta.x/guides/services.md | 24 +- 4 files changed, 237 insertions(+), 410 deletions(-) diff --git a/docs/3.0.0-beta.x/api-reference/reference.md b/docs/3.0.0-beta.x/api-reference/reference.md index a06178bb08..5a0b64a272 100644 --- a/docs/3.0.0-beta.x/api-reference/reference.md +++ b/docs/3.0.0-beta.x/api-reference/reference.md @@ -4,7 +4,6 @@ - [.admin](#strapi-admin) - [.api](#strapi-api) - [.app](#strapiapp) - - [.bootstrap()](#strapi-bootstrap) - [.config](#strapi-config) - [.controllers](#strapi-controllers) - [.hook](#strapi-hook) @@ -24,7 +23,7 @@ ## strapi.admin -This object contains the controllers, models, services and configurations contained in the `./admin` folder. +This object contains the controllers, models, services and configurations contained in the `strapi-admin` package. ## strapi.api @@ -35,14 +34,6 @@ And by using `strapi.api[:name]` you can access the controllers, services, the m Returns the Koa instance. -## strapi.bootstrap - -Returns a `Promise`. When resolved, it means that the `./config/functions/bootstrap.js` has been executed. Otherwise, it throws an error. - -::: note -You can also access to the bootstrap function through `strapi.config.functions.boostrap`. -::: - ## strapi.config Returns an object that represents the configurations of the project. Every JavaScript or JSON file located in the `./config` folder will be parsed into the `strapi.config` object. diff --git a/docs/3.0.0-beta.x/guides/controllers.md b/docs/3.0.0-beta.x/guides/controllers.md index 25d8cff934..386174f352 100644 --- a/docs/3.0.0-beta.x/guides/controllers.md +++ b/docs/3.0.0-beta.x/guides/controllers.md @@ -80,7 +80,13 @@ module.exports = { */ create(ctx) { - return strapi.services.product.create(ctx.request.body); + if (ctx.is('multipart')) { + // parse the specific strapi's formData format and return the data and the files + const { data, files } = this.parseMultipartData(ctx); + return service.create(data, { files }); + } + + return service.create(ctx.request.body); }, }; ``` @@ -96,7 +102,13 @@ module.exports = { */ update(ctx) { - return strapi.services.product.update(ctx.params, ctx.request.body); + if (ctx.is('multipart')) { + // parse the specific strapi's formData format and return the data and the files + const { data, files } = this.parseMultipartData(ctx); + return service.update(ctx.params, data, { files }); + } + + return service.update(ctx.params, ctx.request.body); }, }; ``` diff --git a/docs/3.0.0-beta.x/guides/queries.md b/docs/3.0.0-beta.x/guides/queries.md index 8083649a6c..42b595a152 100644 --- a/docs/3.0.0-beta.x/guides/queries.md +++ b/docs/3.0.0-beta.x/guides/queries.md @@ -1,406 +1,214 @@ # Queries -Strapi provides a utility function `strapi.query` to make database queries ORM agnostic. +Strapi provides a utility function `strapi.query` to make database queries. -## Core Queries +You can just call `strapi.query(modelName, pluginName)` to access the query API for any model. + +Those queries handle for you specific Strapi features like `groups` `filters` and `search`. + +## API Reference + +### `findOne` + +This method returns the first entry matching some basic params. +You can also pass a populate option to specify which relations you want to be populated. + +#### Examples + +**Find one by id**: + +```js +strapi.query('post').findOne({ id: 1 }); +``` + +**Find one by title**: + +```js +strapi.query('post').findOne({ title: 'my title' }); +``` + +**Find one by title and creation_date**: + +```js +strapi + .query('post') + .findOne({ title: 'my title', created_at: '2019-01-01T00:00:00.000Z' }); +``` + +**Find one by id and populate a relation** + +```js +strapi.query('post').findOne({ id: 1 }, ['tag', 'tag.picture']); +``` + +### `find` + +This method returns a list of entries matching Strapi filters. +You can also pass a populate option to specify which relations you want to be populated. + +#### Examples + +**Find by id**: + +```js +strapi.query('post').find({ id: 1 }); +``` + +**Find by in IN, with a limit**: + +```js +strapi.query('post').find({ _limit: 10, id_in: [1, 2] }); +``` + +**Find by date orderBy title**: + +```js +strapi + .query('post') + .find({ date_gt: '2019-01-01T00:00:00.000Z', _sort: 'title:desc' }); +``` + +**Find by id not in and populate a relation. Skip the first ten results** + +```js +strapi.query('post').find({ id_nin: [1], _start: 10 }, ['tag', 'tag.picture']); +``` + +### `create` + +Creates an entry in the database and returns the entry. + +#### Example + +```js +strapi.query('post').create({ + title: 'Mytitle', + // this is a group field. the order is persisted in db. + seo: [ + { + metadata: 'description', + value: 'My description', + }, + { + metadata: 'keywords', + value: 'someKeyword,otherKeyword', + }, + ], + // pass the id of a media to link it to the entry + picture: 1, + // automatically creates the relations when passing the ids in the field + tags: [1, 2, 3], +}); +``` + +### `update` + +Updates an entry in the database and returns the entry. + +#### Examples + +**Update by id** + +```js +strapi.query('post').update( + { id: 1 }, + { + title: 'Mytitle', + seo: [ + { + metadata: 'description', + value: 'My description', + }, + { + metadata: 'keywords', + value: 'someKeyword,otherKeyword', + }, + ], + // pass the id of a media to link it to the entry + picture: 1, + // automatically creates the relations when passing the ids in the field + tags: [1, 2, 3], + } +); +``` + +When updating an entry with its groups beware that if you send the groups without any `id` the previous groups will be deleted and replaced. You can update the groups by sending there `id` : + +**Update by id and update previous groups** + +```js +strapi.query('post').update( + { id: 1 }, + { + title: 'Mytitle', + seo: [ + { + id: 2 + metadata: 'keywords', + value: 'someKeyword,otherKeyword', + }, + { + id: 1 + metadata: 'description', + value: 'My description', + } + ], + // pass the id of a media to link it to the entry + picture: 1, + // automatically creates the relations when passing the ids in the field + tags: [1, 2, 3], + } +); +``` + +**Partial update by title** + +```js +strapi.query('post').update( + { title: 'specific title' }, + { + title: 'Mytitle', + } +); +``` + +### `delete` + +Deletes and entry and return it's value before deletion. +You can delete multiple entries at once with the passed params. + +#### Examples + +**Delete one by id** + +```js +strapi.query('post').delete({ id: 1 }); +``` + +**Delete multiple by field** + +```js +strapi.query('post').delete({ lang: 'en' }); +``` + +### `count` + +### `search` + +### `countSearch` + +## Custom Queries In Strapi's [core services](./services.md#core-services) you can see we call a `strapi.query` function. -When customizing your model services you might want to implement some custom database queries. -To help you with that here is the current implementation of the queries for both `bookshelf` and `mongoose`. +When customizing your model services you might want to implement some custom database queries. directly with the underlying ORM (bookshelf or mongoose). -You can simply copy and paste the code in your custom services. +To achieve that you can take some inspiration from the current code in the ORM queries utils. ### Bookshelf -```js -const _ = require('lodash'); -const { convertRestQueryParams, buildQuery } = require('strapi-utils'); +You can see the current implementation of bookshelf queries [here](https://github.com/strapi/strapi/tree/master/packages/strapi-hook-bookshelf/lib/queries.js) -module.exports = ({ model }) => { - return { - find(params, populate) { - const withRelated = - populate || - model.associations - .filter(ast => ast.autoPopulate !== false) - .map(ast => ast.alias); +### Mongoose` - const filters = convertRestQueryParams(params); - - return model - .query(buildQuery({ model, filters })) - .fetchAll({ withRelated }); - }, - - findOne(params, populate) { - const withRelated = - populate || - model.associations - .filter(ast => ast.autoPopulate !== false) - .map(ast => ast.alias); - - return model - .forge({ - [model.primaryKey]: params[model.primaryKey] || params.id, - }) - .fetch({ - withRelated, - }); - }, - - count(params = {}) { - const { where } = convertRestQueryParams(params); - - return model.query(buildQuery({ model, filters: { where } })).count(); - }, - - async create(values) { - const relations = _.pick( - values, - model.associations.map(ast => ast.alias) - ); - const data = _.omit(values, model.associations.map(ast => ast.alias)); - - // Create entry with no-relational data. - const entry = await model.forge(data).save(); - - // Create relational data and return the entry. - return model.updateRelations({ id: entry.id, values: relations }); - }, - - async update(params, values) { - // Extract values related to relational data. - const relations = _.pick( - values, - model.associations.map(ast => ast.alias) - ); - const data = _.omit(values, model.associations.map(ast => ast.alias)); - - // Create entry with no-relational data. - await model.forge(params).save(data); - - // Create relational data and return the entry. - return model.updateRelations( - Object.assign(params, { values: relations }) - ); - }, - - async delete(params) { - params.values = {}; - model.associations.map(association => { - switch (association.nature) { - case 'oneWay': - case 'oneToOne': - case 'manyToOne': - case 'oneToManyMorph': - params.values[association.alias] = null; - break; - case 'oneToMany': - case 'manyToMany': - case 'manyToManyMorph': - params.values[association.alias] = []; - break; - default: - } - }); - - await model.updateRelations(params); - return model.forge(params).destroy(); - }, - - search(params, populate) { - // Convert `params` object to filters compatible with Bookshelf. - const filters = strapi.utils.models.convertParams(model.globalId, params); - - // Select field to populate. - const withRelated = - populate || - model.associations - .filter(ast => ast.autoPopulate !== false) - .map(ast => ast.alias); - - return model - .query(qb => { - buildSearchQuery(qb, model, params); - - if (filters.sort) { - qb.orderBy(filters.sort.key, filters.sort.order); - } - - if (filters.start) { - qb.offset(_.toNumber(filters.start)); - } - - if (filters.limit) { - qb.limit(_.toNumber(filters.limit)); - } - }) - .fetchAll({ - withRelated, - }); - }, - - countSearch(params) { - return model.query(qb => buildSearchQuery(qb, model, params)).count(); - }, - }; -}; - -/** - * util to build search query - * @param {*} qb - * @param {*} model - * @param {*} params - */ -const buildSearchQuery = (qb, model, params) => { - const query = (params._q || '').replace(/[^a-zA-Z0-9.-\s]+/g, ''); - - const associations = model.associations.map(x => x.alias); - - const searchText = Object.keys(model._attributes) - .filter( - attribute => - attribute !== model.primaryKey && !associations.includes(attribute) - ) - .filter(attribute => - ['string', 'text'].includes(model._attributes[attribute].type) - ); - - const searchInt = Object.keys(model._attributes) - .filter( - attribute => - attribute !== model.primaryKey && !associations.includes(attribute) - ) - .filter(attribute => - ['integer', 'decimal', 'float'].includes( - model._attributes[attribute].type - ) - ); - - const searchBool = Object.keys(model._attributes) - .filter( - attribute => - attribute !== model.primaryKey && !associations.includes(attribute) - ) - .filter(attribute => - ['boolean'].includes(model._attributes[attribute].type) - ); - - if (!_.isNaN(_.toNumber(query))) { - searchInt.forEach(attribute => { - qb.orWhereRaw(`${attribute} = ${_.toNumber(query)}`); - }); - } - - if (query === 'true' || query === 'false') { - searchBool.forEach(attribute => { - qb.orWhereRaw(`${attribute} = ${_.toNumber(query === 'true')}`); - }); - } - - // Search in columns with text using index. - switch (model.client) { - case 'mysql': - qb.orWhereRaw( - `MATCH(${searchText.join(',')}) AGAINST(? IN BOOLEAN MODE)`, - `*${query}*` - ); - break; - case 'pg': { - const searchQuery = searchText.map(attribute => - _.toLower(attribute) === attribute - ? `to_tsvector(${attribute})` - : `to_tsvector('${attribute}')` - ); - - qb.orWhereRaw(`${searchQuery.join(' || ')} @@ to_tsquery(?)`, query); - break; - } - } -}; -``` - -### Mongoose - -```js -const _ = require('lodash'); -const { convertRestQueryParams, buildQuery } = require('strapi-utils'); - -module.exports = ({ model, strapi }) => { - const assocs = model.associations.map(ast => ast.alias); - - const defaultPopulate = model.associations - .filter(ast => ast.autoPopulate !== false) - .map(ast => ast.alias); - - return { - find(params, populate) { - const populateOpt = populate || defaultPopulate; - - const filters = convertRestQueryParams(params); - - return buildQuery({ - model, - filters, - populate: populateOpt, - }); - }, - - findOne(params, populate) { - const populateOpt = populate || defaultPopulate; - - return model - .findOne({ - [model.primaryKey]: params[model.primaryKey] || params.id, - }) - .populate(populateOpt); - }, - - count(params) { - const filters = convertRestQueryParams(params); - - return buildQuery({ - model, - filters: { where: filters.where }, - }).count(); - }, - - async create(values) { - // Extract values related to relational data. - const relations = _.pick(values, assocs); - const data = _.omit(values, assocs); - - // Create entry with no-relational data. - const entry = await model.create(data); - - // Create relational data and return the entry. - return model.updateRelations({ _id: entry.id, values: relations }); - }, - - async update(params, values) { - // Extract values related to relational data. - const relations = _.pick(values, assocs); - const data = _.omit(values, assocs); - - // Update entry with no-relational data. - await model.updateOne(params, data, { multi: true }); - - // Update relational data and return the entry. - return model.updateRelations( - Object.assign(params, { values: relations }) - ); - }, - - async delete(params) { - const data = await model - .findOneAndRemove(params, {}) - .populate(defaultPopulate); - - if (!data) { - return data; - } - - await Promise.all( - model.associations.map(async association => { - if (!association.via || !data._id || association.dominant) { - return true; - } - - const search = - _.endsWith(association.nature, 'One') || - association.nature === 'oneToMany' - ? { [association.via]: data._id } - : { [association.via]: { $in: [data._id] } }; - const update = - _.endsWith(association.nature, 'One') || - association.nature === 'oneToMany' - ? { [association.via]: null } - : { $pull: { [association.via]: data._id } }; - - // Retrieve model. - const model = association.plugin - ? strapi.plugins[association.plugin].models[ - association.model || association.collection - ] - : strapi.models[association.model || association.collection]; - - return model.update(search, update, { multi: true }); - }) - ); - - return data; - }, - - search(params, populate) { - // Convert `params` object to filters compatible with Mongo. - const filters = strapi.utils.models.convertParams(model.globalId, params); - - const $or = buildSearchOr(model, params._q); - - return model - .find({ $or }) - .sort(filters.sort) - .skip(filters.start) - .limit(filters.limit) - .populate(populate || defaultPopulate); - }, - - countSearch(params) { - const $or = buildSearchOr(model, params._q); - return model.find({ $or }).countDocuments(); - }, - }; -}; - -const buildSearchOr = (model, query) => { - return Object.keys(model.attributes).reduce((acc, curr) => { - switch (model.attributes[curr].type) { - case 'integer': - case 'float': - case 'decimal': - if (!_.isNaN(_.toNumber(query))) { - return acc.concat({ [curr]: query }); - } - - return acc; - case 'string': - case 'text': - case 'password': - return acc.concat({ [curr]: { $regex: query, $options: 'i' } }); - case 'boolean': - if (query === 'true' || query === 'false') { - return acc.concat({ [curr]: query === 'true' }); - } - - return acc; - default: - return acc; - } - }, []); -}; -``` - -## Understanding queries - -`strapi.query` will generate a queries object by passing a model to the factory function matching the model's ORM. - -In this example the User model from the Users and Permissions plugin is used. -By default the model is passed to `strapi/lib/core-api/queries/bookshelf.js` or `strapi/lib/core-api/queries/mongoose.js` depending on your connection configuration. - -```js -const queries = strapi.query('users', 'users-permissions'); - -// makes the bookshelf or mongoose queries available with a specific model binded to them -queries.find(); -``` - -### Usage in plugins - -To make plugins ORM agnostic, we create a queries function for every plugin that will either load the queries from the plugin's `config/queries` folder if it exists or use the default queries from the `core-api/queries` folder. - -```js -// this will call the queries defined in the users-permissions plugin -// with the model user from the users-permissions plugin -strapi.plugins['users-permissions'].queries('user', 'users-permissions').find(); -``` +You can see the current implementation of mongoose queries [here](https://github.com/strapi/strapi/tree/master/packages/strapi-hook-mongoose/lib/queries.js) diff --git a/docs/3.0.0-beta.x/guides/services.md b/docs/3.0.0-beta.x/guides/services.md index d7900a0ce5..71955ac678 100644 --- a/docs/3.0.0-beta.x/guides/services.md +++ b/docs/3.0.0-beta.x/guides/services.md @@ -74,8 +74,16 @@ module.exports = { * @return {Promise} */ - create(values) { - return strapi.query(Product).create(values); + async create(data, { files } = {}) { + const entry = await strapi.query(model).create(data); + + if (files) { + // automatically uploads the files based on the entry and the model + await this.uploadFiles(entry, files, { model }); + return this.findOne({ id: entry.id }); + } + + return entry; }, }; ``` @@ -90,8 +98,16 @@ module.exports = { * @return {Promise} */ - update(params, values) { - return strapi.query(Product).update(params, values); + async update(params, data, { files } = {}) { + const entry = await strapi.query(model).update(params, data); + + if (files) { + // automatically uploads the files based on the entry and the model + await this.uploadFiles(entry, files, { model }); + return this.findOne({ id: entry.id }); + } + + return entry; }, }; ``` From 5a4b588ea2c26afd87db962ca292301070da8db1 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Mon, 12 Aug 2019 17:32:09 +0200 Subject: [PATCH 2/3] Make model accessible through query to simplify usage --- docs/3.0.0-beta.x/guides/queries.md | 91 ++++++++++++++++++++++++++--- packages/strapi/lib/Strapi.js | 3 + 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/docs/3.0.0-beta.x/guides/queries.md b/docs/3.0.0-beta.x/guides/queries.md index 42b595a152..695e0696d1 100644 --- a/docs/3.0.0-beta.x/guides/queries.md +++ b/docs/3.0.0-beta.x/guides/queries.md @@ -32,7 +32,7 @@ strapi.query('post').findOne({ title: 'my title' }); ```js strapi .query('post') - .findOne({ title: 'my title', created_at: '2019-01-01T00:00:00.000Z' }); + .findOne({ title: 'my title', created_at: '2019-01-01T00:00:00Z' }); ``` **Find one by id and populate a relation** @@ -65,7 +65,7 @@ strapi.query('post').find({ _limit: 10, id_in: [1, 2] }); ```js strapi .query('post') - .find({ date_gt: '2019-01-01T00:00:00.000Z', _sort: 'title:desc' }); + .find({ date_gt: '2019-01-01T00:00:00Z', _sort: 'title:desc' }); ``` **Find by id not in and populate a relation. Skip the first ten results** @@ -132,7 +132,7 @@ strapi.query('post').update( ); ``` -When updating an entry with its groups beware that if you send the groups without any `id` the previous groups will be deleted and replaced. You can update the groups by sending there `id` : +When updating an entry with its groups beware that if you send the groups without any `id` the previous groups will be deleted and replaced. You can update the groups by sending their `id` with the rest of the fields: **Update by id and update previous groups** @@ -193,22 +193,95 @@ strapi.query('post').delete({ lang: 'en' }); ### `count` +Returns the count of entries matching Strapi filters. + +#### Examples + +**Count by lang** + +```js +strapi.query('post').count({ lang: 'en' }); +``` + +**Count by title contains** + +```js +strapi.query('post').count({ title_contains: 'food' }); +``` + +**Count by date less than** + +```js +strapi.query('post').count({ date_lt: '2019-08-01T00:00:00Z' }); +``` + ### `search` +Returns entries base on a search on all fields allowing it. (this feature will return all entries on sqlite). + +#### Examples + +**Search first ten starting at 20** + +```js +strapi.query('post').search({ _q: 'my search query', _limit: 10, _start: 20 }); +``` + +**Search and sort** + +```js +strapi + .query('post') + .search({ _q: 'my search query', _limit: 100, _sort: 'date:desc' }); +``` + ### `countSearch` +Returns the total count of entries mattching a search. (this feature will return all entries on sqlite). + +#### Example + +```js +strapi.query('post').countSearch({ _q: 'my search query' }); +``` + ## Custom Queries -In Strapi's [core services](./services.md#core-services) you can see we call a `strapi.query` function. +When you want to customize your services or create new ones you will have to build your queries with the underlying ORM models. -When customizing your model services you might want to implement some custom database queries. directly with the underlying ORM (bookshelf or mongoose). +To access the underlying model: -To achieve that you can take some inspiration from the current code in the ORM queries utils. +```js +strapi.query(modelName, plugin).model; +``` + +Then you can run any queries available on the model. You should refer to the specific ORM documentation for more details: ### Bookshelf -You can see the current implementation of bookshelf queries [here](https://github.com/strapi/strapi/tree/master/packages/strapi-hook-bookshelf/lib/queries.js) +Documentation: [https://bookshelfjs.org/](https://bookshelfjs.org/) -### Mongoose` +**Example** -You can see the current implementation of mongoose queries [here](https://github.com/strapi/strapi/tree/master/packages/strapi-hook-mongoose/lib/queries.js) +```js +strapi + .query('post') + .model.query(qb => { + qb.where('id', 1); + }) + .fetch(); +``` + +### Mongoose + +Documentation: [https://mongoosejs.com/](https://mongoosejs.com/) + +**Example** + +```js +strapi + .query('post') + .model.find({ + { date: { $gte: '2019-01-01T00.00.00Z } + }); +``` diff --git a/packages/strapi/lib/Strapi.js b/packages/strapi/lib/Strapi.js index 97d6734c98..fda2f74a51 100644 --- a/packages/strapi/lib/Strapi.js +++ b/packages/strapi/lib/Strapi.js @@ -472,6 +472,9 @@ class Strapi extends EventEmitter { // custom queries made easy Object.assign(query, { + get model() { + return model; + }, custom(mapping) { if (typeof mapping === 'function') { return mapping.bind(query, { model, modelKey }); From 2f7927854bb2afd66a7f2ffa85479c957bfbd00d Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Tue, 13 Aug 2019 10:41:11 +0200 Subject: [PATCH 3/3] Fix typos --- docs/3.0.0-beta.x/guides/controllers.md | 4 ++-- docs/3.0.0-beta.x/guides/queries.md | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/3.0.0-beta.x/guides/controllers.md b/docs/3.0.0-beta.x/guides/controllers.md index 386174f352..19e974407e 100644 --- a/docs/3.0.0-beta.x/guides/controllers.md +++ b/docs/3.0.0-beta.x/guides/controllers.md @@ -81,7 +81,7 @@ module.exports = { create(ctx) { if (ctx.is('multipart')) { - // parse the specific strapi's formData format and return the data and the files + // Parses strapi's formData format const { data, files } = this.parseMultipartData(ctx); return service.create(data, { files }); } @@ -103,7 +103,7 @@ module.exports = { update(ctx) { if (ctx.is('multipart')) { - // parse the specific strapi's formData format and return the data and the files + // Parses strapi's formData format const { data, files } = this.parseMultipartData(ctx); return service.update(ctx.params, data, { files }); } diff --git a/docs/3.0.0-beta.x/guides/queries.md b/docs/3.0.0-beta.x/guides/queries.md index 695e0696d1..c9eed07632 100644 --- a/docs/3.0.0-beta.x/guides/queries.md +++ b/docs/3.0.0-beta.x/guides/queries.md @@ -4,7 +4,7 @@ Strapi provides a utility function `strapi.query` to make database queries. You can just call `strapi.query(modelName, pluginName)` to access the query API for any model. -Those queries handle for you specific Strapi features like `groups` `filters` and `search`. +These queries handle for you specific Strapi features like `groups`, `filters` and `search`. ## API Reference @@ -32,7 +32,7 @@ strapi.query('post').findOne({ title: 'my title' }); ```js strapi .query('post') - .findOne({ title: 'my title', created_at: '2019-01-01T00:00:00Z' }); + .findOne({ title: 'my title', date: '2019-01-01T00:00:00Z' }); ``` **Find one by id and populate a relation** @@ -174,7 +174,7 @@ strapi.query('post').update( ### `delete` -Deletes and entry and return it's value before deletion. +Deletes and entry and return its value before deletion. You can delete multiple entries at once with the passed params. #### Examples @@ -217,7 +217,7 @@ strapi.query('post').count({ date_lt: '2019-08-01T00:00:00Z' }); ### `search` -Returns entries base on a search on all fields allowing it. (this feature will return all entries on sqlite). +Returns entries based on a search on all fields allowing it. (this feature will return all entries on sqlite). #### Examples @@ -237,7 +237,7 @@ strapi ### `countSearch` -Returns the total count of entries mattching a search. (this feature will return all entries on sqlite). +Returns the total count of entries based on a search. (this feature will return all entries on sqlite). #### Example