diff --git a/docs/developer-docs/latest/development/backend-customization/services.md b/docs/developer-docs/latest/development/backend-customization/services.md
index fa8f733b86..0b89270f7a 100644
--- a/docs/developer-docs/latest/development/backend-customization/services.md
+++ b/docs/developer-docs/latest/development/backend-customization/services.md
@@ -8,438 +8,47 @@ sidebarDepth: 3
# Services
-Services are a set of reusable functions. They are particularly useful to respect the DRY (don’t repeat yourself) programming concept and to simplify [controllers](/developer-docs/latest/development/backend-customization/controllers.md) logic.
+Services are a set of reusable functions. They are particularly useful to respect the DRY (don’t repeat yourself) programming concept and to simplify [controllers](/developer-docs/latest/development/backend-customization/controllers.md) logic. Just like [all the other parts of the Strapi backend](/developer-docs/latest/development/backend-customization.md), services can be customized.
+
-## Core services
+## Implementation
-When you create a new `Content Type` or a new model, you will see a new empty service has been created. It is because Strapi builds a generic service for your models by default and allows you to override and extend it in the generated files.
+A new service can be implemented:
-### Extending a Model Service
+- with the [interactive CLI command `strapi generate`](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-generate)
+
+- or manually by creating a JavaScript file in the appropriate folder (see [project structure](/developer-docs/latest/setup-deployment-guides/file-structure.md)):
+ - `./src/api/[api-name]/services/` for API services
+ - or `./src/plugins/[plugin-name]/services/` for [plugin services](/developer-docs/latest/developer-resources/plugin-api-reference/server.md#services).
-Here are the core methods (and their current implementation).
-You can simply copy and paste this code to your own service file to customize the methods.
-
-For more information, see the [Query Engine API documentation](/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.md).
-
-::: tip
-In the following example your controller, service and model are named `restaurant`.
-:::
-
-### Utils
-
-If you're extending the `create` or `update` service, first require the following utility function:
-
-```js
-const { isDraft } = require('strapi-utils').contentTypes;
-```
-
-- `isDraft`: This function checks if the entry is a draft.
-
-#### Collection Type
-
-:::: tabs card
-
-::: tab find
-
-##### `find`
-
-```js
-module.exports = {
- /**
- * Promise to fetch all records
- *
- * @return {Promise}
- */
- find(params, populate) {
- return strapi.query('restaurant').find(params, populate);
- },
-};
-```
-
-The `find` function accepts the following arguments:
-
-- `params` (object): the filters for the find request,
-
- The object follows the URL query format (see [API parameters](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#api-parameters)).
-
-```json
-{
- "name": "Tokyo Sushi"
-}
-// or
-{
- "_limit": 20,
- "name_contains": "sushi"
-}
-```
-
-- `populate` (array): the data to populate `["author", "author.name", "comment", "comment.content"]`
-
-:::
-
-::: tab findOne
-
-##### `findOne`
+To manually create a service, export a factory function that returns the service implementation (i.e. an object with methods). This factory function receives the `strapi` instance:
```js
-module.exports = {
- /**
- * Promise to fetch record
- *
- * @return {Promise}
- */
-
- findOne(params, populate) {
- return strapi.query('restaurant').findOne(params, populate);
- },
+/**
+ * @param {{ strapi: import('@strapi/strapi').Strapi }} opts
+ */
+module.exports = ({ strapi }) => {
+ return {
+ archiveArticles(ids) {
+ // do some logic here
+ },
+ };
};
```
-The `find` function accepts the following arguments:
-
-- `params` (object): the filters for the find request.
-
- The object follows the URL query format (see [API parameters](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#api-parameters)).
-
-```json
-{
- "name": "Tokyo Sushi"
-}
-// or
-{
- "name_contains": "sushi"
-}
-```
-
-- `populate` (array): the data to populate `["author", "author.name", "comment", "comment.content"]`
-
+::: strapi Entity Service API
+To get started creating your own services, see Strapi's built-in functions in the [Entity Service API](/developer-docs/latest/developer-resources/database-apis-reference/entity-service-api.md) documentation.
+
:::
-::: tab count
-
-##### `count`
-
-```js
-module.exports = {
- /**
- * Promise to count record
- *
- * @return {Promise}
- */
-
- count(params) {
- return strapi.query('restaurant').count(params);
- },
-};
-```
-
-- `params` (object): this represent filters for your find request.
- The object follow the URL query format, [refer to this documentation.](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#api-parameters).
-
-```json
-{
- "name": "Tokyo Sushi"
-}
-// or
-{
- "name_contains": "sushi"
-}
-```
-
-:::
-
-::: tab create
-
-##### `create`
-
-```js
-const { isDraft } = require('strapi-utils').contentTypes;
-
-module.exports = {
- /**
- * Promise to add record
- *
- * @return {Promise}
- */
-
- async create(data, { files } = {}) {
- const isDraft = isDraft(data, strapi.models.restaurant);
- const validData = await strapi.entityValidator.validateEntityCreation(
- strapi.models.restaurant,
- data,
- { isDraft }
- );
-
- const entry = await strapi.query('restaurant').create(validData);
-
- if (files) {
- // automatically uploads the files based on the entry and the model
- await strapi.entityService.uploadFiles(entry, files, {
- model: 'restaurant',
- // if you are using a plugin's model you will have to add the `source` key (source: 'users-permissions')
- });
- return this.findOne({ id: entry.id });
- }
-
- return entry;
- },
-};
-```
-
-:::
-
-::: tab update
-
-##### `update`
-
-```js
-const { isDraft } = require('strapi-utils').contentTypes;
-
-module.exports = {
- /**
- * Promise to edit record
- *
- * @return {Promise}
- */
-
- async update(params, data, { files } = {}) {
- const existingEntry = await strapi.query('restaurant').findOne(params);
-
- const isDraft = isDraft(existingEntry, strapi.models.restaurant);
- const validData = await strapi.entityValidator.validateEntityUpdate(
- strapi.models.restaurant,
- data,
- { isDraft }
- );
-
- const entry = await strapi.query('restaurant').update(params, validData);
-
- if (files) {
- // automatically uploads the files based on the entry and the model
- await strapi.entityService.uploadFiles(entry, files, {
- model: 'restaurant',
- // if you are using a plugin's model you will have to add the `source` key (source: 'users-permissions')
- });
- return this.findOne({ id: entry.id });
- }
-
- return entry;
- },
-};
-```
-
-- `params` (object): it should look like this `{id: 1}`
-
-:::
-
-::: tab delete
-
-##### `delete`
-
-```js
-module.exports = {
- /**
- * Promise to delete a record
- *
- * @return {Promise}
- */
-
- delete(params) {
- return strapi.query('restaurant').delete(params);
- },
-};
-```
-
-- `params` (object): it should look like this `{id: 1}`
-
-:::
-
-::: tab search
-
-##### `search`
-
-```js
-module.exports = {
- /**
- * Promise to search records
- *
- * @return {Promise}
- */
-
- search(params) {
- return strapi.query('restaurant').search(params);
- },
-};
-```
-
-- `params` (object): this represent filters for your find request.
- The object follow the URL query format, [refer to this documentation.](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#api-parameters).
-
-```json
-{
- "name": "Tokyo Sushi"
-}
-// or
-{
- "name_contains": "sushi"
-}
-```
-
-:::
-
-::: tab countSearch
-
-##### `countSearch`
-
-```js
-module.exports = {
- /**
- * Promise to count searched records
- *
- * @return {Promise}
- */
- countSearch(params) {
- return strapi.query('restaurant').countSearch(params);
- },
-};
-```
-
-- `params` (object): this represent filters for your find request.
- The object follow the URL query format, [refer to this documentation.](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#api-parameters).
-
-```json
-{
- "name": "Tokyo Sushi"
-}
-// or
-{
- "name_contains": "sushi"
-}
-```
-
-:::
-
-::::
-
-#### Single Type
-
-:::: tabs card
-
-::: tab find
-
-##### `find`
-
-```js
-const _ = require('lodash');
-
-module.exports = {
- /**
- * Promise to fetch the record
- *
- * @return {Promise}
- */
- async find(params, populate) {
- const results = await strapi.query('restaurant').find({ ...params, _limit: 1 }, populate);
- return _.first(results) || null;
- },
-};
-```
-
-The `find` function accepts the following arguments:
-
-- `params` (object): the filters for the find request.
-
- The object follows the URL query format (see [API parameters](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#api-parameters)).
-
-- `populate` (array): the data to populate `["author", "author.name", "comment", "comment.content"]`
-
-:::
-
-::: tab createOrUpdate
-
-##### `createOrUpdate`
-
-```js
-const _ = require('lodash');
-
-module.exports = {
- /**
- * Promise to add/update the record
- *
- * @return {Promise}
- */
-
- async createOrUpdate(data, { files } = {}) {
- const results = await strapi.query('restaurant').find({ _limit: 1 });
- const entity = _.first(results) || null;
-
- let entry;
- if (!entity) {
- entry = await strapi.query('restaurant').create(data);
- } else {
- entry = await strapi.query('restaurant').update({ id: entity.id }, data);
- }
-
- if (files) {
- // automatically uploads the files based on the entry and the model
- await strapi.entityService.uploadFiles(entry, files, {
- model: 'restaurant',
- // if you are using a plugin's model you will have to add the `plugin` key (plugin: 'users-permissions')
- });
- return this.findOne({ id: entry.id });
- }
-
- return entry;
- },
-};
-```
-
-:::
-
-::: tab delete
-
-##### `delete`
-
-```js
-module.exports = {
- /**
- * Promise to delete a record
- *
- * @return {Promise}
- */
-
- delete() {
- const results = await strapi.query('restaurant').find({ _limit: 1 });
- const entity = _.first(results) || null;
-
- if (!entity) return;
-
- return strapi.query('restaurant').delete({id: entity.id});
- },
-};
-```
-
-:::
-
-::::
-
-## Custom services
-
-You can also create custom services to build your own business logic.
-
-There are two ways to create a service.
-
-- Using the CLI `strapi generate:service restaurant`.
Read the [CLI documentation](/developer-docs/latest/developer-resources/cli/CLI.md) for more information.
-- Manually create a JavaScript file named in `./api/**/services/`.
-
-### Example
+:::: details Example of an email service
The goal of a service is to store reusable functions. An `email` service could be useful to send emails from different functions in our codebase:
-**Path —** `./api/email/services/Email.js`.
-
```js
-const nodemailer = require('nodemailer');
+// path: ./src/api/email/services/email.js
+
+const nodemailer = require('nodemailer'); // Requires nodemailer to be installed (npm install nodemailer)
// Create reusable transporter object using SMTP transport.
const transporter = nodemailer.createTransport({
@@ -466,15 +75,11 @@ module.exports = {
};
```
-::: tip
-please make sure you installed `nodemailer` (`npm install nodemailer`) for this example.
-:::
-
-The service is now available through the `strapi.services` global variable. We can use it in another part of our codebase. For example a controller like below:
-
-**Path —** `./api/user/controllers/User.js`.
+The service is now available through the `strapi.services` global variable. It can be used in another part of the codebase, like in the following controller:
```js
+// path: ./src/api/user/controllers/user.js
+
module.exports = {
// GET /hello
signup: async ctx => {
@@ -482,7 +87,8 @@ module.exports = {
const user = await User.create(ctx.query);
// Send an email to validate his subscriptions.
- strapi.services.email.send('welcome@mysite.com', user.email, 'Welcome', '...');
+ strapi.service('api::email.send')('welcome@mysite.com', user.email, 'Welcome', '...');
+
// Send response to the server.
ctx.send({
@@ -491,3 +97,25 @@ module.exports = {
},
};
```
+
+::::
+
+::: note
+When a new [content-type](/developer-docs/latest/development/backend-customization/models.md#content-types) is created, Strapi builds a generic service with placeholder code, ready to be customized.
+:::
+
+## Usage
+
+Once a service is created, it's accessible from [controllers](/developer-docs/latest/development/backend-customization/controllers.md) or from other services:
+
+```js
+// access an API service
+strapi.service('api::apiName.serviceName');
+// access a plugin service
+strapi.service('plugin::pluginName.serviceName');
+```
+
+::: tip
+To list all the available services, run `yarn strapi services:list`.
+
+:::