diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 187d84aed0..3587d6d5a1 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -50,14 +50,16 @@ const sidebar = { children: [ ['/developer-docs/latest/setup-deployment-guides/configurations/required/databases.md', 'Database'], ['/developer-docs/latest/setup-deployment-guides/configurations/required/server.md', 'Server'], + ['/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md', 'Admin panel'], + ['/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md', 'Middlewares'], ] }, { title: 'Optional configurations', collapsable: true, children: [ - ['/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md', 'Middlewares'], ['/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md', 'Functions'], + ['/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md', 'Cron jobs'], ['/developer-docs/latest/setup-deployment-guides/configurations/optional/api.md', 'API'], ['/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md', 'Plugins'], ['/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md', 'Environment'], @@ -149,11 +151,13 @@ const sidebar = { initialOpenGroupIndex: -1, // make sure that no subgroup is expanded by default children: [ { - title: 'Backend customization', + title: 'Back-end customization', collapsable: true, + path: '/developer-docs/latest/development/backend-customization', children: [ - ['/developer-docs/latest/development/backend-customization/routing.md', 'Routing'], + ['/developer-docs/latest/development/backend-customization/routes.md', 'Routes'], ['/developer-docs/latest/development/backend-customization/policies.md', 'Policies'], + ['/developer-docs/latest/development/backend-customization/middlewares.md', 'Middlewares'], ['/developer-docs/latest/development/backend-customization/controllers.md', 'Controllers'], ['/developer-docs/latest/development/backend-customization/requests-responses.md', 'Requests & Responses'], ['/developer-docs/latest/development/backend-customization/services.md', 'Services'], @@ -334,15 +338,15 @@ const sidebar = { ] }, ['/developer-docs/latest/developer-resources/cli/CLI', 'Command Line Interface'], + ['/developer-docs/latest/developer-resources/error-handling.md', 'Error handling'], ], }, { title: 'πŸ“š Guides', collapsable: true, children: [ - ['/developer-docs/latest/guides/api-token', 'API tokens'], ['/developer-docs/latest/guides/auth-request', 'Authenticated request'], - ['/developer-docs/latest/guides/count-graphql', 'Count with GraphQL'], + // ['/developer-docs/latest/guides/count-graphql', 'Count with GraphQL'], ['/developer-docs/latest/guides/slug', 'Create a slug system'], ['/developer-docs/latest/guides/is-owner', 'Create is owner policy'], ['/developer-docs/latest/guides/custom-admin', 'Custom admin'], diff --git a/docs/developer-docs/latest/developer-resources/cli/CLI.md b/docs/developer-docs/latest/developer-resources/cli/CLI.md index 1105655de7..a0fce2fba4 100644 --- a/docs/developer-docs/latest/developer-resources/cli/CLI.md +++ b/docs/developer-docs/latest/developer-resources/cli/CLI.md @@ -95,6 +95,15 @@ options: [--no-optimization] - **strapi build --no-optimization**
Builds the administration panel without minimizing the assets. The build duration is faster. +## strapi watch-admin + +Starts the admin server. Strapi should already be running with `strapi develop`. + +```sh +strapi watch-admin +options: [--browser ] +``` + ## strapi configuration:dump **Alias**: `config:dump` @@ -183,181 +192,75 @@ strapi admin:reset-user-password --email=chef@strapi.io --password=Gourmet1234 | -p, --password | string | New password for the user | | -h, --help | | display help for command | -## strapi generate:api +## strapi generate -Scaffold a complete API with its configurations, controller, model and service. +Run a fully interactive CLI to generate APIs, [controllers](/developer-docs/latest/development/backend-customization/controllers.md), [content-types](/developer-docs/latest/development/backend-customization/models.md), [plugins](/developer-docs/latest/development/plugins-development.md#creating-a-plugin), [policies](/developer-docs/latest/development/backend-customization/policies.md), [middlewares](/developer-docs/latest/development/backend-customization/middlewares.md) and [services](/developer-docs/latest/development/backend-customization/services.md). -```bash -strapi generate:api [] - -options: [--plugin ] +```sh +strapi generate ``` -- **strapi generate:api <name>**
- Generates an API called **<name>** in the `./api` folder at the root of your project. - -- **strapi generate:api <name> --draft-and-publish=true**
- Generates an API called **<name>** in the `./api` folder at the root of your project and enabled the draft/publish feature. - -- **strapi generate:api <name> <attribute:type>**
- Generates an API called **<name>** in the `./api` folder at the root of your project. The model will already contain an attribute called **<attribute>** with the type property set to **<type>**. +## strapi templates:generate - Example: `strapi generate:api product name:string description:text price:integer` - -- **strapi generate:api <name> --plugin <plugin>**
- Generates an API called **<name>** in the `./plugins/` folder. - - Example: `strapi generate:api product --plugin content-manager` - -::: tip TIPS -* The filename will be kebab-cased. -* When you create a new API using the CLI, a model is automatically created. -::: - -## strapi generate:controller - -Create a new controller. +Create a template from the current strapi project ```bash -strapi generate:controller - -options: [--api |--plugin ] +strapi templates:generate ``` -- **strapi generate:controller <name>**
- Generates an empty controller called **<name>** in the `./api//controllers` folder. - - Example: `strapi generate:controller category` will create the controller at `./api/category/controllers/Category.js`. - -- **strapi generate:controller <name> --api <api>**
- Generates an empty controller called **<name>** in the `./api//controllers` folder. - - Example: `strapi generate:controller category --api product` will create the controller at `./api/product/controllers/Category.js`. - -- **strapi generate:controller <name> --plugin <plugin>**
- Generates an empty controller called **<name>** in the `./plugins//controllers` folder. +- **strapi templates:generate <path>**
+ Generates a Strapi template at `` -::: tip -The first letter of the filename will be uppercase. -::: + Example: `strapi templates:generate ../strapi-template-name` will copy the required files and folders to a `template` directory inside `../strapi-template-name` -## strapi generate:model +## strapi routes:list -Create a new model. +Display a list of all the available [routes](/developer-docs/latest/development/backend-customization/routes.md). -```bash -strapi generate:model [] - -options: [--api |--plugin |--draft-and-publish ] +```sh +strapi routes:list ``` -- **strapi generate:model <name>**
- Generates an empty model called **<name>** in the `./api//models` folder. It will create two files. - The first one will be **<name>.js** which contains your lifecycle callbacks and another **<name>.settings.json** that will list your attributes and options. - - Example: `strapi generate:model category` will create these two files `./api/category/models/Category.js` and `./api/category/models/Category.settings.json`. - -- **strapi generate:model <name> <attribute:type>**
- Generates an empty model called **<name>** in the `./api//models` folder. The file **<name>.settings.json** will already contain a list of attribute with their associated **<type>**. - - Example: `strapi generate:model category name:string description:text` will create these two files `./api/category/models/Category.js` and `./api/category/models/Category.settings.json`. This last file will contain two attributes `name` with the type `string` and `description` with type `text`. - -- **strapi generate:model <name> --api <api>**
- Generates an empty model called **<name>** in the `./api//models` folder. - - Example: `strapi generate:model category --api product` will create these two files: - - - `./api/product/models/Category.js` - - `./api/product/models/Category.settings.json`. +## strapi policies:list -* **strapi generate:model <name> --plugin <plugin>**
- Generates an empty model called **<name>** in the `./plugins//models` folder. +Display a list of all the registered [policies](/developer-docs/latest/development/backend-customization/policies.md). -* **strapi generate:model <name> --draft-and-publish=true**
- Generates an empty model called **<name>** in the `./plugins//models` folder with the draft/publish feature enabled - -::: tip -The first letter of the filename will be uppercase. -::: - -## strapi generate:service - -Create a new service. - -```bash -strapi generate:service - -options: [--api |--plugin ] +```sh +strapi policies:list ``` -- **strapi generate:service <name>**
- Generates an empty service called **<name>** in the `./api//services` folder. - - Example: `strapi generate:service category` will create the service at `./api/category/services/Category.js`. +## strapi middlewares:list -- **strapi generate:service <name> --api <api>**
- Generates an empty service called **<name>** in the `./api//services` folder. +Display a list of all the registered [middlewares](/developer-docs/latest/development/backend-customization/middlewares.md). - Example: `strapi generate:service category --api product` will create the service at `./api/product/services/Category.js`. - -- **strapi generate:service <name> --plugin <plugin>**
- Generates an empty service called **<name>** in the `./plugins//services` folder. - -::: tip -The first letter of the filename will be uppercase. -::: - -## strapi generate:policy - -Create a new policy. - -```bash -strapi generate:policy - -options: [--api |--plugin ] +```sh +strapi middlewares:list ``` -- **strapi generate:policy <name>**
- Generates an empty policy called **<name>** in the `./config/policies` folder. - - Example: `strapi generate:policy isAuthenticated` will create the policy at `./config/policies/isAuthenticated.js`. - -- **strapi generate:policy <name> --api <api>**
- Generates an empty policy called **<name>** in the `./api//config/policies` folder. This policy will be scoped and only accessible by the **<api>** routes. - - Example: `strapi generate:policy isAuthenticated --api product` will create the policy at `./api/product/config/policies/isAuthenticated.js`. +## strapi content-types:list -- **strapi generate:policy <name> --plugin <plugin>**
- Generates an empty policy called **<name>** in the `./plugins//config/policies` folder. This policy will be scoped and accessible only by the **<plugin>** routes. +Display a list of all the existing [content-types](/developer-docs/latest/development/backend-customization/models.md). -## strapi generate:plugin - -Create a new plugin skeleton. - -```bash -strapi generate:plugin +```sh +strapi content-types:list ``` -- **strapi generate:plugin <name>**
- Generates an empty plugin called **<name>** in the `./plugins` folder. + +## strapi hooks:list - Example: `strapi generate:plugin user` will create the plugin at `./plugins/user`. +Display a list of all the available hooks. -Please refer to the [plugins development](/developer-docs/latest/development/plugins-development.md) section to know more. - -## strapi generate:template - -Create a template from the current strapi project - -```bash -strapi generate:template +```sh +strapi hooks:list ``` -- **strapi generate:template <path>**
- Generates a Strapi template at `` +## strapi services:list - Example: `strapi generate:template ../strapi-template-name` will copy the required files and folders to a `template` directory inside `../strapi-template-name` +Display a list of all the registered [services](/developer-docs/latest/development/backend-customization/services.md). +```sh +strapi services:list +``` ## strapi install diff --git a/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/components-dynamic-zones.md b/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/components-dynamic-zones.md index fcd7b29e80..395137ed91 100644 --- a/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/components-dynamic-zones.md +++ b/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/components-dynamic-zones.md @@ -7,11 +7,11 @@ description: … # Entity Service API: Components and dynamic zones -The [Entity Service](/developer-docs/latest/developer-resources/database-apis-reference/entity-service-api.md) is the layer that handles [components](/developer-docs/latest/development/backend-customization/models.html#components-2) and [dynamic zones](/developer-docs/latest/development/backend-customization/models.html#dynamic-zones) logic. With the Entity Service API, components and dynamic zones can be [created](#creation) and [updated](#update) while creating or updating entries. +The [Entity Service](/developer-docs/latest/developer-resources/database-apis-reference/entity-service-api.md) is the layer that handles [components](/developer-docs/latest/development/backend-customization/models.md#components-2) and [dynamic zones](/developer-docs/latest/development/backend-customization/models.md#dynamic-zones) logic. With the Entity Service API, components and dynamic zones can be [created](#creation) and [updated](#update) while creating or updating entries. ## Creation -A [component](/developer-docs/latest/development/backend-customization/models.html#components-2) can be created while creating an entry with the Entity Service API: +A [component](/developer-docs/latest/development/backend-customization/models.md#components-2) can be created while creating an entry with the Entity Service API: ```js strapi.entityService.create('api::article.article', { @@ -23,7 +23,7 @@ strapi.entityService.create('api::article.article', { }); ``` -A [dynamic zone](/developer-docs/latest/development/backend-customization/models.html#dynamic-zones) (i.e. a list of components) can be created while creating an entry with the Entity Service API: +A [dynamic zone](/developer-docs/latest/development/backend-customization/models.md#dynamic-zones) (i.e. a list of components) can be created while creating an entry with the Entity Service API: ```js strapi.entityService.create('api::article.article', { @@ -44,7 +44,7 @@ strapi.entityService.create('api::article.article', { ## Update -A [component](/developer-docs/latest/development/backend-customization/models.html#components-2) can be updated while updating an entry with the Entity Service API. If a component `id` is specified, the component is updated, otherwise the old one is deleted and a new one is created: +A [component](/developer-docs/latest/development/backend-customization/models.md#components-2) can be updated while updating an entry with the Entity Service API. If a component `id` is specified, the component is updated, otherwise the old one is deleted and a new one is created: ```js strapi.entityService.update('api::article.article', 1, { @@ -57,7 +57,7 @@ strapi.entityService.update('api::article.article', 1, { }); ``` -A [dynamic zone](/developer-docs/latest/development/backend-customization/models.html#dynamic-zones) (i.e. a list of components) can be updated while updating an entry with the Entity Service API. If a component `id` is specified, the component is updated, otherwise the old one is deleted and a new one is created: +A [dynamic zone](/developer-docs/latest/development/backend-customization/models.md#dynamic-zones) (i.e. a list of components) can be updated while updating an entry with the Entity Service API. If a component `id` is specified, the component is updated, otherwise the old one is deleted and a new one is created: ```js strapi.entityService.update('api::article.article', 1, { diff --git a/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/crud.md b/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/crud.md index b85fab4968..756ab16699 100644 --- a/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/crud.md +++ b/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/crud.md @@ -20,7 +20,7 @@ Syntax: `findOne(uid: string, id: ID, parameters: Params)` β‡’ `Entry` | Parameter | Description | Type | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `fields` | Attributes to return | `String[]` | -| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | +| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | ### Example @@ -43,10 +43,10 @@ Syntax: `findMany(uid: string, parameters: Params)` β‡’ `Entry[]` | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `fields` | Attributes to return | `String[]` | | `filters` | [Filters](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/filter.md) to use | [`FiltersParameters`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/filter.md) | -| `start` | Number of entries to skip (see [pagination](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#pagination)) | `Number` | -| `limit` | Number of entries to return (see [pagination](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#pagination)) | `Number` | -| `sort` | [Order](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/ordering-pagination.md) definition | [`OrderByParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/ordering-pagination.md) | -| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | +| `start` | Number of entries to skip (see [pagination](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.md#pagination)) | `Number` | +| `limit` | Number of entries to return (see [pagination](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.md#pagination)) | `Number` | +| `sort` | [Order](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.md) definition | [`OrderByParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.md) | +| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | | `publicationState` | Publication state, can be:
  • `live` to return only published entries (default)
  • `preview` to return both draft entries & published entries
| `PublicationStateParameter` | ### Example @@ -71,7 +71,7 @@ Syntax: `create(uid: string, parameters: Params)` β‡’ `Entry` | Parameter | Description | Type | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `fields` | Attributes to return | `String[]` | -| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | +| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | | `data` | Input data | `Object` | ### Example @@ -99,7 +99,7 @@ Syntax: `update(uid: string, id: ID, parameters: Params)` β‡’ `Entry` | Parameter | Description | Type | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `fields` | Attributes to return | `String[]` | -| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | +| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | | `data` | Input data | `object` | ### Example @@ -123,7 +123,7 @@ Syntax: `delete(uid: string, id: ID, parameters: Params)` β‡’ `Entry` | Parameter | Description | Type | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `fields` | Attributes to return | `String[]` | -| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populating.md) | +| `populate` | Relations, components and dynamic zones to [populate](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | [`PopulateParameter`](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.md) | ### Example diff --git a/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/filter.md b/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/filter.md index 5570c5d046..e46679103c 100644 --- a/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/filter.md +++ b/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/filter.md @@ -8,7 +8,7 @@ sidebarDepth: 3 # Entity Service API: Filtering -The [Entity Service API](/developer-docs/latest/developer-resources/database-apis-reference/entity-service-api.md) offers the ability to filter results found with its [findMany()](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/crud.html#findmany) method. +The [Entity Service API](/developer-docs/latest/developer-resources/database-apis-reference/entity-service-api.md) offers the ability to filter results found with its [findMany()](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/crud.md#findmany) method. Results are filtered with the `filters` parameter that accepts [logical operators](#logical-operators) and [attribute operators](#attribute-operators). Every operator should be prefixed with `$`. diff --git a/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.md b/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.md index f2578edc6e..325f35d0e7 100644 --- a/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.md +++ b/docs/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.md @@ -6,7 +6,7 @@ description: (add description here) # Entity Service API: Ordering & Pagination -The [Entity Service API](/developer-docs/latest/developer-resources/database-apis-reference/entity-service-api.md) offers the ability to [order](#ordering) and [paginate](#pagination) results found with its [findMany()](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/crud.html#findmany) method. +The [Entity Service API](/developer-docs/latest/developer-resources/database-apis-reference/entity-service-api.md) offers the ability to [order](#ordering) and [paginate](#pagination) results found with its [findMany()](/developer-docs/latest/developer-resources/database-apis-reference/entity-service/crud.md#findmany) method. ## Ordering diff --git a/docs/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.md b/docs/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.md index f1c476ff6f..d2f17c7ab4 100644 --- a/docs/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.md +++ b/docs/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.md @@ -20,6 +20,8 @@ Responses are unified with the GraphQL API in that: - queries and mutations that return information for a single entry mainly use a `XxxEntityResponse` type - queries and mutations that return i️nformation for multiple entries mainly use a `XxxEntityResponseCollection` type, which includes `meta` information (with [pagination](#pagination)) in addition to the data itself +Responses can also include an `error` (see [error handling documentation](/developer-docs/latest/developer-resources/error-handling.md)). + ::: details Example: Response formats for queries and mutations with an example 'Article' content-type ```graphql diff --git a/docs/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.md b/docs/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.md index 3eec1dfc24..9acb5e85b8 100644 --- a/docs/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.md +++ b/docs/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.md @@ -6,7 +6,7 @@ sidebarDepth: 3 # Query Engine API: Filtering -The [Query Engine API](/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.md) offers the ability to filter results found with its [findMany()](/developer-docs/latest/developer-resources/database-apis-reference/query-engine/single-operations.html#findmany) method. +The [Query Engine API](/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.md) offers the ability to filter results found with its [findMany()](/developer-docs/latest/developer-resources/database-apis-reference/query-engine/single-operations.md#findmany) method. Results are filtered with the `filters` parameter that accepts [logical operators](#logical-operators) and [attribute operators](#attribute-operators). Every operator should be prefixed with `$`. diff --git a/docs/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md b/docs/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md index bb5794d374..77a574f098 100644 --- a/docs/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md +++ b/docs/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md @@ -145,7 +145,7 @@ Whatever the query, the response is always an object with the following keys: - `meta`(object): information about pagination, publication state, available locales, etc. -- `error` (object, _optional_): information about any error thrown by the request +- `error` (object, _optional_): information about any [error](/developer-docs/latest/developer-resources/error-handling.md) thrown by the request ### Get entries diff --git a/docs/developer-docs/latest/developer-resources/error-handling.md b/docs/developer-docs/latest/developer-resources/error-handling.md new file mode 100644 index 0000000000..d62416a84d --- /dev/null +++ b/docs/developer-docs/latest/developer-resources/error-handling.md @@ -0,0 +1,94 @@ +--- +title: Error handling - Strapi Developer Documentation +description: … +--- + + + +# Error handling + +Strapi is natively handling errors with a standard format. + +There are 2 use cases for error handling: + +- As a developer querying content through the [REST](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md) or [GraphQL](/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.md) APIs, you might [receive errors](#receiving-errors) in response to the requests. +- As a developer customizing the backend of your Strapi application, you could use controllers and services to [throw errors](#throwing-errors). + +## Receiving errors + +Errors are included in the response object with the `error` key and include information such as the HTTP status code, the name of the error, and additional information. + +### REST errors + +Errors thrown by the REST API are included in the [response](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#unified-response-format) that has the following format: + +```json +{ + "data": null, + "error": { + "status": "", // HTTP status + "name": "", // Strapi error name ('ApplicationError' or 'ValidationError') + "message": "", // A human reable error message + "details": { + // error info specific to the error type + } + } +} +``` + + + +### GraphQL errors + +Errors thrown by the GraphQL API are included in the [response](/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.md#unified-response-format) that has the following format: + +```json +{ "errors": [ + { + "message": "", // A human reable error message + "extensions": { + "error": { + "name": "", // Strapi error name ('ApplicationError' or 'ValidationError'), + "message": "", // A human reable error message (same one as above); + "details": {}, // Error info specific to the error type + }, + "code": "" // GraphQL error code (ex: BAD_USER_INPUT) + } + } + ], + "data": { + "graphQLQueryName": null + } +} +``` + +## Throwing errors + +The recommended way to throw errors when developing any custom logic with Strapi is to have the [controller](/developer-docs/latest/development/backend-customization/controllers.md) respond with the correct status and body. + +This can be done by calling an error function on the context (i.e. `ctx`). Available error functions are listed in the [http-errors documentation](https://github.com/jshttp/http-errors#list-of-all-constructors) but their name should be camel-cased to be used by Strapi (e.g. `badRequest`). + +Error functions accept 2 parameters that correspond to the `error.message` and `error.details` attributes [received](#receiving-errors) by a developer querying the API: + +- the first parameter of the function is the error `message` +- and the second one is the object that will be set as `details` in the response received + +```js + +// path: ./src/api/[api-name]/controllers/my-controller.js + +module.exports = { + renameDog: async (ctx, next) => { + const newName = ctx.request.body.name; + if (!newName) { + return ctx.badRequest('name is missing', { foo: 'bar' }) + } + ctx.body = strapi.service('api::dog.dog').rename(newName); + } +} + +``` + +:::note +[Services](/developer-docs/latest/development/backend-customization/services.md) don't have access to the controller's `ctx` object. If services need to throw errors, these need to be caught by the controller, that in turn is in charge of calling the proper error function. +::: diff --git a/docs/developer-docs/latest/developer-resources/plugin-api-reference/server.md b/docs/developer-docs/latest/developer-resources/plugin-api-reference/server.md index 15d50e1e51..e18a3afbed 100644 --- a/docs/developer-docs/latest/developer-resources/plugin-api-reference/server.md +++ b/docs/developer-docs/latest/developer-resources/plugin-api-reference/server.md @@ -100,7 +100,7 @@ module.exports = () => ({ ### destroy() -This function is called to cleanup the plugin (close connections, remove listeners…) when the Strapi instance is destroyed. +The [destroy](/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md#destroy) lifecycle function is called to cleanup the plugin (close connections, remove listeners…) when the Strapi instance is destroyed. **Type**: `Function` @@ -220,10 +220,7 @@ module.exports = { ### Routes - - - -An array of [routes](/developer-docs/latest/development/backend-customization/routing.md) configuration. +An array of [routes](/developer-docs/latest/development/backend-customization/routes.md) configuration. **Type**: `Object[]` @@ -387,7 +384,7 @@ module.exports = (ctx, next) => { -An object with the [middlewares](/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md) the plugin provides. +An object with the [middlewares](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md) the plugin provides. **Type**: `Object` diff --git a/docs/developer-docs/latest/development/admin-customization.md b/docs/developer-docs/latest/development/admin-customization.md index 20066d7944..158d195101 100644 --- a/docs/developer-docs/latest/development/admin-customization.md +++ b/docs/developer-docs/latest/development/admin-customization.md @@ -39,14 +39,19 @@ To make the admin panel accessible from `http://localhost:1337/dashboard`, use t module.exports = ({ env }) => ({ host: env('HOST', '0.0.0.0'), port: env.int('PORT', 1337), - admin: { - url: '/dashboard', - }, + }); + + +// path: ./config/admin.js + +module.exports = ({ env }) => ({ + url: '/dashboard', +}) ``` -:::strapi Advanced server settings -For more advanced settings please see the [server configuration](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md) documentation. +:::strapi Advanced settings +For more advanced settings please see the [admin panel configuration](/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md) documentation. ::: #### Host and port @@ -54,16 +59,21 @@ For more advanced settings please see the [server configuration](/developer-docs By default, the front end development server runs on `localhost:8000` but this can be modified: ```js -//path: ./config/server.js +// path: ./config/server.js module.exports = ({ env }) => ({ host: env('HOST', '0.0.0.0'), port: env.int('PORT', 1337), - admin: { - host: 'my-host', // only used along with `strapi develop --watch-admin` command - port: 3000, // only used along with `strapi develop --watch-admin` command - }, }); + + +// path: ./config/admin.js + +module.exports = ({ env }) => ({ + host: 'my-host', // only used along with `strapi develop --watch-admin` command + port: 3000, // only used along with `strapi develop --watch-admin` command +}); + ``` ### Configuration options @@ -172,7 +182,7 @@ module.exports = { ::: note NOTES * The `en` locale cannot be removed from the build as it is both the fallback (i.e. if a translation is not found in a locale, the `en` will be used) and the default locale (i.e. used when a user opens the administration panel for the first time). -* The full list of available locales is accessible on [Strapi's Github repo](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-i18n/constants/iso-locales.json). +* The full list of available locales is accessible on [Strapi's Github repo](https://github.com/strapi/strapi/blob/releases/v4/packages/plugins/i18n/server/constants/iso-locales.json). ::: @@ -253,20 +263,17 @@ The template will be compiled with the following variables: `url`, `user.email`, **Example**: ```js -// path: ./config/servers.js +// path: ./config/admin.js const forgotPasswordTemplate = require('./email-templates/forgot-password'); module.exports = ({ env }) => ({ // ... - admin: { - // ... - forgotPassword: { - from: 'support@mywebsite.fr', - replyTo: 'support@mywebsite.fr', - emailTemplate: forgotPasswordTemplate, - }, - // ... + forgotPassword: { + from: 'support@mywebsite.fr', + replyTo: 'support@mywebsite.fr', + emailTemplate: forgotPasswordTemplate, + }, }, // ... }); @@ -376,10 +383,14 @@ module.exports = ({ env }) => ({ host: env('HOST', '0.0.0.0'), port: env.int('PORT', 1337), url: 'http://yourbackend.com', - admin: { - url: '/', // Note: The administration will be accessible from the root of the domain (ex: http://yourfrontend.com/) - serveAdminPanel: false, // http://yourbackend.com will not serve any static admin files - }, +}); + + +// path: ./config/admin.js + +module.exports = ({ env }) => ({ + url: '/', // Note: The administration will be accessible from the root of the domain (ex: http://yourfrontend.com/) + serveAdminPanel: false, // http://yourbackend.com will not serve any static admin files }); ``` diff --git a/docs/developer-docs/latest/development/backend-customization.md b/docs/developer-docs/latest/development/backend-customization.md index e69de29bb2..75d808bfc5 100644 --- a/docs/developer-docs/latest/development/backend-customization.md +++ b/docs/developer-docs/latest/development/backend-customization.md @@ -0,0 +1,36 @@ +--- +title: Back-end customization - Strapi Developer Documentation +description: +--- + + + +# Back-end customization + +Strapi runs an HTTP server based on [Koa](https://koajs.com/), a back end JavaScript framework. + +:::strapi What is Koa? +If you are not familiar with the Koa back end framework, we highly recommend you to read the [Koa's documentation introduction](http://koajs.com/#introduction). +::: + +Each part of Strapi's back end can be customized: + +- the [requests](/developer-docs/latest/development/backend-customization/requests-responses.md#requests) received by the Strapi server, + +- the [routes](/developer-docs/latest/development/backend-customization/routes.md) that handle the requests and trigger the execution of their controller handlers, + + +- the [policies](/developer-docs/latest/development/backend-customization/policies.md) that can block access to a route, + +- the [middlewares](/developer-docs/latest/development/backend-customization/middlewares.md) that can control the request flow and the request before moving forward, + + +- the [controllers](/developer-docs/latest/development/backend-customization/controllers.md) that execute code once a route has been reached, + +- the [services](/developer-docs/latest/development/backend-customization/services.md) that are used to build custom logic reusable by controllers, + +- the [models](/developer-docs/latest/development/backend-customization/models.md) that are a representation of the content data structure, + +- the [responses](/developer-docs/latest/development/backend-customization/requests-responses.md#responses) sent to the application that sent the request, + +- and the [webhooks](/developer-docs/latest/development/backend-customization/webhooks.md) that are used to notify other applications of events that occured. diff --git a/docs/developer-docs/latest/development/backend-customization/controllers.md b/docs/developer-docs/latest/development/backend-customization/controllers.md index 30da0080c4..4de06c5248 100644 --- a/docs/developer-docs/latest/development/backend-customization/controllers.md +++ b/docs/developer-docs/latest/development/backend-customization/controllers.md @@ -1,355 +1,224 @@ --- -title: +title: Backend customization - Controllers - Strapi Developer Docs description: +sidebarDepth: 3 --- # Controllers -Controllers are JavaScript files which contain a set of methods called **actions** reached by the client according to the requested route. It means that every time a client requests the route, the action performs the business logic code and sends back the response. They represent the _C_ in the _MVC_ pattern. In most cases, the controllers will contain the bulk of a project's business logic. +Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested [route](/developer-docs/latest/development/backend-customization/routes.md). Whenever a client requests the route, the action performs the business logic code and sends back the [response](/developer-docs/latest/development/backend-customization/requests-responses.md#responses). Controllers represent the C in the model-view-controller (MVC) pattern. Just like [all the other parts of the Strapi backend](/developer-docs/latest/development/backend-customization.md), controllers can be customized. -```js -module.exports = { - // GET /hello - async index(ctx) { - return 'Hello World!'; - }, -}; -``` - -In this example, any time a web browser is pointed to the `/hello` URL on your app, the page will display the text: `Hello World!`. - -The controllers are defined in each `./api/**/controllers/` folder. Every JavaScript file put in these folders will be loaded as a controller. They are also available through the `strapi.controllers` and `strapi.api.**.controllers` global variables. - -## Core controllers - -When you create a new `Content Type` you will see a new empty controller has been created. This is because Strapi builds a generic controller for your models by default and allows you to override and extend it in the generated files. - -### Extending a Model Controller - -Here are the core methods (and their current implementation). -You can simply copy and paste this code in your own controller file to customize the methods. - -:::caution -In the following example we will assume your controller, service and model are named `restaurant`. -::: - -#### Utils +In most cases, the controllers will contain the bulk of a project's business logic. But as a controller's logic becomes more and more complicated, it's a good practice to use [services](/developer-docs/latest/development/backend-customization/services.md) to organize the code into re-usable parts. + -First require the utility functions +## Implementation -```js -const { parseMultipartData, sanitizeEntity } = require('strapi-utils'); -``` +Controllers can be [generated or added manually](#adding-a-new-controller), and the [core controllers examples](#extending-core-controllers) can help you get started creating custom ones. -- `parseMultipartData`: This function parses Strapi's formData format. -- `sanitizeEntity`: This function removes all private fields from the model and its relations. - -#### Collection Type - -:::: tabs card +### Adding a new controller -::: tab find +A new controller can be implemented: -##### `find` +- 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 `./src/api/[api-name]/controllers/` for API controllers (this location matters as controllers are auto-loaded by Strapi from there) + - or in a folder like `./src/plugins/[plugin-name]/controllers/` for , though they can be created elsewhere as long as the plugin interface is properly exported in the `strapi-server.js` file (see [Server API for Plugins documentation](/developer-docs/latest/developer-resources/plugin-api-reference/server.md)) ```js -const { sanitizeEntity } = require('strapi-utils'); +// path: ./src/api/[api-name]/controllers/my-controller.js module.exports = { - /** - * Retrieve records. - * - * @return {Array} - */ - - async find(ctx) { - let entities; - if (ctx.query._q) { - entities = await strapi.services.restaurant.search(ctx.query); - } else { - entities = await strapi.services.restaurant.find(ctx.query); + async exampleAction(ctx, next) { + try { + ctx.body = 'ok'; + } catch (err) { + ctx.body = err; } - - return entities.map(entity => sanitizeEntity(entity, { model: strapi.contentType('api::restaurant.restaurant') })); - }, + } }; ``` -::: +Each controller action can be an `async` or `sync` function. +Every action receives a context object (`ctx`) as the first parameter. `ctx` contains the [request context](/developer-docs/latest/development/backend-customization/requests-responses.md#requests) and the [response context](/developer-docs/latest/development/backend-customization/requests-responses.md#responses). + -::: tab findOne +::: details Example: GET /hello route calling a basic controller -##### `findOne` +A specific `GET /hello` [route](/developer-docs/latest/development/backend-customization/routes.md) is defined, which takes `hello.index` as a handler. Every time a `GET /hello` request is sent to the server, Strapi calls the `index` action in the `hello.js` controller, which returns `Hello World!`: + ```js -const { sanitizeEntity } = require('strapi-utils'); +// path: ./src/api/hello/routes/router.js module.exports = { - /** - * Retrieve a record. - * - * @return {Object} - */ + routes: [ + { + method: 'GET', + path: '/hello', + handler: 'hello.index', + } + ] +} - async findOne(ctx) { - const { id } = ctx.params; +// path: ./src/api/hello/controllers/hello.js - const entity = await strapi.services.restaurant.findOne({ id }); - return sanitizeEntity(entity, { model: strapi.contentType('api::restaurant.restaurant') }); +module.exports = { + async index(ctx, next) { // called by GET /hello + ctx.body = 'Hello World!'; // we could also send a JSON }, }; ``` ::: -::: tab count +::: note +When a new [content-type](/developer-docs/latest/development/backend-customization/models.md#content-types) is created, Strapi builds a generic controller with placeholder code, ready to be customized. +::: -##### `count` +### Extending core controllers -```js -module.exports = { - /** - * Count records. - * - * @return {Number} - */ - - count(ctx) { - if (ctx.query._q) { - return strapi.services.restaurant.countSearch(ctx.query); - } - return strapi.services.restaurant.count(ctx.query); - }, -}; -``` +Default controllers are created for each content-type. These default controllers are used to return responses to API requests (e.g. when the `GET /api/articles/3` is accessed, the `findOne` method of the default controller for the "Article" content-type is called). Default controllers can be customized to implement your own logic. The following code examples should help you get started. +:::caution +v4 controllers are currently being refactored. The code examples below will be updated soon to reflect these changes. ::: -::: tab create + + +::::: details Collection type examples + +:::: tabs card -##### `create` +::: tab find() ```js -const { parseMultipartData, sanitizeEntity } = require('strapi-utils'); +async find(ctx) { + const { query } = ctx; -module.exports = { - /** - * Create a record. - * - * @return {Object} - */ - - async create(ctx) { - let entity; - if (ctx.is('multipart')) { - const { data, files } = parseMultipartData(ctx); - entity = await strapi.services.restaurant.create(data, { files }); - } else { - entity = await strapi.services.restaurant.create(ctx.request.body); - } - return sanitizeEntity(entity, { model: strapi.contentType('api::restaurant.restaurant') }); - }, -}; + const { results, pagination } = await service.find(query); + + return transformResponse(sanitize(results), { pagination }); +} ``` ::: -::: tab update - -##### `update` +::: tab findOne() ```js -const { parseMultipartData, sanitizeEntity } = require('strapi-utils'); +async findOne(ctx) { + const { id } = ctx.params; + const { query } = ctx; -module.exports = { - /** - * Update a record. - * - * @return {Object} - */ - - async update(ctx) { - const { id } = ctx.params; - - let entity; - if (ctx.is('multipart')) { - const { data, files } = parseMultipartData(ctx); - entity = await strapi.services.restaurant.update({ id }, data, { - files, - }); - } else { - entity = await strapi.services.restaurant.update({ id }, ctx.request.body); - } + const entity = await service.findOne(id, query); - return sanitizeEntity(entity, { model: strapi.contentType('api::restaurant.restaurant') }); - }, -}; + return transformResponse(sanitize(entity)); +} ``` ::: -::: tab delete - -##### `delete` +::: tab create() ```js -const { sanitizeEntity } = require('strapi-utils'); +async create(ctx) { + const { query } = ctx.request; -module.exports = { - /** - * Delete a record. - * - * @return {Object} - */ + const { data, files } = parseBody(ctx); - async delete(ctx) { - const { id } = ctx.params; + const entity = await service.create({ ...query, data, files }); - const entity = await strapi.services.restaurant.delete({ id }); - return sanitizeEntity(entity, { model: strapi.contentType('api::restaurant.restaurant') }); - }, -}; + return transformResponse(sanitize(entity)); +} ``` ::: -:::: - -#### Single Type - -:::: tabs card - -::: tab find - -##### `find` +::: tab update() ```js -const { sanitizeEntity } = require('strapi-utils'); +async update(ctx) { + const { id } = ctx.params; + const { query } = ctx.request; + const { data, files } = parseBody(ctx); + const entity = await service.update(id, { ...query, data, files }); -module.exports = { - /** - * Retrieve the record. - * - * @return {Object} - */ - - async find(ctx) { - const entity = await strapi.services.homepage.find(); - return sanitizeEntity(entity, { model: strapi.models.homepage }); - }, -}; + return transformResponse(sanitize(entity)); +} ``` ::: -::: tab update - -##### `update` +::: tab delete() ```js -const { parseMultipartData, sanitizeEntity } = require('strapi-utils'); - -module.exports = { - /** - * Update the record. - * - * @return {Object} - */ - - async update(ctx) { - let entity; - if (ctx.is('multipart')) { - const { data, files } = parseMultipartData(ctx); - entity = await strapi.services.restaurant.createOrUpdate(data, { - files, - }); - } else { - entity = await strapi.services.restaurant.createOrUpdate(ctx.request.body); - } - - return sanitizeEntity(entity, { model: strapi.contentType('api::restaurant.restaurant') }); - }, -}; +async delete(ctx) { + const { id } = ctx.params; + const { query } = ctx; + const entity = await service.delete(id, query); + return transformResponse(sanitize(entity)); +} ``` ::: +:::: +::::: -::: tab delete +::::: details Single type examples +:::: tabs card -##### `delete` +::: tab find() ```js -const { sanitizeEntity } = require('strapi-utils'); +async find(ctx) { + const { query } = ctx; + const entity = await service.find(query); + return transformResponse(sanitize(entity)); +} -module.exports = { - /** - * Delete the record. - * - * @return {Object} - */ - - async delete(ctx) { - const entity = await strapi.services.restaurant.delete(); - return sanitizeEntity(entity, { model: strapi.contentType('api::restaurant.restaurant') }); - }, -}; ``` ::: -:::: - -## Custom controllers - -You can also create custom controllers to build your own business logic and API endpoints. - -There are two ways to create a controller: - -- Using the CLI `strapi generate:controller restaurant`.
Read the [CLI documentation](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-generate-controller) for more information. -- Manually create a JavaScript file in `./api/**/controllers`. +::: tab update() -### Adding Endpoints - -Each controller’s action can be an `async` or `sync` function. -Every action receives a `context` (`ctx`) object as first parameter containing the [request context](/developer-docs/latest/development/backend-customization/requests-responses.md#requests) and the [response context](/developer-docs/latest/development/backend-customization/requests-responses.md#responses). +```js +async update(ctx) { + const { query } = ctx.request; + const { data, files } = parseBody(ctx); + const entity = await service.createOrUpdate({ ...query, data, files }); -### Example + return transformResponse(sanitize(entity)); +} +``` -In this example, we are defining a specific route in `./api/hello/config/routes.json` that takes `hello.index` as handler. For more information on routing, please see the [Routing documentation](/developer-docs/latest/development/backend-customization/routing.md) +::: -It means that every time a request `GET /hello` is sent to the server, Strapi will call the `index` action in the `hello.js` controller. -Our `index` action will return `Hello World!`. You can also return a JSON object. +::: tab delete() -**Path β€”** `./api/hello/config/routes.json`. +```js +async delete(ctx) { + const { query } = ctx; + const entity = await service.delete(query); -```json -{ - "routes": [ - { - "method": "GET", - "path": "/hello", - "handler": "hello.index", - "config": { - "policies": [] - } - } - ] + return transformResponse(sanitize(entity)); } ``` -**Path β€”** `./api/hello/controllers/hello.js`. +::: +:::: +::::: + +## Usage + +Controllers are declared and attached to a route. Controllers are automatically called when the route is called, so controllers usually do not need to be called explicitly. However, [services](/developer-docs/latest/development/backend-customization/services.md) can call controllers, and in this case the following syntax should be used: ```js -module.exports = { - // GET /hello - async index(ctx) { - ctx.send('Hello World!'); - }, -}; +// access an API controller +strapi.controller('api::api-name.controller-name'); +// access a plugin controller +strapi.controller('plugin::plugin-name.service-name'); ``` - -::: tip -A route handler can only access the controllers defined in the `./api/**/controllers` folders. -::: diff --git a/docs/developer-docs/latest/development/backend-customization/middlewares.md b/docs/developer-docs/latest/development/backend-customization/middlewares.md new file mode 100644 index 0000000000..aa65ca5ade --- /dev/null +++ b/docs/developer-docs/latest/development/backend-customization/middlewares.md @@ -0,0 +1,71 @@ +--- +title: Backend customization - Middlewares - Strapi Developer Documentation +description : … +--- + + + +# Middlewares customization + +::: strapi Different types of middlewares + +In Strapi, 2 middleware concepts coexist: + +- **Strapi middlewares** are [configured and enabled](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md) for the entire Strapi server application. These middlewares can be applied at the application level or at the API level.
The present documentation describes how to implement them.
Plugins can also add Strapi middlewares (see [Server API documentation](/developer-docs/latest/developer-resources/plugin-api-reference/server.md#middlewares)). + +- **Route middlewares** have a more limited scope and are configured and used as middlewares at the route level. They are described in the [routes documentation](/developer-docs/latest/development/backend-customization/routes.md#middlewares). + +::: + +## Implementation + +A new application-level or API-level middleware can be implemented: +- 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/middlewares/` for application-level middlewares +- `./src/api/[api-name]/middlewares/` for API-level middlewares +- `./src/plugins/[plugin-name]/middlewares/` for [plugin middlewares](/developer-docs/latest/developer-resources/plugin-api-reference/server.md#middlewares) + +Middlewares working with the REST API are functions like the following: + +```js +// path: ./src/middlewares/my-middleware.js or ./src/api/[api-name]/middlewares/my-middleware.js +module.exports = (config, { strapi })=> { + return (context, next) => {}; +}; +``` + +Once created, custom middlewares should be added to the [middlewares configuration file](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md#loading-order) or Strapi won't load them. + + +::: details Example of a custom timer middleware + +```js +module.exports = () => { + return async (ctx, next) => { + const start = Date.now(); + + await next(); + + const delta = Math.ceil(Date.now() - start); + ctx.set('X-Response-Time', delta + 'ms'); + }; +}; +``` + +::: + +The GraphQL plugin also allows [implementing custom middlewares](/developer-docs/latest/plugins/graphql.md#middlewares), with a different syntax. + +## Usage + +Middlewares are called different ways depending on their scope: + +- use `global::middleware-name` for application-level middlewares +- use `api::api-name.middleware-name` for API-level middlewares +- use `pluigin:plugin-name.middleware-name` for plugin middlewares + +::: tip +To list all the registered middlewares, run `yarn strapi middlewares:list`. +::: diff --git a/docs/developer-docs/latest/development/backend-customization/models.md b/docs/developer-docs/latest/development/backend-customization/models.md index 832add5f7d..c782cab04c 100644 --- a/docs/developer-docs/latest/development/backend-customization/models.md +++ b/docs/developer-docs/latest/development/backend-customization/models.md @@ -26,7 +26,7 @@ Content-types and components models are created and stored differently. Content-types in Strapi can be created: - with the [Content-Types Builder in the admin panel](/user-docs/latest/content-types-builder/introduction-to-content-types-builder.md), -- or with [Strapi's CLI `generate:model`](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-generate-model) command. +- or with [Strapi's interactive CLI `strapi generate`](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-generate) command. Creating a content-type with either method generates 2 files: diff --git a/docs/developer-docs/latest/development/backend-customization/policies.md b/docs/developer-docs/latest/development/backend-customization/policies.md index 393474b662..c9918a2f29 100644 --- a/docs/developer-docs/latest/development/backend-customization/policies.md +++ b/docs/developer-docs/latest/development/backend-customization/policies.md @@ -1,164 +1,190 @@ --- -title: Backend customization policies - Strapi Developer Documentation -description: The backend of Strapi can be customized according to your needs, so you can create your own backend behavior. +title: Policies - Backend customization - Strapi Developer Documentation +description: … +sidebarDepth: 3 --- + # Policies -Policies are functions which have the ability to execute specific logic on each request before it reaches the controller's action. They are mostly used for securing business logic easily. -Each route of the project can be associated to an array of policies. For example, you can create a policy named `isAdmin`, which obviously checks that the request is sent by an admin user, and use it for critical routes. +Policies are functions that execute specific logic on each request before it reaches the [controller](/developer-docs/latest/development/backend-customization/controllers.md). They are mostly used for securing business logic. + + +Each [route](/developer-docs/latest/development/backend-customization/routes.md) of a Strapi project can be associated to an array of policies. For example, a policy named `is-admin` could check that the request is sent by an admin user, and restrict access to critical routes. + -The policies are defined in each `./api/**/config/policies/` folders and plugins. They are respectively exposed through `strapi.api.**.config.policies` and `strapi.plugins.**.config.policies`. The global policies are defined at `./config/policies/` and accessible via `strapi.config.policies`. +Policies can be global or scoped. [Global policies](#global-policies) can be associated to any route in the project. Scoped policies only apply to a specific [API](#api-policies) or [plugin](#plugin-policies). -## How to create a policy? +## Implementation -There are several ways to create a policy. +A new policy can be implemented: -- Using the CLI `strapi generate:policy is-authenticated`.
Read the [CLI documentation](/developer-docs/latest/developer-resources/cli/CLI.md) for more information. -- Manually create a JavaScript file named `is-authenticated.js` in `./config/policies/`. +- 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/policies/` for global policies + - `./src/api/[api-name]/policies/` for API policies + - `./src/plugins/[plugin-name]/policies/` for plugin policies -**Path β€”** `./config/policies/is-authenticated.js`. +
+Global policy implementation example: ```js -module.exports = async (ctx, next) => { - if (ctx.state.user) { - // Go to next policy or will reach the controller's action. - return await next(); +// path: ./src/policies/is-authenticated.js + +module.exports = (policyContext, config, { strapi }) => { + if (policyContext.state.user) { // if a session is open + // go to next policy or reach the controller's action + return true; } - ctx.unauthorized(`You're not logged in!`); + return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass }; ``` -In this example, we are verifying that a session is open. If it is the case, we call the `next()` method that will execute the next policy or controller's action. Otherwise, a 401 error is returned. +`policyContext` is a wrapper arround the [controller](/developer-docs/latest/development/backend-customization/controllers.md) context. It adds some logic that can be useful to implement a policy for both REST and GraphQL. + +
+ +Policies can be configured using a `config` object: + +```js +// path: .src/api/[api-name]/policies/my-policy.js + +module.exports = (policyContext, config, { strapi }) => { + if (policyContext.state.user.role.code === config.role) { // if user's role is the same as the one described in configuration + return true; + } + + return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass + } +}; +``` ## Usage -To apply policies to a route, you need to associate an array of policies to it. There are two kinds of policies: global and scoped. +To apply policies to a route, add them to its configuration object (see [routes documentation](/developer-docs/latest/development/backend-customization/routes.md#policies)). + +Policies are called different ways depending on their scope: + +- use `global::policy-name` for [global policies](#global-policies) +- use `api::api-name.policy-name` for [API policies](#API-policies) +- use `plugin::plugin-name.policy-name` for [plugin policies](#plugin-policies) + +::: tip +To list all the available policies, run `yarn strapi policies:list`. +::: ### Global policies -The global policies can be associated to any route in your project. + +Global policies can be associated to any route in a project. -**Path β€”** `./api/restaurant/routes.json`. +```js +// path: ./src/api/restaurant/routes/router.js -```json -{ - "routes": [ +module.exports = { + routes: [ { - "method": "GET", - "path": "/restaurants", - "handler": "Restaurant.find", - "config": { - "policies": ["global::is-authenticated"] + method: 'GET', + path: '/restaurants', + handler: 'Restaurant.find', + config: { + /** + Before executing the find action in the Restaurant.js controller, + we call the global 'is-authenticated' policy, + found at ./src/policies/is-authenticated.js. + */ + policies: ['global::is-authenticated'] } } ] } ``` -Before executing the `find` action in the `Restaurant.js` controller, the global policy `is-authenticated` located in `./config/policies/is-authenticated.js` will be called. +### Plugin policies -::: tip -You can put as much policy as you want in this array. However be careful about the performance impact. -::: - -### Plugins policies - -Plugins can add and expose policies into your app. For example, the plugin **Users & Permissions** comes with useful policies to ensure that the user is well authenticated or has the rights to perform an action. +[Plugins](/developer-docs/latest/plugins/plugins-intro.md) can add and expose policies to an application. For example, the [Users & Permissions plugin](/user-docs/latest/users-roles-permissions/introduction-to-users-roles-permissions.md) comes with policies to ensure that the user is authenticated or has the rights to perform an action: -**Path β€”** `./api/restaurant/config/routes.json`. +```js +// path: ./src/api/restaurant/routes/router.js -```json -{ - "routes": [ +module.exports = { + routes: [ { - "method": "GET", - "path": "/restaurants", - "handler": "Restaurant.find", - "config": { - "policies": ["plugins::users-permissions.isAuthenticated"] + method: 'GET', + path: '/restaurants', + handler: 'Restaurant.find', + config: { + /** + The `isAuthenticated` policy prodived with the `users-permissions` plugin + is executed before the `find` action in the `Restaurant.js` controller. + */ + policies: ['plugins::users-permissions.isAuthenticated'] } } ] } ``` -The policy `isAuthenticated` located in the `users-permissions` plugin will be executed before the `find` action in the `Restaurant.js` controller. +### API policies -#### API policies +API policies are associated to the routes defined in the API where they have been declared. -The API policies can be associated to the routes defined in the API where they have been declared. +```js -**Path β€”** `./api/restaurant/config/policies/isAdmin.js`. +// path: ./src/api/restaurant/policies/is-admin.js. -```js -module.exports = async (ctx, next) => { - if (ctx.state.user.role.name === 'Administrator') { +module.exports = async (policyContext, config, { strapi }) => { + if (policyContext.state.user.role.name === 'Administrator') { // Go to next policy or will reach the controller's action. - return await next(); + return true; } - ctx.unauthorized(`You're not allowed to perform this action!`); + return false; }; -``` -**Path β€”** `./api/restaurant/config/routes.json`. -```json -{ - "routes": [ +// path: ./src/api/restaurant/routes/router.js + +module.exports = { + routes: [ { - "method": "GET", - "path": "/restaurants", - "handler": "Restaurant.find", - "config": { - "policies": ["isAdmin"] + method: 'GET', + path: '/restaurants', + handler: 'Restaurant.find', + config: { + /** + The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js` + is executed before the `find` action in the `Restaurant.js` controller. + */ + policies: ['is-admin'] } } ] } -``` - -The policy `isAdmin` located in `./api/restaurant/config/policies/isAdmin.js` will be executed before the `find` action in the `Restaurant.js` controller. -#### Using a policy outside its api +``` -To use a policy in another api you can reference it with the following syntax: `{apiName}.{policyName}`. +To use a policy in another API, reference it with the following syntax: `api::[apiName].[policyName]`: -**Path β€”** `./api/category/config/routes.json`. +```js +// path: ./src/api/category/routes/router.js -```json -{ - "routes": [ +module.exports = { + routes: [ { - "method": "GET", - "path": "/categories", - "handler": "Category.find", - "config": { - "policies": ["restaurant.isAdmin"] + method: 'GET', + path: '/categories', + handler: 'Category.find', + config: { + /** + The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js` + is executed before the `find` action in the `Restaurant.js` controller. + */ + policies: ['api::restaurant.is-admin'] } } ] } ``` - -## Advanced usage - -As it's explained above, the policies are executed before the controller's action. It looks like an action that you can make `before` the controller's action. You can also execute a logic `after`. - -**Path β€”** `./config/policies/custom404.js`. - -```js -module.exports = async (ctx, next) => { - // Indicate to the server to go to - // the next policy or to the controller's action. - await next(); - - // The code below will be executed after the controller's action. - if (ctx.status === 404) { - ctx.body = 'We cannot find the resource.'; - } -}; -``` diff --git a/docs/developer-docs/latest/development/backend-customization/routes.md b/docs/developer-docs/latest/development/backend-customization/routes.md new file mode 100644 index 0000000000..5a2901237c --- /dev/null +++ b/docs/developer-docs/latest/development/backend-customization/routes.md @@ -0,0 +1,164 @@ +--- +title: Routes - Strapi Developer Documentation +description: … +sidebarDepth: 3 +--- + + + +# Routes + +Requests sent to Strapi on any URL are handled by routes. By default, Strapi generates routes for all the content-types (see [REST API documentation](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md)). Routes can be [added](#implementation) and configured: + +- with [policies](#policies), which are a way to block access to a route, +- and with [middlewares](#middlewares), which are a way to control and change the request flow and the request itself. + +Once a route exists, reaching it executes some code handled by a controller (see [controllers](/developer-docs/latest/development/backend-customization/controllers.md) documentation). + +## Implementation + +Implementing a new route consists in defining it in a router file within the `.src/api/[apiName]/routes` folder (see [project structure](/developer-docs/latest/setup-deployment-guides/file-structure.md)). + +A router file consists of an array of objects, each object being a route with the following parameters: + +| Parameter | Description | Type | +| -------------------------- | -------------------------------------------------------------------------------- | -------- | +| `method` | Method associated to the route (i.e. `GET`, `POST`, `PUT`, `DELETE` or `PATCH`) | `String` | +| `path` | Path to reach, starting with a forward-leading slash (e.g. `/articles`)| `String` | +| `handler` | Function to execute when the route is reached.
Should follow this syntax: `.` | `String` | +| `config`

_Optional_ | Configuration to handle [policies](policies), [middlewares](middlewares) and [public availability](#public-routes) for the route

| `Object` | + +Generic implementation example: + +To handle any `GET` request on the `/articles` path by calling the `actionName` function from the `controllerName` [controller](/developer-docs/latest/development/backend-customization/controllers.md), use the following code: + +```js +// path: ./src/api/[apiName]/routes/[routerName].js (e.g './src/api/blog/routes/articles.js') + +module.exports = { + routes: [ + { + method: 'GET', + path: '/articles', + handler: 'controllerName.actionName', + }, + ], +}; +``` + +
+ +The router used by Strapi allows the creation of dynamic routes, using parameters and regular expressions. These parameters will be exposed in the `ctx.params` object. For more details, please refer to the [PathToRegex](https://github.com/pillarjs/path-to-regexp) documentation. + +::: details Example of routes using URL parameters and regular expressions +```js +// path: ./src/api/[apiName]/routes/[routerName].js (e.g './src/api/blog/routes/articles.js') + +module.exports = { + routes: [ + { // Path defined with a URL parameter + method: 'GET', + path: '/restaurants/:category/:id', + handler: 'Restaurant.findOneByCategory', + }, + { // Path defined with a regular expression + method: 'GET', + path: '/restaurants/:region(\\d{2}|\\d{3})/:id', // Only match when the first parameter contains 2 or 3 digits. + handler: 'Restaurant.findOneByRegion', + } + ] +} +``` + +::: + +## Configuration + +The optional configuration for a route is defined in its `config` object, which can be used to handle [policies](#policies) and [middlewares](#middlewares) or to [make the route public](#public-routes). + +### Policies + +[Policies](/developer-docs/latest/development/backend-customization/policies.md) can be added to a route configuration: + +- by pointing to a policy registered in `./src/policies`, with or without passing a custom configuration +- or by declaring the policy implementation directly, as a function that takes `policyContext` to extend [Koa's context](https://koajs.com/#context) (`ctx`) and the `strapi` instance as arguments (see [policies documentation](/developer-docs/latest/development/backend-customization/routes.md)) + +```js +// path: ./src/api/[apiName]/routes/[routerName].js (e.g './src/api/blog/routes/articles.js') + +module.exports = { + routes: [ + { + method: 'GET', + path: '/articles', + handler: 'controllerName.actionName', + config: { + policies: [ + 'policy-name', // point to a registered policy + { name: 'policy-name', config: {} }, // point to a registered policy with some custom configuration + // pass a policy implementation directly + (policyContext, config, { strapi }) => { + return true; + }, + ], + }, + }, + ], +}; +``` + +### Middlewares + +[Middlewares](/developer-docs/latest/development/backend-customization/middlewares.md) can be added to a route configuration: + +- by pointing to a middleware registered in `./src/middlewares`, with or without passing a custom configuration +- or by declaring the middleware implementation directly, as a function that takes [Koa's context](https://koajs.com/#context) (`ctx`) and the `strapi` instance as arguments: + +```js +// path: ./src/api/[apiName]/routes/[routerName].js (e.g './src/api/blog/routes/articles.js') + +module.exports = { + routes: [ + { + method: 'GET', + path: '/articles', + handler: 'controllerName.actionName', + config: { + middlewares: [ + 'middleware-name', // point to a registered middleware + { name: 'middleware-name', config: {} }, // point to a registered middleware with some custom configuration + // pass a middleware implementation directly + (ctx, next) => { + return next(); + }, + ], + }, + }, + ], +}; +``` + +### Public routes + +By default, routes are protected by Strapi's authentication system, which is based on [API tokens](/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md#api-tokens) or the use of a plugin such as the [Users & Permissions plugin](/user-docs/latest/plugins/strapi-plugins.md#users-permissions-plugin). + +In some scenarios, it can be useful to have a route publicly available and control the access outside of the normal Strapi authentication system. This can be achieved by setting the `auth` configuration parameter of a route to `false`: + +```js +// path: ./src/api/[apiName]/routes/[routerName].js (e.g './src/api/blog/routes/articles.js') + +module.exports = { + routes: [ + { + method: 'GET', + path: '/articles', + handler: 'controllerName.actionName', + config: { + auth: false, + }, + }, + ], +}; +``` + +*** diff --git a/docs/developer-docs/latest/development/backend-customization/routing.md b/docs/developer-docs/latest/development/backend-customization/routing.md deleted file mode 100644 index da482a2841..0000000000 --- a/docs/developer-docs/latest/development/backend-customization/routing.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: -description: ---- - - - -# Routing - -`./api/**/config/routes.json` files define all available endpoints for the clients. - -By default, Strapi generates endpoints for all your Content Types. More information is in the [Content API](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#api-endpoints) documentation. - -## How to create a route? - -You have to edit the `routes.json` file in one of your APIs folders (`./api/**/config/routes.json`) and manually add a new route object into the `routes` array. - -**Path β€”** `./api/**/config/routes.json`. - -```json -{ - "routes": [ - { - "method": "GET", - "path": "/restaurants", - "handler": "Restaurant.find", - "config": { - "policies": [] - } - }, - { - "method": "PUT", - "path": "/restaurants/bulkUpdate", - "handler": "Restaurant.bulkUpdate", - "config": { - "policies": [] - } - }, - { - "method": "POST", - "path": "/restaurants/:id/reservation", - "handler": "Restaurant.reservation", - "config": { - "policies": ["is-authenticated", "has-credit-card"] - } - } - ] -} -``` - -- `method` (string): Method or array of methods to hit the route (e.g. `GET`, `POST`, `PUT`, `HEAD`, `DELETE`, `PATCH`). -- `path` (string): URL starting with `/` (e.g. `/restaurants`). -- `handler` (string): Action to execute when the route is hit following this syntax `.`. -- `config` - - `policies` (array): Array of policy names or paths ([see more](/developer-docs/latest/development/backend-customization/policies.md)) - -::: tip -You can exclude the entire `config` object if you do not want the route to be checked by the [Users & Permissions plugin](/developer-docs/latest/plugins/users-permissions.md). -::: - -## Dynamic parameters - -The router used by Strapi allows you to create dynamic routes where you can use parameters and simple regular expressions. These parameters will be exposed in the `ctx.params` object. For more details, please refer to the [PathToRegex](https://github.com/pillarjs/path-to-regexp) documentation. - -```json -{ - "routes": [ - { - "method": "GET", - "path": "/restaurants/:category/:id", - "handler": "Restaurant.findOneByCategory", - "config": { - "policies": [] - } - }, - { - "method": "GET", - "path": "/restaurants/:region(\\d{2}|\\d{3})/:id", // Only match when the first parameter contains 2 or 3 digits. - "handler": "Restaurant.findOneByRegion", - "config": { - "policies": [] - } - } - ] -} -``` - -Example: Route definition with URL params: - -```json -{ - "routes": [ - { - "method": "GET", - "path": "/restaurants/:id", - "handler": "Restaurant.findOne", - "config": { - "policies": [] - } - } - ] -} -``` - -Get the URL param in the controller - -```js -module.exports = { - findOne: async ctx => { - // const id = ctx.params.id; - const { id } = ctx.params; - return id; - }, -}; -``` 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`. + +::: diff --git a/docs/developer-docs/latest/development/plugins-development.md b/docs/developer-docs/latest/development/plugins-development.md index f05966bba0..78c65529fd 100644 --- a/docs/developer-docs/latest/development/plugins-development.md +++ b/docs/developer-docs/latest/development/plugins-development.md @@ -17,8 +17,9 @@ To create a plugin, use Strapi CLI tools: 1. (_Optional_) If you don't already have an existing project, create a new development project with `strapi new myDevelopmentProject`. 2. Start the project with `cd myDevelopmentProject && strapi develop`. -3. In a new terminal window, [create a new plugin](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-generate-plugin) with `cd /path/to/myDevelopmentProject && strapi generate:plugin my-plugin` -4. Enable the plugin by adding it to the [plugins configurations](/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md) file: +3. In a new terminal window, run `cd /path/to/myDevelopmentProject && strapi generate` to launch the interactive `strapi generate` CLI. +4. Choose "plugin" from the list, press Enter, and give the plugin a name. +5. Enable the plugin by adding it to the [plugins configurations](/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md) file: ```js // path: /path/to/myDevelopmentProject/config/plugins.js diff --git a/docs/developer-docs/latest/guides/api-token.md b/docs/developer-docs/latest/guides/api-token.md deleted file mode 100644 index d3948d9706..0000000000 --- a/docs/developer-docs/latest/guides/api-token.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: API Tokens - Strapi Developer Documentation -description: Learn in this guide how to create an API token system in your Strapi project to execute request as an authenticated user. ---- - -# API tokens - -In this guide we will see how you can create an API token system to execute request as an authenticated user. - -This feature is in our [roadmap](https://portal.productboard.com/strapi/1-public-roadmap/c/40-api-access-token-with-permissions). -This guide is a workaround to achieve this feature before we support it natively in strapi. - -## Introduction - -The goal is to be able to request API endpoints with a query parameter `token` that authenticates as a user. `eg. /restaurants?token=my-secret-token`. - -To achieve this feature in development, we will have to customize the `users-permissions` plugin. This guide will help you understand how to customize all your applications. You can read more about [Strapi plugins and customization](/developer-docs/latest/development/plugins-extension.md). - -## Create the Token Content Type - -To manage your tokens, you will have to create a new Content Type named `token`. - -- `string` attribute named `token` -- `relation` attribute **Token** (`user`) - **Token** has and belongs to one **User** - **User** (`token`) - -Then add some users and create some token linked to these users. - -## Setup the file to override - -We now have to customize the function that verifies the `token` token. Strapi has an Authentication process that uses `JWT` tokens, we will reuse this function to customize the verification. - -[Here is the function](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/config/policies/permissions.js) that manages the JWT validation. - -To be able to customize it, you will have to create a new file in your application `./extensions/users-permissions/config/policies/permissions.js`. - -Then copy the original function that is on GitHub and paste it in your new file. - -When it's done, the Strapi application will use this function instead of the core one. We are ready to customize it. - -## Add token validation logic - -You will have to update the first lines of this function. - -**Path β€”** `./extensions/users-permissions/config/policies/permissions.js` - -```js -const _ = require('lodash'); - -module.exports = async (ctx, next) => { - let role; - - if (ctx.state.user) { - // request is already authenticated in a different way - return next(); - } - - // add the detection of `token` query parameter - if ( - (ctx.request && ctx.request.header && ctx.request.header.authorization) || - (ctx.request.query && ctx.request.query.token) - ) { - try { - // init `id` and `isAdmin` outside of validation blocks - let id; - let isAdmin; - - if (ctx.request.query && ctx.request.query.token) { - // find the token entry that match the token from the request - const [token] = await strapi.query('token').find({token: ctx.request.query.token}); - - if (!token) { - throw new Error(`Invalid token: This token doesn't exist`); - } else { - if (token.user && typeof token.token === 'string') { - id = token.user.id; - } - isAdmin = false; - } - - delete ctx.request.query.token; - } else if (ctx.request && ctx.request.header && ctx.request.header.authorization) { - // use the current system with JWT in the header - const decoded = await strapi.plugins[ - 'users-permissions' - ].services.jwt.getToken(ctx); - - id = decoded.id; - isAdmin = decoded.isAdmin || false; - } - - // this is the line that already exist in the code - if (id === undefined) { - throw new Error('Invalid token: Token did not contain required fields'); - } - - ... -``` - -And tada! You can now create a token, link it to a user and use it in your URLs with `token` as query parameters. diff --git a/docs/developer-docs/latest/guides/client.md b/docs/developer-docs/latest/guides/client.md index 1676b710c8..717a4bc07c 100644 --- a/docs/developer-docs/latest/guides/client.md +++ b/docs/developer-docs/latest/guides/client.md @@ -2,9 +2,12 @@ title: Client - Strapi Developer Documentation description: Learn in this guide how to setup a connection with a third party client and use it everywhere in your code. --- - # Setup a third party client +:::caution +This guide is outdated as hooks have been removed from Strapi v4. We recommend taking advantage of [plugins](/developer-docs/latest/development/plugins-development.md) instead. +::: + This guide will explain how to setup a connection with a third party client and use it everywhere in your code. In our example we will use the GitHub Node.JS client [OctoKit REST.js](https://github.com/octokit/rest.js/). @@ -33,7 +36,7 @@ npm install @octokit/rest ## Create a hook -To init the client, we will use the [hooks system](/developer-docs/latest/setup-deployment-guides/configurations/optional/hooks.md). Hooks let you add new features in your Strapi application. +To init the client, we will use the hooks system. Hooks let you add new features in your Strapi application. Hooks are loaded once at server start. diff --git a/docs/developer-docs/latest/guides/custom-data-response.md b/docs/developer-docs/latest/guides/custom-data-response.md index 58af529f9b..c8c78c8e4e 100644 --- a/docs/developer-docs/latest/guides/custom-data-response.md +++ b/docs/developer-docs/latest/guides/custom-data-response.md @@ -57,7 +57,7 @@ After saving the new function, let's restart the `GET /restaurants` request. We We now know the function we have to update, but we just want to customize the returned restaurant values. -In the [controller documentation](/developer-docs/latest/development/backend-customization/controllers.md#extending-a-model-controller) you will find the default implementation of every actions. It will help you overwrite the fetch logic. +In the [controller documentation](/developer-docs/latest/development/backend-customization/controllers.md#extending-core-controllers) you will find the default implementation of every actions. It will help you overwrite the fetch logic. **Path β€”** `./api/restaurant/controller/Restaurant.js` diff --git a/docs/developer-docs/latest/guides/draft.md b/docs/developer-docs/latest/guides/draft.md index 843776154c..5cff4a0e16 100644 --- a/docs/developer-docs/latest/guides/draft.md +++ b/docs/developer-docs/latest/guides/draft.md @@ -86,7 +86,7 @@ After saving the new function, let's restart the `GET /articles` request. We wil We now know the function we have to update, but we just want to customize the returned article values. -In the [controller documentation](/developer-docs/latest/development/backend-customization/controllers.md#extending-a-model-controller) you will find the default implementation of every action. It will help you overwrite the fetch logic. +In the [controller documentation](/developer-docs/latest/development/backend-customization/controllers.md#extending-core-controllers) you will find the default implementation of every action. It will help you overwrite the fetch logic. **Path β€”** `./api/article/controller/Article.js` diff --git a/docs/developer-docs/latest/guides/error-catching.md b/docs/developer-docs/latest/guides/error-catching.md index 90d3e59f10..02f9eaf689 100644 --- a/docs/developer-docs/latest/guides/error-catching.md +++ b/docs/developer-docs/latest/guides/error-catching.md @@ -5,6 +5,10 @@ description: Learn in this guide how you can catch errors and send them to the A # Error catching +:::caution +This guide is outdated and we recommend using [the official Sentry plugin for Strapi](https://www.npmjs.com/package/strapi-plugin-sentry) instead. +::: + In this guide we will see how you can catch errors and send them to the Application Monitoring / Error Tracking Software you want. ::: tip @@ -13,7 +17,7 @@ In this example we will use [Sentry](https://sentry.io). ## Create a middleware -A [middleware](/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md) will be used in order to catch the errors which will then be sent to Sentry. +A [middleware](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md) will be used in order to catch the errors which will then be sent to Sentry. - Create a `./middlewares/sentry/index.js` file. diff --git a/docs/developer-docs/latest/guides/external-data.md b/docs/developer-docs/latest/guides/external-data.md index 4fb1727427..661b64737e 100644 --- a/docs/developer-docs/latest/guides/external-data.md +++ b/docs/developer-docs/latest/guides/external-data.md @@ -84,7 +84,7 @@ With this code, everytime this function is called it will fetch the docker repo' Here is how to call the function in your application `strapi.config.functions.docker()` -So let's execute this function everyday at 2am. For this we will use a [CRON tasks](/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md#cron-tasks). +So let's execute this function everyday at 2am. For this we will use a [CRON tasks](/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md). **Path β€”** `./config/functions/cron.js` diff --git a/docs/developer-docs/latest/guides/is-owner.md b/docs/developer-docs/latest/guides/is-owner.md index 9af2f1160e..9d8fe9e0ef 100644 --- a/docs/developer-docs/latest/guides/is-owner.md +++ b/docs/developer-docs/latest/guides/is-owner.md @@ -32,7 +32,7 @@ When we are creating a new Article via `POST /articles` we will need to set the To do so we will customize the `create` controller function of the Article API. **Concepts we will use:** -Here is the code of [core controllers](/developer-docs/latest/development/backend-customization/controllers.md#core-controllers). +Here is the code of [core controllers](/developer-docs/latest/development/backend-customization/controllers.md#extending-core-controllers). We will also use this [documentation](/developer-docs/latest/plugins/users-permissions.md#user-object-in-strapi-context) to access the current authenticated user information. **Path β€”** `./api/article/controllers/Article.js` diff --git a/docs/developer-docs/latest/guides/scheduled-publication.md b/docs/developer-docs/latest/guides/scheduled-publication.md index 3962ec6898..6e82545240 100644 --- a/docs/developer-docs/latest/guides/scheduled-publication.md +++ b/docs/developer-docs/latest/guides/scheduled-publication.md @@ -29,7 +29,7 @@ The goal will be to check every minute if there are draft articles that have a ` To execute a function every minutes, we will use a CRON task. -Here is the [full documentation](/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md#cron-tasks) of this feature. If your CRON task requires to run based on a specific timezone then do look into the full documentation. +Here is the [full documentation](/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md) of this feature. If your CRON task requires to run based on a specific timezone then do look into the full documentation. **Path β€”** `./config/functions/cron.js` diff --git a/docs/developer-docs/latest/guides/send-email.md b/docs/developer-docs/latest/guides/send-email.md index d5b3b68930..580fda5370 100644 --- a/docs/developer-docs/latest/guides/send-email.md +++ b/docs/developer-docs/latest/guides/send-email.md @@ -58,7 +58,7 @@ After saving the new function, let's restart the `POST /comment` request. We wil We now know the function we have to update. Let's get back to the original function. -In the [controller documentation](/developer-docs/latest/development/backend-customization/controllers.md#extending-a-model-controller) you will find the default implementation of every action. It will help you overwrite the create logic. +In the [controller documentation](/developer-docs/latest/development/backend-customization/controllers.md#extending-core-controllers) you will find the default implementation of every action. It will help you overwrite the create logic. **Path β€”** `./api/comment/controller/Comment.js` diff --git a/docs/developer-docs/latest/guides/unit-testing.md b/docs/developer-docs/latest/guides/unit-testing.md index b3679a08be..ca10340bcd 100644 --- a/docs/developer-docs/latest/guides/unit-testing.md +++ b/docs/developer-docs/latest/guides/unit-testing.md @@ -192,7 +192,7 @@ If you receive a timeout error for Jest, please add the following line right bef ### Testing basic endpoint controller. ::: tip -In the example we'll use and example `Hello world` `/hello` endpoint from [controllers](/developer-docs/latest/development/backend-customization/controllers.md#example) section. +In the example we'll use and example `Hello world` `/hello` endpoint from [controllers](/developer-docs/latest/development/backend-customization/controllers.md) section. ::: diff --git a/docs/developer-docs/latest/plugins/graphql.md b/docs/developer-docs/latest/plugins/graphql.md index 60ec4e2412..2caa9e7c94 100644 --- a/docs/developer-docs/latest/plugins/graphql.md +++ b/docs/developer-docs/latest/plugins/graphql.md @@ -75,7 +75,7 @@ To simplify and automate the build of the GraphQL schema, we introduced the Shad **Example:** -If you've generated an API called `Restaurant` using the CLI `strapi generate:api restaurant` or the administration panel, your model looks like this: +If you've generated an API called `Restaurant` using [the interactive `strapi generate` CLI](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-generate) or the administration panel, your model looks like this: ```json // path: ./src/api/[api-name]/content-types/restaurant/schema.json @@ -186,75 +186,74 @@ Strapi provides a programmatic API to customize GraphQL, which allows: * disabling some operations for the [Shadow CRUD](#shadow-crud) * [using getters](#using-getters) to return information about allowed operations -* registering and using an `extension` object to extend the existing schema (e.g. extend types or define custom resolvers) +* registering and using an `extension` object to [extend the existing schema](#extending-the-schema) (e.g. extend types or define custom resolvers, policies and middlewares) -::: details Example of a full GraphQL customization file: +::: details Example of GraphQL customizations ```js -// path: /config/functions/register.js - -'use strict'; +// path: ./src/index.js ​ +module.exports = { /** * An asynchronous register function that runs before - * your application is loaded. + * your application is initialized. * * This gives you an opportunity to extend code. */ -​ -module.exports = ({ strapi }) => { - const extensionService = strapi.plugin('graphql').service('extension'); -​ - extensionService.shadowCRUD('api::restaurant.restaurant').disable(); - extensionService.shadowCRUD('api::category.category').disableQueries(); - extensionService.shadowCRUD('api::address.address').disableMutations(); - extensionService.shadowCRUD('api::document.document').field('locked').disable(); - extensionService.shadowCRUD('api::like.like').disableActions(['create', 'update', 'delete']); -​ - const extension = ({ nexus }) => ({ - // Nexus - types: [ - nexus.objectType({ - name: 'Book', - definition(t) { - t.string('title'); - }, - }), - ], - plugins: [ - nexus.plugin({ - name: 'MyPlugin', -​ - onAfterBuild(schema) { - console.log(schema); - }, - }), - ], -​ - // GraphQL SDL - typeDefs: ` - type Article { - name: String - } - `, - resolvers: { - Query: { - address: { - resolve() { - return { value: { city: 'Montpellier' } }; + register({ strapi }) => { + const extensionService = strapi.plugin('graphql').service('extension'); + ​ + extensionService.shadowCRUD('api::restaurant.restaurant').disable(); + extensionService.shadowCRUD('api::category.category').disableQueries(); + extensionService.shadowCRUD('api::address.address').disableMutations(); + extensionService.shadowCRUD('api::document.document').field('locked').disable(); + extensionService.shadowCRUD('api::like.like').disableActions(['create', 'update', 'delete']); + ​ + const extension = ({ nexus }) => ({ + // Nexus + types: [ + nexus.objectType({ + name: 'Book', + definition(t) { + t.string('title'); + }, + }), + ], + plugins: [ + nexus.plugin({ + name: 'MyPlugin', + ​ + onAfterBuild(schema) { + console.log(schema); + }, + }), + ], + ​ + // GraphQL SDL + typeDefs: ` + type Article { + name: String + } + `, + resolvers: { + Query: { + address: { + resolve() { + return { value: { city: 'Montpellier' } }; + }, }, }, }, - }, -​ - resolversConfig: { - 'Query.address': { - auth: false, + ​ + resolversConfig: { + 'Query.address': { + auth: false, + }, }, - }, - }); -​ - extensionService.use(extension); + }); + ​ + extensionService.use(extension); + }, }; ``` @@ -325,7 +324,6 @@ The following getters can be used to retrieve information about operations allow | `hasOutputEnabled()` | Returns whether a field has output enabled | | `hasFiltersEnabled()` | Returns whether a field has filtering enabled | - ### Extending the schema The schema generated by the Content API can be extended by registering an extension. @@ -341,33 +339,211 @@ The object describing the extension accepts the following parameters: | `typeDefs` | String | Allows extending the schema types using [GraphQL SDL](https://graphql.org/learn/schema/) | | `plugins` | Array | Allows extending the schema using Nexus [plugins](https://nexusjs.org/docs/plugins) | | `resolvers` | Object | Defines custom resolvers | -| `resolversConfig` | Object | Defines configuration options for the resolvers | +| `resolversConfig` | Object | Defines [configuration options for the resolvers](#custom-configuration-for-resolvers), such as [authorization](#authorization-configuration), [policies](#policies) and [middlewares](#middlewares) | ::::tip The `types` and `plugins` parameters are based on [Nexus](https://nexusjs.org/). To use them, register the extension as a function that takes `nexus` as a parameter: ::: details Example: + ```js -// path: /config/functions/register.js - -const extension = ({ nexus }) => ({ - types: [ - nexus.objectType({ - … - }), - ], - plugins: [ - nexus.plugin({ - … + +// path: ./src/index.js + +module.exports = { + register({ strapi }) { + const extension = ({ nexus }) => ({ + types: [ + nexus.objectType({ + … + }), + ], + plugins: [ + nexus.plugin({ + … + }) + ] }) - ] -}) -strapi.plugin('graphql').service('extension').use(extension) + strapi.plugin('graphql').service('extension').use(extension) + } +} ``` + ::: :::: +#### Custom configuration for resolvers + +A resolver is a GraphQL query or mutation handler (i.e. a function, or a collection of functions, that generate(s) a response for a GraphQL query or mutation). Each field has a default resolver. + +When [extending the GraphQL schema](#extending-the-schema), the `resolversConfig` key can be used to define a custom configuration for a resolver, which can include: + +* [authorization configuration](#authorization-configuration) with the `auth` key +* [policies with the `policies`](#policies) key +* and [middlewares with the `middlewares`](#middlewares) key + +##### Authorization configuration + +By default, the authorization of a GraphQL request is handled by the registered authorization strategy that can be either [API token](/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md#api-tokens) or through the [Users & Permissions plugin](#usage-with-the-users-permissions-plugin). The Users & Permissions plugin offers a more granular control. + +::: details Authorization with the Users & Permissions plugin +With the Users & Permissions plugin, a GraphQL request is allowed if the appropriate permissions are given. + +For instance, if a 'Category' content-type exists and is queried through GraphQL with the `Query.categories` handler, the request is allowed if the appropriate `find` permission for the 'Categories' content-type is given. + +To query a single category, which is done with the `Query.category` handler, the request is allowed if the the `findOne` permission is given. + +Please refer to the user guide on how to [define permissions with the Users & Permissions plugin](/user-docs/latest/users-roles-permissions/configuring-administrator-roles.md#editing-a-role). +::: + +To change how the authorization is configured, use the resolver configuration defined at `resolversConfig.[MyResolverName]`. The authorization can be configured: + +* either with `auth: false` to fully bypass the authorization system and allow all requests, +* or with a `scope` attribute that accepts an array of strings to define the permissions required to authorize the request. + +::: details Examples of authorization configuration + +```js + +// path: ./src/index.js + +module.exports = { + register({ strapi }) { + const extensionService = strapi.plugin('graphql').service('extension'); + + extensionService.use({ + resolversConfig: { + 'Query.categories': { + /** + * Querying the Categories content-type + * bypasses the authorization system. + */ + auth: false + }, + 'Query.restaurants': { + /** + * Querying the Restaurants content-type + * requires the find permission + * on the 'Address' content-type + * of the 'Address' API + */ + scope: ['api::address.address.find'] + }, + } + }) + } +} + +``` + +::: + +##### Policies + +[Policies](/developer-docs/latest/development/backend-customization/policies.md) can be applied to a GraphQL resolver through the `resolversConfig.[MyResolverName].policies` key. + +The `policies` key is an array accepting a list of policies, each item in this list being either a reference to an already registered policy or an implementation that is passed directly (see [policies configuration documentation](/developer-docs/latest/development/backend-customization/routes.md#policies)). + + +Policies directly implemented in `resolversConfig` are functions that take a `context` object and the `strapi` instance as arguments. +The `context` object gives access to: + +* the `parent`, `args`, `context` and `info` arguments of the GraphQL resolver, +* Koa's [context](https://koajs.com/#context) with `context.http` and [state](https://koajs.com/#ctx-state) with `context.state`. + +::: details Example of a custom GraphQL policy applied to a resolver + +```js + +// path: ./src/index.js + +module.exports = { + register({ strapi }) { + const extensionService = strapi.plugin('graphql').service('extension'); + + extensionService.use({ + resolversConfig: { + 'Query.categories': { + policies: [ + (context, { strapi }) => { + console.log('hello', context.parent) + /** + * If 'categories' have a parent, the function returns true, + * so the request won't be blocked by the policy. + */ + return context.parent !== undefined; + } + ], + auth: false, + }, + } + }) + } +} +``` + +::: + +#### Middlewares + +[Middlewares](/developer-docs/latest/development/backend-customization/middlewares.md) can be applied to a GraphQL resolver through the `resolversConfig.[MyResolverName].middlewares` key. + +The `middlewares` key is an array accepting a list of middlewares, each item in this list being either a reference to an already registered policy or an implementation that is passed directly (see [middlewares configuration documentation](/developer-docs/latest/development/backend-customization/routes.md#middlewares)). + + +Middlewares directly implemented in `resolversConfig` can take the GraphQL resolver's `parent`, `args`, `context` and `info` objects as arguments. + +:::tip +Middlewares with GraphQL can even act on nested resolvers, which offer a more granular control than with REST. +::: + +:::details Examples of custom GraphQL middlewares applied to a resolver + +```js + +// path: ./src/index.js + +module.exports = { + register({ strapi }) { + const extensionService = strapi.plugin('graphql').service('extension'); + + extensionService.use({ + resolversConfig: { + 'Query.categories': { + middlewares: [ + /** + * Basic middleware example #1 + * Log resolving time in console + */ + async (next, parent, args, context, info ) => { + console.time('Start resolving categories'); + console.timeEnd('Stop resolving categories'); + + return res; + }, + /** + * Basic middleware example #2 + * change the 'name' attribute of parent with id 1 to 'foobar' + */ + (resolve, parent, ...rest) => { + if (parent.id === 1) { + return resolve({...parent, name: 'foobar' }, ...rest); + } + + return resolve(parent, ...rest); + } + ], + auth: false, + }, + } + }) + } +} +``` + +::: + ## Usage with the Users & Permissions plugin The [Users & Permissions plugin](/developer-docs/latest/plugins/users-permissions.md) is an optional plugin that allows protecting the API with a full authentication process. diff --git a/docs/developer-docs/latest/plugins/i18n.md b/docs/developer-docs/latest/plugins/i18n.md index 963b58484b..2d09068e24 100644 --- a/docs/developer-docs/latest/plugins/i18n.md +++ b/docs/developer-docs/latest/plugins/i18n.md @@ -1,6 +1,7 @@ --- title: Internationalization (i18n) - Strapi Developer Documentation description: Instructions on how to use Strapi Content API with the Internationalization (i18n) optional plugin +sidebarDepth: 3 --- # 🌍 Internationalization (i18n) @@ -44,7 +45,9 @@ yarn strapi install i18n -## Usage with Strapi Content API +## Usage with the REST API + + The i18n plugin adds new features to the [REST API](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md): @@ -53,7 +56,7 @@ The i18n plugin adds new features to the [REST API](/developer-docs/latest/devel ### Getting localized entries with the `locale` parameter -The `locale` [API parameter](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#api-parameters) can be used to fetch entries only for a specified locale. It takes a locale code as value (see [full list of available locales](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-i18n/constants/iso-locales.json)). +The `locale` [API parameter](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#api-parameters) can be used to fetch entries only for a specified locale. It takes a locale code as value (see [full list of available locales](https://github.com/strapi/strapi/blob/master/packages/plugins/i18n/server/constants/iso-locales.json)). :::tip To fetch content for a locale, make sure it has been already [added to Strapi in the admin panel](/user-docs/latest/settings/managing-global-settings.md#configuring-internationalization-locales). @@ -62,58 +65,66 @@ The `locale` [API parameter](/developer-docs/latest/developer-resources/database The format for a GET request is the following: :::request -`GET /api/{content-type}?_locale={locale-code}` +`GET /api/{content-type}?locale={locale-code}` ::: -Use `all` as a value for the locale code, as in `http://localhost:1337/api/restaurants?_locale=all`, to fetch entries for all locales that have been configured in the admin panel. +Use `all` as a value for the locale code, as in `http://localhost:1337/api/restaurants?locale=all`, to fetch entries for all locales that have been configured in the admin panel. -If the `locale` parameter isn't defined, it will be set to the default locale. `en` is the default locale when i18n plugin is installed, so by default a GET request to `http://localhost:1337/api/restaurants` will return the same response as a request to `http://localhost:1337/api/restaurants?_locale=en`. +If the `locale` parameter isn't defined, it will be set to the default locale. `en` is the default locale when the i18n plugin is installed, so by default a GET request to `http://localhost:1337/api/restaurants` will return the same response as a request to `http://localhost:1337/api/restaurants?locale=en`. ::: tip Another locale can be [set as the default locale](/user-docs/latest/settings/managing-global-settings.md#adding-a-new-locale) in the admin panel. ::: -When the i18n plugin is installed, the response to requests includes fields that are specific to internationalization: +When the i18n plugin is installed, the response to requests can include fields that are specific to internationalization: + +- The `locale` (string) field is always included, it's the locale code for the content entry. -- `locale` (string) is the locale code for the content entry -- `localizations` (array) lists the existing localizations for this content entry; these localizations objects have 3 properties: - - `id` (number|string) is the id of the localized content entry - - `locale`(string) is the locale code for the localized content entry - - `published_at` (string) is the date and time when the localized content entry was [published](/developer-docs/latest/concepts/draft-and-publish.md#publishing-a-draft), in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format +- The `localizations` (object) can be included if specifically requested by appending `?populate=localizations` to the URL (see [relations population documentation](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md#relations-population). It includes a `data` array with a list of objects, each of them including the `id` and `attributes` of the localization. :::: api-call ::: request Example request -`GET http://localhost:1337/api/restaurants?_locale=fr` +`GET http://localhost:1337/api/restaurants?locale=fr` ::: ::: response Example Response ```json -[ - { - "id": 4, - "name": "Can Alegria", - "description": "description in French", - "locale": "fr", - "localizations": [ - { - "id": 3, - "locale": "en", - "published_at": "2021-04-07T10:10:31.949Z" +{ + "data": [ + { + "id": 4, + "attributes": { + "name": "Can Alegria", + "description": "description in French", + "locale": "fr", + "createdAt": "2021-10-28T15:24:42.129Z", + "publishedAt": "2021-10-28T15:24:42.129Z", } - ] - }, - { - "id": 8, - "name": "She's Cake", - "description": "description in French", - "locale": "fr", - "localizations": [] + }, + { + "id": 8, + "attributes": { + "name": "She's Cake", + "description": "description in French", + "locale": "fr", + "createdAt": "2021-10-28T16:17:42.129Z", + "publishedAt": "2021-10-28T16:18:24.126Z", + } + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 2, + "total": 2 + } } -] +} ``` ::: @@ -121,7 +132,7 @@ When the i18n plugin is installed, the response to requests includes fields that In the example response above: -- restaurant with `"id": 4` is a French (`"locale": "fr"`) localization of the existing restaurant with `"id": 3` (created for the default `en` locale), as shown in the `localizations` object included in the response (see [creating a localization for an existing entry](#creating-a-localization-for-an-existing-entry)). +- restaurant with `"id": 4` is a French (`"locale": "fr"`) localization of the existing restaurant with `"id": 3` (created for the default `en` locale). - restaurant with `"id": "8"` was created from scratch using the API, passing the `locale: fr` in the request body (see [creating a new localized entry](#creating-a-new-localized-entry)). ### Creating a new localized entry @@ -133,12 +144,14 @@ If no locale has been passed in the request body, the entry is created using the :::: api-call ::: request Example request -`POST http://localhost:1337/restaurants` +`POST http://localhost:1337/api/restaurants` ```json { - "name": "Oplato", - "description": "description in English" + "data": { + "name": "Oplato", + "description": "description in English" + } } ``` @@ -148,11 +161,18 @@ If no locale has been passed in the request body, the entry is created using the ```json { - "id": 5, - "name": "Oplato", - "description": "description in English", - "locale": "en", - "localizations": [] + "data": { + "id": 3, + "attributes": { + "name": "Oplato", + "description": "description in English", + "createdAt": "2021-10-28T16:57:26.352Z", + "updatedAt": "2021-10-28T16:57:26.352Z", + "publishedAt": "2021-10-28T16:57:26.345Z", + "locale": "en" + } + }, + "meta": {} } ``` @@ -164,13 +184,15 @@ To create a localized entry for a locale different from the default one, add the :::: api-call ::: request Example request -`POST http://localhost:1337/restaurants` +`POST http://localhost:1337/api/restaurants` ```json { - "name": "She's Cake", - "description": "description in French", - "locale": "fr" + "data": { + "name": "She's Cake", + "description": "description in French", + "locale": "fr" + } } ``` @@ -180,11 +202,18 @@ To create a localized entry for a locale different from the default one, add the ```json { + "data": { "id": 8, - "name": "She's Cake", - "description": "description in French", - "locale": "fr", - "localizations": [] + "attributes": { + "name": "She's Cake", + "description": "description in French", + "createdAt": "2021-10-28T17:01:47.089Z", + "updatedAt": "2021-10-28T17:01:47.089Z", + "publishedAt": "2021-10-28T17:01:47.082Z", + "locale": "en" + } + }, + "meta": {} } ``` @@ -195,15 +224,15 @@ To create a localized entry for a locale different from the default one, add the To create another localization for an existing localized entry, send a POST request to the appropriate URL depending on the type of content: -| Content-Type | Request URL format | -|:----------------|:-----------------------------------------| -| Collection type | `POST /{content-type}/:id/localizations` | -| Single type | `POST /{content-type}/localizations` | +| Content-Type | Request URL format | +| --------------- | -------------------------------------------- | +| Collection type | `POST /api/{content-type}/:id/localizations` | +| Single type | `POST /api/{content-type}/localizations` | When creating a localization for existing localized entries, the body of the POST request can only accept localized fields. ::: tip -The Content-Type should have the [`createlocalization` permission](/user-docs/latest/users-roles-permissions/configuring-administrator-roles.md#collection-and-single-types) enabled, otherwise the POST request will return a `403: Forbidden` status. +The Content-Type should have the [`createLocalization` permission](/user-docs/latest/users-roles-permissions/configuring-administrator-roles.md#collection-and-single-types) enabled, otherwise the POST request will return a `403: Forbidden` status. ::: #### Creating a localization for a collection type @@ -217,13 +246,12 @@ When sending a POST request to a collection type, Strapi will: ::: request Example request -`POST http://localhost:1337/restaurants/8/localizations` +`POST http://localhost:1337/api/restaurants/8/localizations` ```json { "locale": "en", "name": "She's Cake", - "test": 9, "description": "description in English" } ``` @@ -237,19 +265,27 @@ This request: ::: response Example response + ```json { - "id": 9, - "name": "She's Cake", - "description": "description in English", - "locale": "en", - "localizations": [ - { - "id": 8, - "locale": "fr", - "published_at": "2021-04-07T13:22:46.589Z" - } - ] + "id": 9, + "name": "She's Cake", + "description": "description in English", + "createdAt": "2021-10-29T08:39:29.709Z", + "updatedAt": "2021-10-29T08:39:29.709Z", + "publishedAt": null, + "locale": "en", + "localizations": [ + { + "id": 8, + "name": "She's Cake", + "description": "description in French", + "createdAt": "2021-10-28T16:57:26.352Z", + "updatedAt": "2021-10-29T08:38:04.106Z", + "publishedAt": "2021-10-28T16:57:26.345Z", + "locale": "fr" + } + ] } ``` @@ -257,19 +293,19 @@ This request: :::: - #### Creating a localization for a single type :::: api-call ::: request Example request -`POST http://localhost:1337/homepage/localizations` +`POST http://localhost:1337/api/homepage/localizations` ```json + { + "title": "Bienvenue sur FoodAdvisor !", "locale": "fr", - "title": "Bienvenue sur FoodAdvisor !" } ``` @@ -278,15 +314,17 @@ This request: ::: response Example response ```json + { "id": 2, "title": "Bienvenue sur FoodAdvisor!", "locale": "fr", + // ... "localizations": [ { - "id": 1, - "locale": "en", - "published_at": "2021-04-14T12:49:37.055Z" + "id": 1, + "locale": "en", + // ... } ] } @@ -298,7 +336,7 @@ This request: ### Updating an entry -Currently, it is not possible to change the locale of an existing localized entry. +It is not possible to change the locale of an existing localized entry. When updating a localized entry (with `PUT /{localized-content-type}/:id`), if you set a `locale` attribute in the request body, it will be ignored. @@ -328,12 +366,21 @@ To fetch entries for all locales, use `locale: "all"` in the query. ```graphql query { restaurants(locale: "en") { - id - name - locale - localizations { + data { id - locale + attributes { + name + locale + localizations { + data { + id + attributes { + name + description + } + } + } + } } } } @@ -346,36 +393,33 @@ query { ```json { "data": { - "restaurants": [ - { - "id": "3", - "name": "Can Alegria", - "locale": "en", - "localizations": [ - { - "id": "4", - "locale": "fr" - } - ] - }, - { - "id": "5", - "name": "Oplato", - "locale": "en", - "localizations": [] - }, - { - "id": "9", - "name": "She's Cake", - "locale": "en", - "localizations": [ - { - "id": "8", - "locale": "fr" + "restaurants": { + "data": [ + { + "id": "1", + "attributes": { + "name": "can alegria", + "locale": "en", + "localizations": { + "data": [ + { + "id": "2", + "attributes": { + "name": "can alegria (fr)", + "description": "description en franΓ§ais" + } + } + ] + } } - ] - } - ] + }, + { + "id": "2", + ... + }, + ... + ] + } } } ``` @@ -392,12 +436,11 @@ query { ```graphql query { homepage(locale: "en") { - id - title - locale - localizations { + data { id - locale + attributes { + title + } } } } @@ -411,15 +454,12 @@ query { { "data": { "homepage": { - "id": "1", - "title": "Welcome on FoodAdvisor!", - "locale": "en", - "localizations": [ - { - "id": "2", - "locale": "fr" + "data": { + "id": "1", + "attributes": { + "title": "Welcome on FoodAdvisor!", } - ] + } } } } @@ -442,20 +482,28 @@ The `locale` field can be passed in the `data` object of the mutation to create ```graphql mutation { createRestaurantLocalization( - input: { where: { id: 8 }, data: { - locale: "en", - name: "She's Cake", - description: "description in English" - }} + id: 8 + locale: "en" + data: { + name: "She's Cake" + description: "description in english" + } ) { - id - locale - name - description - localizations { + data { id - locale - description + attributes { + name + description + locale + localizations { + data { + attributes { + locale + description + } + } + } + } } } } @@ -483,6 +531,36 @@ mutation { } } } +{ + "data": { + "createRestaurantLocalization": { + "data": { + "id": "6", + "attributes": { + "name": "She's Cake", + "description": "description in English", + "locale": "fr", + "localizations": { + "data": [ + { + "attributes": { + "locale": "en", + "description": "description in English" + } + }, + { + "attributes": { + "locale": "fr", + "description": "description in French" + } + } + ] + } + } + } + } + } +} ``` @@ -499,20 +577,17 @@ mutation { ```graphql mutation { createHomepageLocalization( - input: { - data: { - locale: "fr" - title: "Bienvenue sur FoodAdvisor !" - } + locale: "fr" + data: { + title: "Bienvenue sur FoodAdvisor !" } ) { - id - locale - title - localizations { + data { id - locale - title + attributes { + locale + title + } } } } @@ -526,20 +601,19 @@ mutation { { "data": { "createHomepageLocalization": { - "id": "2", - "locale": "fr", - "title": "Bienvenue sur FoodAdvisor !", - "localizations": [ - { - "id": "1", - "locale": "en", - "title": "Welcome on FoodAdvisor!" + "data": { + "id": "2", + "attributes": { + "locale": "fr", + "title": "Bienvenue sur FoodAdvisor !" } - ] + } } } } + ``` + ::: :::: @@ -558,11 +632,13 @@ Currently, it is not possible to change the locale of an existing localized entr mutation { updateHomepage( locale: "fr" - input: { data: { title: "Bienvenue sur l'annuaire FoodAdvisor !" } } + data: { title: "Bienvenue sur l'annuaire FoodAdvisor !" } ) { - homepage { + data { id - title + attributes { + title + } } } } @@ -577,9 +653,11 @@ mutation { { "data": { "updateHomepage": { - "homepage": { - "id": "2", - "title": "Bienvenue sur l'annuaire FoodAdvisor !" + "data": { + "id": "1", + "attributes": { + "title": "Bienvenue sur l'annuaire FoodAdvisor !" + } } } } @@ -600,9 +678,11 @@ Pass the `locale` argument in the mutation to delete a specific localization for ```graphql mutation { deleteHomepage(locale: "fr") { - homepage { + data { id - title + attributes { + title + } } } } @@ -616,9 +696,11 @@ mutation { { "data": { "deleteHomepage": { - "homepage": { - "id": "2", - "title": "Bienvenue sur FoodAdvisor !" + "data": { + "id": "1", + "attributes": { + "title": "Bienvenue sur l'annuaire FoodAdvisor !" + } } } } @@ -633,6 +715,6 @@ The response returns the entry that has just been deleted. ## Configuration in production environments -A `STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE` [environment variable](http://localhost:8080/documentation/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md-variables) can be configured to set the initialization locale for your environment. The value used for this variable should be a string (see [full list of available locales](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-i18n/constants/iso-locales.json)). +A `STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE` [environment variable](http://localhost:8080/documentation/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md-variables) can be configured to set the initialization locale for your environment. The value used for this variable should be a string (see [full list of available locales](https://github.com/strapi/strapi/blob/releases/v4/packages/plugins/i18n/server/constants/iso-locales.json)). This is useful when a Strapi app is deployed in production, with the i18n plugin installed and enabled on your content types for the first time. On a fresh i18n plugin installation, `en` is the default locale. So if the database does not contain any locale, and no `STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE` is set for the environment, the content of the content types with i18n enabled will be automatically migrated to the `en` locale. But if the `STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE` is defined, then the content will be migrated to this locale. Using this environment variable saves you from having to manually update the locale for existing content entries. diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations.md b/docs/developer-docs/latest/setup-deployment-guides/configurations.md index bd02eeec91..fd15ef1487 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations.md @@ -54,15 +54,17 @@ When using a `.js` file, the configuration can be exported: Some parts of Strapi must be configured for the Strapi application to work properly: -- the [database](/developer-docs/latest/setup-deployment-guides/configurations/required/databases.md) -- and the [server](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md). +- the [database](/developer-docs/latest/setup-deployment-guides/configurations/required/databases.md), +- the [server](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md), +- the [admin panel](/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md), +- and the [middlewares](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md). ## Optional configurations Strapi also offers the following optional configuration options for specific features: -- [middlewares](/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md) - [functions](/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md) +- [cron jobs](/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md) - [API calls](/developer-docs/latest/setup-deployment-guides/configurations/optional/api.md) - [plugins](/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md) - the [environment and its variables](/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md) diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md new file mode 100644 index 0000000000..a1faa35573 --- /dev/null +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md @@ -0,0 +1,98 @@ +--- +title: Cron jobs - Strapi Developer Documentation +description: … +--- + + + +# Cron jobs + +:::prerequisites +The `cron.enabled` configuration option should be set to `true` in [the `./config/server.js` file](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md). +::: + +`cron` allows scheduling arbitrary functions for execution at specific dates, with optional recurrence rules. These functions are named cron jobs. `cron` only uses a single timer at any given time, rather than reevaluating upcoming jobs every second/minute. + +This feature is powered by the [`node-schedule`](https://www.npmjs.com/package/node-schedule) package. + +The `cron` format consists of: + +``` + +* * * * * * +┬ ┬ ┬ ┬ ┬ ┬ +β”‚ β”‚ β”‚ β”‚ β”‚ | +β”‚ β”‚ β”‚ β”‚ β”‚ β”” day of week (0 - 7) (0 or 7 is Sun) +β”‚ β”‚ β”‚ β”‚ └───── month (1 - 12) +β”‚ β”‚ β”‚ └────────── day of month (1 - 31) +β”‚ β”‚ └─────────────── hour (0 - 23) +β”‚ └──────────────────── minute (0 - 59) +└───────────────────────── second (0 - 59, OPTIONAL) + +``` + +To define cron jobs and have them run at the required times: + +1. [Create](#creating-a-cron-job) the appropriate file. +2. [Enable](#enabling-cron-jobs) the cron jobs in the server configuration file. + +::: tip +Optionally, cron jobs can be directly created in the `cron.tasks` key of the [server configuration file](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md). +::: + +## Creating a cron job + +To define a cron job, create a file with the following structure: + +```js +// path: ./config/cron-tasks.js + +module.exports = { + /** + * Simple example. + * Every monday at 1am. + */ + + '0 0 1 * * 1': ({ strapi }) => { + // Add your own logic here (e.g. send a queue of email, create a database backup, etc.). + }, +}; +``` + +If the cron job is required to run based on a specific timezone, configure it like the following: + +```js +module.exports = { + /** + * Cron job with timezone example. + * Every Monday at 1am for Asia/Dhaka timezone. + * List of valid timezones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List + */ + + '0 0 1 * * 1': { + task: ({ strapi }) => { /* Add your own logic here */ }, + options: { + tz: 'Asia/Dhaka', + }, + }, +}; +``` + +## Enabling cron jobs + +To enable cron jobs, set `cron.enabled` to `true` in the [server configuration file](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md) and declare the jobs: + +```js +// path: ./config/server.js + +const cronTasks = require("./cron-tasks"); + +module.exports = ({ env }) => ({ + host: env("HOST", "0.0.0.0"), + port: env.int("PORT", 1337), + cron: { + enabled: true, + tasks: cronTasks, + }, +}); +``` diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md index b764267130..3969b0ad6f 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md @@ -69,7 +69,8 @@ Some settings can only be modified through environment variables: | `NODE_ENV` | String |Β Type of environment where the app is running | `'development'` | | `BROWSER` | Boolean | Open the admin panel in the browser after startup | `true` | | `ENV_PATH` | String | Path to the file that contains your environment variables | `'./.env'` | -| `STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE` | String |Β _Optional_

Initialization locale for the app, if the [Internationalization (i18n) plugin](/developer-docs/latest/plugins/i18n.md) is installed and enabled on Content-Types (see [Configuration of i18n in production environments](/developer-docs/latest/plugins/i18n.md#configuration-in-production-environments)) | `'en'` | +| `STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE`

_Optional_| String |Β Initialization locale for the app, if the [Internationalization (i18n) plugin](/developer-docs/latest/plugins/i18n.md) is installed and enabled on Content-Types (see [Configuration of i18n in production environments](/developer-docs/latest/plugins/i18n.md#configuration-in-production-environments)) | `'en'` | +| `API_TOKEN_SALT`

_Optional_ | String | Salt to use to generate [API tokens](/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md#api-tokens) | - | ### Configuration using environment variables diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md index 73537b19c9..aa282cfd22 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md @@ -7,17 +7,18 @@ description: # Functions -The `./src/index.js` file contains some functions that can be used to add dynamic and logic based configurations. +The `./src/index.js` file includes global [register](#register), [bootstrap](#bootstrap) and [destroy](#destroy) functions that can be used to add dynamic and logic-based configurations. ## Register **Path β€”** `./src/index.js`. -The `register` function is an asynchronous function that runs before the application is initialized. +The `register` lifecycle function is an asynchronous function that runs before the application is initialized. It can be used to: - [extend plugins](/developer-docs/latest/development/plugins-extension.md#extending-a-plugin-s-interface) -- extend content-types programmatically +- extend [content-types](/developer-docs/latest/development/backend-customization/models.md) programmatically +- load some [environment variables](/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md). @@ -25,13 +26,12 @@ It can be used to: **Path β€”** `./src/index.js` -The `bootstrap` function is called at every server start. You can use it to add a specific logic at this moment of your server's lifecycle. +The `bootstrap` lifecycle function is called at every server start. -Here are some use cases: +It can be used to: -- Create an admin user if there isn't one. -- Fill the database with some necessary data. -- Load some environment variables. +- create an admin user if there isn't one. +- fill the database with some necessary data. The bootstrap function can be synchronous or asynchronous. @@ -59,64 +59,11 @@ module.exports = async () => { }; ``` -## CRON tasks - -CRON tasks allow you to schedule jobs (arbitrary functions) for execution at specific dates, with optional recurrence rules. It only uses a single timer at any given time (rather than reevaluating upcoming jobs every second/minute). - -This feature is powered by the [`node-schedule`](https://www.npmjs.com/package/node-schedule) package. +## Destroy -:::caution -Make sure the `enabled` cron config is set to `true` in `./config/server.js` file. -::: +The `destroy` function is an asynchronous function that runs before the application gets shut down. -The cron format consists of: - -``` -* * * * * * -┬ ┬ ┬ ┬ ┬ ┬ -β”‚ β”‚ β”‚ β”‚ β”‚ | -β”‚ β”‚ β”‚ β”‚ β”‚ β”” day of week (0 - 7) (0 or 7 is Sun) -β”‚ β”‚ β”‚ β”‚ └───── month (1 - 12) -β”‚ β”‚ β”‚ └────────── day of month (1 - 31) -β”‚ β”‚ └─────────────── hour (0 - 23) -β”‚ └──────────────────── minute (0 - 59) -└───────────────────────── second (0 - 59, OPTIONAL) -``` - -To define a CRON job, add your logic like below: - -```js -// path: ./config/functions/cron.js +It can be used to gracefully: -module.exports = { - /** - * Simple example. - * Every monday at 1am. - */ - - '0 0 1 * * 1': () => { - // Add your own logic here (e.g. send a queue of email, create a database backup, etc.). - }, -}; -``` - -If your CRON task is required to run based on a specific timezone then you can configure the task like below: - -```js -module.exports = { - /** - * CRON task with timezone example. - * Every monday at 1am for Asia/Dhaka timezone. - * List of valid timezones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List - */ - - '0 0 1 * * 1': { - task: () => { - // Add your own logic here (e.g. send a queue of email, create a database backup, etc.). - }, - options: { - tz: 'Asia/Dhaka', - }, - }, -}; -``` +- stop [services](/developer-docs/latest/development/backend-customization/services.md) that are running +- [clean up plugin actions](/developer-docs/latest/developer-resources/plugin-api-reference/server.md#destroy) (e.g. close connections, remove listeners, etc.). diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md deleted file mode 100644 index a1e6356dbf..0000000000 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md +++ /dev/null @@ -1,377 +0,0 @@ ---- -title: Middlewares - Strapi Developer Documentation -description: ---- - - - -# Middlewares - -Strapi middlewares are functions that are composed and executed in a stack-like manner upon request. They are based on [Koa](http://koajs.com/#introduction)'s middleware stack. - -## General usage - -Middleware files are functions that return an object. This object accepts an `initialize` function that is called during the server boot: - -```js -module.exports = strapi => { - return { - // can also be async - initialize() { - strapi.app.use(async (ctx, next) => { - // await someAsyncCode() - - await next(); - - // await someAsyncCode() - }); - }, - }; -}; -``` - -Once exported, middlewares are accessible through the [`strapi.middleware` global variable](/developer-docs/latest/developer-resources/global-strapi/api-reference.md#strapi-middleware). - -### Node modules - -Every folder that follows this name pattern `strapi-middleware-*` in the `./node_modules` folder will be loaded as a middleware. - -A middleware needs to follow the structure below: - -``` -/middleware -└─── lib - - index.js -- LICENSE.md -- package.json -- README.md -``` - -The `index.js` is the entry point to the middleware. It should look like the example above. - -### Custom middlewares - -The framework allows the application to override the default middlewares and [add new ones](#custom-middlewares-usage). To do so, create a `./middlewares` folder at the root of the project and put the middlewares into it. - -``` -/project -└─── api -└─── config -└─── middlewares -β”‚ └─── responseTime // It will override the core default responseTime middleware. -β”‚ - index.js -β”‚ └─── views // New custom middleware, will be added to the stack of middlewares. -β”‚ - index.js -└─── public -- favicon.ico -- package.json -- server.js -``` - -Every middleware will be injected into the Koa stack, and the [load order](#load-order) can be managed. - -## Configuration and activation - -To configure the middlewares of an application, create or edit the `./config/middleware.js` file. - -This configuration file can accept the following parameters: - -| Parameter | Type | Description | -| ---------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `timeout` | integer | Maximum allowed time (in milliseconds) to load a middleware | -| `load` | Object | [Load order](#load-order) | -| `settings` | Object | Configuration of each middleware

Accepts a list of middlewares with their options, with the format:
`middlewareName`: `{ option1: value, option2: value, … }` | - -::: details Example of settings definition: - -```js -// path: ./config/middleware.js - -module.exports = { - //... - settings: { - cors: { - origin: ['http://localhost', 'https://mysite.com', 'https://www.mysite.com'], - }, - }, -}; -``` - -::: - -### Load order - -The middlewares are injected into the Koa stack asynchronously. Sometimes it happens that some of these middlewares need to be loaded in a specific order. To define a load order, create or edit the `./config/middleware.js` file. - -The `load` key accepts 3 arrays, in which the order of items matters: - -| Parameter | Type | Description | -| --------- | ----- | ------------------------------------------- | -| `before` | Array | Middlewares to load first | -| `order` | Array | Middlewares to load in a specific order | -| `after` | Array | Middlewares to load at the end of the stack | - -::: details Example of load order definition: - -```js -// path : ./config/middleware.js - -module.exports = { - load: { - before: ['responseTime', 'logger', 'cors', 'responses'], - order: [ - "Define the middlewares' load order by putting their name in this array in the right order", - ], - after: ['parser', 'router'], - }, -}; -``` - -::: - -## Core middleware configurations reference - -The core of Strapi embraces a small list of middlewares for performances, security and error handling: - -- boom -- [cors](#cors-configuration) -- cron -- [csp](#csp-configuration) -- [favicon](#favicon-configuration) -- [gzip](#gzip-configuration) -- [hsts](#hsts-configuration) -- [ip](#ip-configuration) -- language -- [logger](#logger-configuration) -- [p3p](#p3p-configuration) -- [parser](#parser-configuration) -- [public](#public-configuration) -- responses -- responseTime -- router -- [session](#session-configuration) -- [xframe](#xframe-configuration) -- [xss](#xss-configuration) - -::: caution -The following middlewares cannot be disabled: `responses`, `router`, `logger` and `boom`. -::: - - -### Global middlewares - -#### favicon configuration - -| Parameter | Type | Description | Default value | -| --------- | ------- | ------------------------------------------------ | ------------- | -| `path` | String | Path to the favicon file | `favicon.ico` | -| `maxAge` | Integer | Cache-control max-age directive, in milliseconds | `86400000` | - -#### public configuration - -| Parameter | Type | Description | Default value | -| -------------- | ------- | --------------------------------------------------- | ------------- | -| `path` | String | Path to the public folder | `./public` | -| `maxAge` | Integer | Cache-control max-age directive, in milliseconds | `60000` | -| `defaultIndex` | Boolean | Display default index page at `/` and `/index.html` | `true` | - -### Request middlewares - -#### `session` configuration - -| Parameter | Type | Description | Default value | -| --------- | ------- | --------------- | ------------- | -| `enabled` | Boolean | Enable sessions | `false` | - -#### `logger` configuration - -| Parameter | Type | Description | Default value | -| --------- | ------- | -------------------- | ------------- | -| `enabled` | Boolean | Enable requests logs | `false` | - -To define a custom configuration for the `logger` middleware, create a dedicated configuration file (`./config/logger.js`). It should export an object that must be a complete or partial [winstonjs](https://github.com/winstonjs/winston) logger configuration. The object will be merged with Strapi's default logger configuration on server start. - -::: details Example: Custom configuration for the logger middleware - -```js -'use strict'; - -const { - winston, - formats: { prettyPrint, levelFilter }, -} = require('@strapi/logger'); - -module.exports = { - transports: [ - new winston.transports.Console({ - level: 'http', - format: winston.format.combine( - levelFilter('http'), - prettyPrint({ timestamps: 'YYYY-MM-DD hh:mm:ss.SSS' }) - ), - }), - ], -}; - -``` - -::: - -#### `parser` configuration - -| Parameter | Type | Description | Default value | -| --------- | ------- | --------------------------- | ------------- | -| `enabled` | Boolean | Enable requests logs | `false` | -| `multipart` | Boolean | Enable multipart bodies parsing | `true` | -| `jsonLimit` | String or Integer | The byte (if integer) limit of the JSON body | `1mb` | -| `formLimit` | String or Integer | The byte (if integer) limit of the form body | `56k` | -| `queryStringParser` | Object | QueryString parser options

Might contain the following keys (see [qs](https://github.com/ljharb/qs) for a full list of options):
  • `arrayLimit` (integer): the maximum length of an array in the query string. Any array members with an index of greater than the limit will instead be converted to an object with the index as the key. Default value: `100`
  • `depth` (integer): maximum parsing depth of nested query string objects. Default value: `20`
| - | - -::: tip -See [koa-body](https://github.com/dlau/koa-body#options) for more information. -::: - -### Response middlewares - -#### `gzip` configuration - -| Parameter | Type | Description | -| --------- | ------- | --------------------------------------------------------------------------------------- | -| `enabled` | Boolean | Enable GZIP response compression | -| `options` | Object | Allow passing of options from [koa-compress](https://github.com/koajs/compress#options) | - -::: tip -`gzip` compression via `koa-compress` uses [Brotli](https://en.wikipedia.org/wiki/Brotli) by default, but is not configured with sensible defaults for most cases. If you experience slow response times with `gzip` enabled, consider disabling Brotli by passing `{br: false}` as an option. You may also pass more sensible params with `{br: { params: { // YOUR PARAMS HERE } }}` -::: - -#### `responseTime` configuration - -| Parameter | Type | Description | Default value | -| --------- | ------- | ------------------------------------------- | ------------- | -| `enabled` | Boolean | Enable `X-Response-Time header` to response | `false` | - -#### `poweredBy` configuration - -| Parameter | Type | Description | Default value | -| --------- | ------- | ---------------------------------------- | -------------------- | -| `enabled` | Boolean | Enable `X-Powered-By` header to response | `true` | -| `value` | String | Value of the header | `Strapi ` | - -### Security middlewares - -#### `csp` configuration - -This security middleware is about [Content Security Policy (CSP)](https://en.wikipedia.org/wiki/Content_Security_Policy). - -| Parameter | Type | Description | Default value | -| --------- | ------- | ----------------------------------------------------------------------------------- | ------------- | -| `enabled` | Boolean | Enable to avoid Cross Site Scripting (XSS) and data injection attacks | | -| `policy` | String | Configure the `Content-Security-Policy` header. If not specified uses default value | `undefined` | - -#### `p3p` configuration - -This security middleware is about [Platform for Privacy Preferences (P3P)](https://en.wikipedia.org/wiki/P3P). - -| Parameter | Type | Description | Default value | -| --------- | ------- | ----------- | ------------- | -| `enabled` | Boolean | Enable p3p | | - -#### `hsts` configuration - -This security middleware is about [HTTP Strict Transport Security (HSTS)](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security). - -| Parameter | Type | Description | Default value | -| ------------------- | ------- | ------------------------------------------ | ------------- | -| `enabled` | Boolean | Enable HSTS. | - | -| `maxAge` | Integer | Number of seconds HSTS is in effect | `31536000` | -| `includeSubDomains` | Boolean | Applies HSTS to all subdomains of the host | `true` | -#### `xframe` configuration - -This security middleware is about [clickjacking](https://en.wikipedia.org/wiki/Clickjacking). - -| Parameter | Type | Description | Default value | -| --------- | ------- | ----------------------------------------------------------------- | ------------- | -| `enabled` | Boolean | Enable `X-FRAME-OPTIONS` headers in response. | | -| `value` | String | The value for the header, e.g. DENY, SAMEORIGIN or ALLOW-FROM uri | `SAMEORIGIN` | - -#### `xss` configuration - -This security middleware is about [cross-site scripting](https://en.wikipedia.org/wiki/Cross-site_scripting). - -| Parameter | Type | Description | Default value | -| --------- | ------- | ------------------------------------------------------------------------------------ | ------------- | -| `enabled` | Boolean | Enable XSS to prevent Cross Site Scripting (XSS) attacks in older IE browsers (IE8). | | - -#### `cors` configuration - -This security middleware is about [cross-origin resource sharing (CORS)](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing). - -| Parameter | Type | Description | Default value | -| ------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | -| `enabled` | Boolean | Enable CORS to prevent the server to be requested from another domain | | | -| `origin` | String or Array | Allowed URLs.

The value(s) can be:
  • strings (e.g. `http://example1.com, http://example2.com`)
  • an array of strings (e.g. `['http://www.example1.com', 'http://example1.com']`)
  • or `*` to allow all URLs
| `*` | -| `expose` | Array | Configure the `Access-Control-Expose-Headers` CORS header.

If not specified, no custom headers are exposed | `["WWW-Authenticate", "Server-Authorization"]`. | | -| `maxAge` | Integer | Configure the `Access-Control-Max-Age` CORS header | `31536000` | | -| `credentials` | Boolean | Configure the `Access-Control-Allow-Credentials` CORS header | `true` | | -| `methods` | Array or String | Configures the `Access-Control-Allow-Methods` CORS header | `["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]` | -| `headers` | Array | Configure the `Access-Control-Allow-Headers` CORS header

If not specified, defaults to reflecting the headers specified in the request's Access-Control-Request-Headers header | `["Content-Type", "Authorization", "X-Frame-Options"]` | | - -#### `ip` configuration - -| Parameter | Type | Description | Default value | -| ----------- | ------- | ----------------- | ------------- | -| `enabled` | Boolean | Enable IP blocker | `false` | -| `whiteList` | Array | Whitelisted IPs | `[]` | -| `blackList` | Array | Blacklisted IPs | `[]` | - -## Custom middlewares usage - -To add a custom middleware to the stack: - -1. Create a `./middlewares/your-middleware-name/index.js` file -2. Enable it and define the loading order, using the `settings` and `load` keys respectively, in the configuration object exported from the `./config/middlewares.js` file - - - -::: details Example: Create and set up a custom "timer" middleware: - -```js - -// path: ./middlewares/timer/index.js - -module.exports = strapi => { - return { - initialize() { - strapi.app.use(async (ctx, next) => { - const start = Date.now(); - - await next(); - - const delta = Math.ceil(Date.now() - start); - - ctx.set('X-Response-Time', delta + 'ms'); - }); - }, - }; -}; - -// path: ./config/middleware.js - -module.exports = { - load: { - before: ['timer', 'responseTime', 'logger', 'cors', 'responses', 'gzip'], - order: [ - "Define the middlewares' load order by putting their name in this array is the right order", - ], - after: ['parser', 'router'], - }, - settings: { - timer: { - enabled: true, - }, - }, -}; - -``` - -::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/sso.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/sso.md index 3fcb7c0da1..ad6895b0f4 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/sso.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/sso.md @@ -25,18 +25,17 @@ SSO configuration lives in the server configuration of the application, found at ## Accessing the configuration -The providers' configuration should be written within the `admin.auth.providers` path of the server configuration. +The providers' configuration should be written within the `auth.providers` path of the admin panel configuration. -`admin.auth.providers` is an array of [provider configuration](#setting-up-provider-configuration). +`auth.providers` is an array of [provider configuration](#setting-up-provider-configuration). ```javascript +// path: ./config/admin.js + module.exports = ({ env }) => ({ // ... - admin: { - // ... - auth: { - providers: [], // The providers' configuration lives there - }, + auth: { + providers: [], // The providers' configuration lives there }, }); ``` @@ -105,7 +104,7 @@ You can also use services such as Okta and Auth0 as bridge services. To configure a provider, follow the procedure below: 1. Make sure to import your strategy in your server configuration file, either from an installed package or a local file. -2. You'll need to add a new item to the `admin.auth.providers` array in your server configuration that will match the [format given above](#setting-up-provider-configuration) +2. You'll need to add a new item to the `auth.providers` array in your admin panel configuration that will match the [format given above](#setting-up-provider-configuration) 3. Restart your application, the provider should appear on your admin login page. ### Configuration providers examples @@ -448,8 +447,7 @@ module.exports = ({ env }) => ({ ### Admin panel URL If the administration panel lives on a host/port different from the Strapi server, the admin panel URL needs to be updated: -update the `admin.url` key in the `./config/server.js` configuration file (see [admin panel customization documentation](/developer-docs/latest/development/admin-customization.md#access-url)). - +update the `url` key in the `./config/admin.js` configuration file (see [admin panel customization documentation](/developer-docs/latest/development/admin-customization.md#access-url)). ### Custom Logic diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md new file mode 100644 index 0000000000..97a2ae38b7 --- /dev/null +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md @@ -0,0 +1,117 @@ +--- +title: Admin panel configuration - Strapi Developer Documentation +description: +--- + + + +# Admin panel configuration + +The `./config/admin.js` is used to define admin panel configuration for the Strapi application. This file should at least include configurations for authentication and [API tokens](#api-tokens). + +:::: tabs card +::: tab Minimal configuration + +## Minimal configuration + +The default configuration created with any new project should at least include the following: + +```js +// path: ./config/admin.js + +module.exports = ({ env }) => ({ + apiToken: { + salt: env('API_TOKEN_SALT', 'someRandomLongString'), + }, + auth: { + secret: env('ADMIN_JWT_SECRET', 'someSecretKey'), + }, +}) + +``` + +::: + +::: tab Full configuration + +## Full configuration + +```js +// path: ./config/admin.js + +module.exports = ({ env }) => ({ + apiToken: { + salt: env('API_TOKEN_SALT', 'someRandomLongString'), + }, + auth: { + events: { + onConnectionSuccess(e) { + console.log(e.user, e.provider); + }, + onConnectionError(e) { + console.error(e.error, e.provider); + }, + }, + secret: env('ADMIN_JWT_SECRET', 'someSecretKey'), + }, + url: env('PUBLIC_ADMIN_URL', '/dashboard'), + autoOpen: false, + watchIgnoreFiles: [ + './my-custom-folder', // Folder + './scripts/someScript.sh', // File + ], + host: 'localhost', // Only used for --watch-admin + port: 8003, // Only used for --watch-admin + serveAdminPanel: env.bool('SERVE_ADMIN', true), + forgotPassword: { + from: 'no-reply@example.com', + replyTo: 'no-reply@example.com', + }, +}) + +``` + +::: +:::: + +## Available options + + The `./config/admin.js` file can include the following parameters: + +| Parameter | Description | Type | Default | +| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `apiToken.salt` | Salt used to generate [API tokens](#api-tokens) | String | (A random string
generated
by Strapi) | +| `auth` | Authentication configuration | Object | - | +| `auth.secret` | Secret used to encode JWT tokens | string | `undefined` | +| `auth.events` | Record of all the events subscribers registered for the authentication | object | `{}` | +| `auth.events.onConnectionSuccess` | Function called when an admin user log in successfully to the administration panel | function | `undefined` | +| `auth.events.onConnectionError` | Function called when an admin user fails to log in to the administration panel | function | `undefined` | +| `url` | Url of your admin panel. Default value: `/admin`. Note: If the url is relative, it will be concatenated with `url`. | string | `/admin` | +| `autoOpen` | Enable or disabled administration opening on start. | boolean | `true` | +| `watchIgnoreFiles` | Add custom files that should not be watched during development. See more [here](https://github.com/paulmillr/chokidar#path-filtering) (property `ignored`). | Array(string) | `[]` | +| `host` | Use a different host for the admin panel. Only used along with `strapi develop --watch-admin` | string | `localhost` | +| `port` | Use a different port for the admin panel. Only used along with `strapi develop --watch-admin` | string | `8000` | +| `serveAdminPanel` | If false, the admin panel won't be served. Note: the `index.html` will still be served, see [defaultIndex option](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md) | boolean | `true` | +| `forgotPassword` | Settings to customize the forgot password email (see more here: [Forgot Password Email](/developer-docs/latest/development/admin-customization.md#forgotten-password-email)) | Object | {} | +| `forgotPassword.emailTemplate` | Email template as defined in [email plugin](/developer-docs/latest/plugins/email.md#programmatic-usage) | Object | [Default template](https://github.com/strapi/strapi/tree/master/packages/strapi-admin/config/email-templates/forgot-password.js) | +| `forgotPassword.from` | Sender mail address | string | Default value defined in your [provider configuration](/developer-docs/latest/plugins/email.md#configure-the-plugin) | +| `forgotPassword.replyTo` | Default address or addresses the receiver is asked to reply to | string | Default value defined in your [provider configuration](/developer-docs/latest/plugins/email.md#configure-the-plugin) | + +### API tokens + +Authentication strategies in Strapi can either be based on the use of the [Users & Permissions plugin](/user-docs/latest/users-roles-permissions/introduction-to-users-roles-permissions.md) or on the built-in [API token](/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md#api-tokens) feature. + + + +Using API tokens allows executing a request on [REST API](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md) endpoints as an authenticated user. The API token should be added to the request's `Authorization` header with the following syntax: `bearer your-api-token`. + +New API tokens are generated from the admin panel using a salt. This salt is automatically generated by Strapi and stored in `./config/admin.js` as `apiToken.salt`. + +The salt can be customized: + +- either by updating the string value for `apiToken.salt` in `./config/admin.js` +- or by creating an `API_TOKEN_SALT` [environment variable](/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md#environment-variables) in the `.env` file of the project + +::: caution +Changing the salt invalidates all the existing API tokens. +::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md new file mode 100644 index 0000000000..40d1e51d94 --- /dev/null +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md @@ -0,0 +1,250 @@ +--- +title: Middlewares configuration - Strapi Developer Documentation +description: +--- + + + + +# Middlewares configuration + +::: strapi Different types of middlewares + +In Strapi, 2 middleware concepts coexist: + +- **Strapi middlewares** are configured and enabled as global middlewares for the entire Strapi server application. The present documentation describes how to configure Strapi middlewares.
Strapi also offers the ability to implement your own custom middlewares (see [middlewares customization documentation](/developer-docs/latest/development/backend-customization/middlewares.md)). + +- **Route middlewares** have a more limited scope and are configured and used as middlewares at the route level. They are described in the [route middlewares documentation](/developer-docs/latest/development/backend-customization/routes.md#middlewares). + + +::: + +The `./config/middlewares.js` file is used to define all the Strapi middlewares that should be applied by the Strapi server. + +Only the middlewares present in `./config/middlewares.js` are applied. Loading middlewares happens in a specific [loading order](#loading-order), with some [naming conventions](#naming-conventions) and an [optional configuration](#optional-configuration) for each middleware. + + +Strapi prepopulates the `./config/middlewares.js` file with built-in, internal middlewares that all have their own [configuration options](#internal-middlewares-configuration-reference). + +## Loading order + +The `./config/middlewares.js` file exports an array, where order matters and controls the execution order of the middleware stack: + +```js +// path: ./config/middlewares.js + +module.exports = [ + // The array is pre-populated with internal, built-in middlewares, prefixed by `strapi::` + 'strapi::cors', + 'strapi::body', + 'strapi::errors', + // ... + 'my-custom-node-module', // custom middleware that does not require any configuration + { + // custom resolve to find a package or a path + resolve: 'my-custom-node-module', + config: { + foo: 'bar', + }, + }, + { + // custom resolve to find a package or a path + resolve: '../some-dir/custom-middleware', + config: { + foo: 'bar', + }, + }, +]; +``` + +:::tip +If you aren't sure where to place a middleware in the stack, add it to the end of the list. +::: + +## Naming conventions + +Strapi middlewares can be classified into different types depending on their origin, which defines the following naming conventions: + + + +| Middleware type | Origin | Naming convention | +| --------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| Internal | Built-in middlewares (i.e. included with Strapi), automatically loaded | `strapi::middleware-name` | +| Application-level | Loaded from the `./src/middlewares` folder | `global::middleware-name` | +| API-level | Loaded from the `./src/api/[api-name]/middlewares` folder | `api::api-name.middleware-name` | +| Plugin | Exported from `strapi-server.js` in the [`middlewares` property of the plugin interface](/developer-docs/latest/developer-resources/plugin-api-reference/server.md#middlewares) | `plugin::plugin-name.middleware-name` | +| External | Can be:
  • either node modules installed with [npm](https://www.npmjs.com/search?q=strapi-middleware)
  • or local middlewares (i.e. custom middlewares created locally and configured in `./config/middlewares.js`.)
| -

As they are directly configured and resolved from the configuration file, they have no naming convention. | + +## Optional configuration + +Middlewares can have an optional configuration with the following parameters: + +| Parameter | Description | Type | +| --------- | ----------------------------------------------------------------- | ------- | +| `config` | Used to define or override the middleware configuration | Object | +| `resolve` | Path to the middleware's folder (useful for external middlewares) | String | + +## Internal middlewares configuration reference + +Strapi's core includes the following internal middlewares, mostly used for performances, security and error handling: + +- [body](#body), +- [compression](#compression), +- [cors](#cors), +- [errors](#errors), +- [favicon](#favicon), +- [ip](#ip), +- [logger](#logger), +- [poweredBy](#poweredby), +- [query](#query), +- [response-time](#response-time), +- responses, which handle the [responses](/developer-docs/latest/development/backend-customization/requests-responses.md), +- [security](#security), +- [public](#public), + +::: caution +The following built-in middlewares are automatically added by Strapi: `errors`, `security`, `cors`, `query`', `body`, `public`, `favicon`. They should not be removed. Removing them will throw an error. +::: + +### `body` + +The `body` middleware is based on [koa-body](https://github.com/koajs/koa-body) and uses these sensible defaults: + +| Option | Description | Type | Default | +| ----------- | ----------------------------------------- | --------- | ------- | +| `multipart` | Parse multipart bodies | `Boolean` | `true` | +| `patchKoa` | Patch request body to Koa's `ctx.request` | `Boolean` | `true` | + +For a full list of available options, check the [koa-body documentation](https://github.com/koajs/koa-body#options). + +### `compression` + +The `compression` middleware is based on [koa-compress](https://github.com/koajs/compress) and offers the same [options](https://github.com/koajs/compress#options). + +### `cors` + +This security middleware is about cross-origin resource sharing (CORS) and is based on [@koa/cors](https://github.com/koajs/cors). It accepts the following options: + + +| Option | Type | Description | Default value | +| ---------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `origin` | Allowed URLs.

The value(s) can be:
  • strings (e.g. `http://example1.com, http://example2.com`)
  • an array of strings (e.g. `['http://www.example1.com', 'http://example1.com']`)
  • or `*` to allow all URLs
| `String or Array` | `*` | +| `maxAge` | Configure the `Access-Control-Max-Age` CORS header parameter, in seconds | `String or Number` | `31536000` | | +| `credentials` | Configure the `Access-Control-Allow-Credentials` CORS header | `Boolean` | `true` | +| `methods` | Configures the `Access-Control-Allow-Methods` CORS header | `Array` or `String` | `['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']` | +| `headers` | Configure the `Access-Control-Allow-Headers` CORS header

If not specified, defaults to reflecting the headers specified in the request's `Access-Control-Request-Headers` header | `Array` or `String` | `['Content-Type', 'Authorization', 'Origin', 'Accept']` | +| `keepHeaderOnError` | Add set headers to `err.header` if an error is thrown | `Boolean` | `false` | | + +### `errors` + +The errors middleware handles [errors](/developer-docs/latest/developer-resources/error-handling.md) thrown by the code. Based on the type of error it sets the appropriate HTTP status to the response. By default, any error not supposed to be exposed to the end-user will result in a 500 HTTP response. + +The middleware doesn't have any configuration option. + +### `favicon` + +The `favicon` middleware is used to serve the favicon. It's based on [koa-favicon](https://github.com/koajs/favicon) and has these sensible defaults: + +| Option | Description | Type | Default value | +| -------- | ------------------------------------------------ | ------- | ------------- | +| `path` | Path to the favicon file | String | `favicon.ico` | +| `maxAge` | Cache-control max-age directive, in milliseconds | Integer | `86400000` | + +#### `ip` + +The `ip` middleware is an IP filter middleware based on [koa-ip](https://github.com/nswbmw/koa-ip) and accepts the following options: + +| Option | Description | Type | Default value | +| ----------- | --------------- | ----- | ------------- | +| `whitelist` | Whitelisted IPs | Array | `[]` | +| `blacklist` | Blacklisted IPs | Array | `[]` | + +### `logger` + +The `logger` middleware is used to log requests. + +| Option | Description | Type | Default value | +| --------- | -------------------- | ------- | ------------- | +| `enabled` | Enable requests logs | Boolean | `false` | + +To define a custom configuration for the `logger` middleware, create a dedicated configuration file (`./config/logger.js`). It should export an object that must be a complete or partial [winstonjs](https://github.com/winstonjs/winston) logger configuration. The object will be merged with Strapi's default logger configuration on server start. + +::: details Example: Custom configuration for the logger middleware + +```js +'use strict'; + +const { + winston, + formats: { prettyPrint, levelFilter }, +} = require('@strapi/logger'); + +module.exports = { + transports: [ + new winston.transports.Console({ + level: 'http', + format: winston.format.combine( + levelFilter('http'), + prettyPrint({ timestamps: 'YYYY-MM-DD hh:mm:ss.SSS' }) + ), + }), + ], +}; +``` +::: + +### `poweredBy` + +The `poweredBy` middleware adds a `X-Powered-By` parameter to the response header, with a default value of `Strapi `. + +### `query` + +The `query` middleware is a query parser based on [qs](https://github.com/ljharb/qs) and has the following sensible defaults: + +| Option | Description | Type | Default value | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------- | +| `strictNullHandling` | Distinguish between null values and empty strings (see [qs documentation](https://github.com/ljharb/qs#handling-of-null-values)) | Boolean | `true` | +| `arrayLimit` | Maximum index limit when parsing arrays (see [qs documentation](https://github.com/ljharb/qs#parsing-arrays)) | Number | 100 | +| `depth` | Maximum depth of nested objects when parsing objects (see [qs documentation](https://github.com/ljharb/qs#parsing-objects)) | Number | 20 | + +### `response-time` + +The `response-time` middleware enables the `X-Response-Time` (in milliseconds) for the response header. + +### `public` + +The `public` middleware is a static file serving middleware, based on [koa-static](https://github.com/koajs/static). It accepts the following options: + +| Option | Description | Type | Default value | +| -------------- | --------------------------------------------------- | ------- | ------------- | +| `path` | Path to the public folder | String | `./public` | +| `maxAge` | Cache-control max-age directive, in milliseconds | Integer | `60000` | +| `defaultIndex` | Display default index page at `/` and `/index.html` | Boolean | `true` | + +### `security` + +The security middleware is based on [koa-helmet](https://helmetjs.github.io/) and has the following sensible defaults: + +| Option | Description | Type | Default value | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | ---------------------------------------------------- | +| `crossOriginEmbedderPolicy` | Sets the `Cross-Origin-Embedder-Policy` header to `require-corp` | `Boolean` | `false` | +| `crossOriginOpenerPolicy` | Sets the `Cross-Origin-Opener-Policy` header | `Boolean` | `false` | +| `crossOriginOpenerPolicy` | Sets the `Cross-Origin-Resource-Policy` header | `Boolean` | `false` | +| `originAgentCluster` | Sets the `Origin-Agent-Cluster` header | `Boolean` | `false` | +| `contentSecurityPolicy` | Sets the `Content-Security-Policy` header | `Boolean` | `false` | +| `xssFilter` | Disables browsers' cross-site scripting filter by setting the `X-XSS-Protection` header to `0` | `false` | | +| `hsts` | Sets options for the HTTP Strict Transport Security (HSTS) policy.

Accepts the following parameters:
  • `maxAge`: Number of seconds HSTS is in effect
  • `includeSubDomains`: Applies HSTS to all subdomains of the host
|
  • `maxAge`: `Integer`
  • `includeSubDomains`: `Boolean`
|
  • `maxAge`: `31536000`
  • `includeSubDomains`: `true`
| +| `frameguard` | Sets `X-Frame-Options` header to help mitigate clickjacking attacks

Accepts the `action` parameter that specifies which directive to use. | `String` | `sameorigin` | + + diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/server.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/server.md index 64dea8758b..90b1d2f3f5 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/server.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/server.md @@ -9,39 +9,42 @@ description: The `./config/server.js` is used to define server configuration for the Strapi application. -:::: tabs card +::::: tabs card -::: tab Minimal +:::: tab Minimal configuration -## Minimal Server Config +## Minimal configuration -This is the default config created with any new project, all these keys are required at the very least, environmental configs do not need to contain all these values so long as they exist in the default `./config/server.js`. - -**Path β€”** `./config/server.js`. +The default configuration created with any new project should at least include the following: ```js +// path: ./config/server.js + module.exports = ({ env }) => ({ host: env('HOST', '0.0.0.0'), port: env.int('PORT', 1337), - admin: { - auth: { - secret: env('ADMIN_JWT_SECRET', 'someSecretKey'), - }, - }, }); ``` +:::note +Environmental configurations (`env`) do not need to contain all these values so long as they exist in the default `./config/server.js`. ::: -::: tab Full +:::: -## Full Server Config +:::: tab Full configuration -This is an example of a full configuration, typically certain keys do not need to present in environmental configs, and not all of these keys are required. Please see the table below to see what each key does. +## Full configuration -**Path β€”** `./config/server.js`. +The following is an example of a full configuration file. Not all of these keys are required (see [available options](#available-options)). + +:::note +Environmental configurations (`env`) do not need to contain all these values so long as they exist in the default `./config/server.js`. +::: + +```js +// path: ./config/server.js -```javascript module.exports = ({ env }) => ({ host: env('HOST', '0.0.0.0'), port: env.int('PORT', 1337), @@ -52,66 +55,28 @@ module.exports = ({ env }) => ({ cron: { enabled: env.bool('CRON_ENABLED', false), }, - admin: { - auth: { - events: { - onConnectionSuccess(e) { - console.log(e.user, e.provider); - }, - onConnectionError(e) { - console.error(e.error, e.provider); - }, - }, - secret: env('ADMIN_JWT_SECRET', 'someSecretKey'), - }, - url: env('PUBLIC_ADMIN_URL', '/dashboard'), - autoOpen: false, - watchIgnoreFiles: [ - './my-custom-folder', // Folder - './scripts/someScript.sh', // File - ], - host: 'localhost', // Only used for --watch-admin - port: 8003, // Only used for --watch-admin - serveAdminPanel: env.bool('SERVE_ADMIN', true), - forgotPassword: { - from: 'no-reply@example.com', - replyTo: 'no-reply@example.com', - }, - }, }); ``` -::: - :::: -### Available options +::::: + +## Available options + +The `./config/server.js` file can contain the following parameters: -| Property | Description | Type | Default | +| Parameter | Description | Type | Default | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `host` | Host name | string | `localhost` | -| `port` | Port on which the server should be running. | integer | `1337` | +| `host`

❗️ _Mandatory_ | Host name | string | `localhost` | +| `port`

❗️ _Mandatory_ | Port on which the server should be running. | integer | `1337` | | `socket` | Listens on a socket. Host and port are cosmetic when this option is provided and likewise use `url` to generate proper urls when using this option. This option is useful for running a server without exposing a port and using proxy servers on the same machine (e.g [Heroku nginx buildpack](https://github.com/heroku/heroku-buildpack-nginx#requirements-proxy-mode)) | string \| integer | `/tmp/nginx.socket` | | `emitErrors` | Enable errors to be emitted to `koa` when they happen in order to attach custom logic or use error reporting services. | boolean | `false` | | `url` | Public url of the server. Required for many different features (ex: reset password, third login providers etc.). Also enables proxy support such as Apache or Nginx, example: `https://mywebsite.com/api`. The url can be relative, if so, it is used with `http://${host}:${port}` as the base url. An absolute url is however **recommended**. | string | `''` | | `proxy` | Set the koa variable `app.proxy`. When `true`, proxy header fields will be trusted. | boolean | `false` | | `cron` | Cron configuration (powered by [`node-schedule`](https://github.com/node-schedule/node-schedule)) | Object | | -| `cron.enabled` | Enable or disable CRON tasks to schedule jobs at specific dates. | boolean | `false` | -| `admin` | Admin panel configuration | Object | | -| `admin.auth` | Authentication configuration | Object | | -| `admin.auth.secret` | Secret used to encode JWT tokens | string | `undefined` | -| `admin.auth.events` | Record of all the events subscribers registered for the authentication | object | `{}` | -| `admin.auth.events.onConnectionSuccess` | Function called when an admin user log in successfully to the administration panel | function | `undefined` | -| `admin.auth.events.onConnectionError` | Function called when an admin user fails to log in to the administration panel | function | `undefined` | -| `admin.url` | Url of your admin panel. Default value: `/admin`. Note: If the url is relative, it will be concatenated with `url`. | string | `/admin` | -| `admin.autoOpen` | Enable or disabled administration opening on start. | boolean | `true` | -| `admin.watchIgnoreFiles` | Add custom files that should not be watched during development. See more [here](https://github.com/paulmillr/chokidar#path-filtering) (property `ignored`). | Array(string) | `[]` | -| `admin.host` | Use a different host for the admin panel. Only used along with `strapi develop --watch-admin` | string | `localhost` | -| `admin.port` | Use a different port for the admin panel. Only used along with `strapi develop --watch-admin` | string | `8000` | -| `admin.serveAdminPanel` | If false, the admin panel won't be served. Note: the `index.html` will still be served, see [defaultIndex option](/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md#global-middlewares) | boolean | `true` | -| `admin.forgotPassword` | Settings to customize the forgot password email (see more here: [Forgot Password Email](/developer-docs/latest/development/admin-customization.md#forgotten-password-email)) | Object | {} | -| `admin.forgotPassword.emailTemplate` | Email template as defined in [email plugin](/developer-docs/latest/plugins/email.md#programmatic-usage) | Object | [Default template](https://github.com/strapi/strapi/tree/master/packages/strapi-admin/config/email-templates/forgot-password.js) | -| `admin.forgotPassword.from` | Sender mail address | string | Default value defined in your [provider configuration](/developer-docs/latest/plugins/email.md#configure-the-plugin) | -| `admin.forgotPassword.replyTo` | Default address or addresses the receiver is asked to reply to | string | Default value defined in your [provider configuration](/developer-docs/latest/plugins/email.md#configure-the-plugin) | +| `cron.enabled` | Enable or disable [CRON jobs](/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md) to schedule jobs at specific dates. | boolean | `false` | +| `cron.tasks` | Declare [CRON jobs](/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md) to be run at specific dates. | boolean | `false` | + | string | Default value defined in your [provider configuration](/developer-docs/latest/plugins/email.md#configure-the-plugin) | diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/google-app-engine.md b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/google-app-engine.md index ce84b01658..79c81cefea 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/google-app-engine.md +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/google-app-engine.md @@ -244,7 +244,7 @@ Follow the [documentation of the plugin](https://github.com/Lith/strapi-provider CORS is enabled by default, allowing `*` origin. You may want to limit the allowed origins. -Read the documentation [here](/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md). +Read the documentation [here](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md). **Changing the admin url** diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.md b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.md index 595b7d29d5..534256d778 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.md +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.md @@ -353,7 +353,7 @@ Due to Heroku's filesystem you will need to use an upload provider such as AWS S ### Gzip -As of version `3.2.1`, Strapi uses [`koa-compress`](https://github.com/koajs/compress) v5, which enables [Brotli](https://en.wikipedia.org/wiki/Brotli) compression by default. At the time of writing, the default configuration for Brotli results in poor performance, causing very slow response times and potentially response timeouts. If you plan on enabling the [gzip middleware](/developer-docs/latest/setup-deployment-guides/configurations/optional/middlewares.md#core-middleware-configurations-reference), it is recommended that you disable Brotli or define better configuration params. +As of version `3.2.1`, Strapi uses [`koa-compress`](https://github.com/koajs/compress) v5, which enables [Brotli](https://en.wikipedia.org/wiki/Brotli) compression by default. At the time of writing, the default configuration for Brotli results in poor performance, causing very slow response times and potentially response timeouts. If you plan on enabling the [gzip middleware](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md#internal-middlewares-configuration-reference), it is recommended that you disable Brotli or define better configuration params. To disable Brotli, provide the following configuration in `config/middleware.js`. diff --git a/docs/developer-docs/latest/setup-deployment-guides/file-structure.md b/docs/developer-docs/latest/setup-deployment-guides/file-structure.md index 37c9026e76..3f8804b206 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/file-structure.md +++ b/docs/developer-docs/latest/setup-deployment-guides/file-structure.md @@ -45,8 +45,9 @@ The default structure of a Strapi project created without the starter CLI looks β”‚ β”‚ β”œ cron-tasks.js β”‚ β”‚ β”” response-handlers.js β”‚ β”œ api.js +β”‚ β”œ admin.js β”‚ β”œ database.js -β”‚ β”œ middleware.js +β”‚ β”œ middlewares.js β”‚ β”œ plugins.js β”‚ β”” server.js β”œβ”€β”€β”€β”€ database @@ -88,11 +89,13 @@ The default structure of a Strapi project created without the starter CLI looks β”‚ β”‚ β”‚ └──── src β”‚ β”‚ β”‚ β”” index.js β”‚ β”‚ β”œβ”€β”€β”€β”€ server +β”‚ β”‚ β”œβ”€β”€β”€β”€ controllers +β”‚ β”‚ β”œβ”€β”€β”€β”€ policies β”‚ β”‚ β”œ package.json β”‚ β”‚ β”œ strapi-admin.js β”‚ β”‚ β”” strapi-server.js β”‚ β”œβ”€β”€β”€ policies -β”‚ β”” index.js # include register() and bootstrap() functions +β”‚ β”” index.js # include register(), bootstrap() and destroy() functions β”œ .env β”” package.json diff --git a/docs/developer-docs/latest/setup-deployment-guides/installation/templates.md b/docs/developer-docs/latest/setup-deployment-guides/installation/templates.md index 7ee2f5be0f..0a4fdc7caf 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/installation/templates.md +++ b/docs/developer-docs/latest/setup-deployment-guides/installation/templates.md @@ -78,14 +78,14 @@ First, a template's only concern should be to adapt Strapi to a use case. It sho Second, a template must follow the file structure detailed below. -You can create this file structure by hand or generate it via the [CLI](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-generate-template). +You can create this file structure by hand or generate it via the [CLI](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-templates-generate). :::: tabs ::: tab yarn ```bash -yarn strapi generate:template +yarn strapi templates:generate ``` ::: @@ -93,7 +93,7 @@ yarn strapi generate:template ::: tab npx ```bash -npx strapi generate:template +npx strapi templates:generate ``` ::: @@ -155,7 +155,7 @@ After reading the above rules, follow these steps to create your template: 1. Create a standard Strapi app with `create-strapi-app`, using the `--quickstart` option. 2. Customize your app to match the needs of your use case. -3. Generate your template using the [CLI](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-generate-template) by running `strapi generate:template ` +3. Generate your template using the [CLI](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-templates-generate) by running `strapi templates:generate ` 4. Navigate to this path to see your generated template 5. If you have modified your app's `package.json`, include these changes (and _only_ these changes) in `template.json` in a `package` property. Otherwise, leave it as an empty object. 6. Publish the root template project on GitHub. Make sure that the repository is public, and that the code is on the `master` branch. diff --git a/docs/developer-docs/latest/update-migration-guides/migration-guides.md b/docs/developer-docs/latest/update-migration-guides/migration-guides.md index 8e1343df3c..af56c25e06 100644 --- a/docs/developer-docs/latest/update-migration-guides/migration-guides.md +++ b/docs/developer-docs/latest/update-migration-guides/migration-guides.md @@ -39,6 +39,7 @@ If you were to upgrade your version from `3.2.3` to `3.6.1`, you would have to f ::: warning The Strapi Beta version is no longer supported, you should upgrade to the V3 Stable. +If you have issues upgrading, it's our general recommendation to create a new project. ::: diff --git a/docs/developer-docs/latest/update-migration-guides/migration-guides/migration-guide-beta.15-to-beta.16.md b/docs/developer-docs/latest/update-migration-guides/migration-guides/migration-guide-beta.15-to-beta.16.md index 1a169d1ab2..8784a98931 100644 --- a/docs/developer-docs/latest/update-migration-guides/migration-guides/migration-guide-beta.15-to-beta.16.md +++ b/docs/developer-docs/latest/update-migration-guides/migration-guides/migration-guide-beta.15-to-beta.16.md @@ -172,7 +172,7 @@ module.exports = () => {}; ### Custom hooks -If you have custom [hooks](/developer-docs/latest/setup-deployment-guides/configurations/optional/hooks.md) in your project, the `initialize` function will not receive a callback anymore. You can either use an async function, return a promise or simply run a synchronous function. +If you have custom hooks in your project, the `initialize` function will not receive a callback anymore. You can either use an async function, return a promise or simply run a synchronous function. **Before** diff --git a/docs/user-docs/latest/settings/managing-global-settings.md b/docs/user-docs/latest/settings/managing-global-settings.md index dbc57dce8b..dd91e6b244 100644 --- a/docs/user-docs/latest/settings/managing-global-settings.md +++ b/docs/user-docs/latest/settings/managing-global-settings.md @@ -41,7 +41,7 @@ For each locale, the table displays the default ISO code of the locale, its opti Administrators can add and manage as many locales as they want. There can however only be one locale set as the default one for the whole Strapi application. ::: note -It is not possible to create custom locales. Locales can only be created based on [the 500+ pre-created list of locales](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-i18n/constants/iso-locales.json) set by Strapi. +It is not possible to create custom locales. Locales can only be created based on [the 500+ pre-created list of locales](https://github.com/strapi/strapi/blob/releases/v4/packages/plugins/i18n/server/constants/iso-locales.json) set by Strapi. ::: To add a new locale: @@ -50,4 +50,39 @@ To add a new locale: 2. In the locale addition window, choose your new locale among the *Locales* drop-down list. The latter lists alphabetically all locales, displayed as their ISO code, that can be added to your Strapi application. 3. (optional) In the *Locale display name* textbox, write a new display name for your new locale. 4. (optional) In the Advanced settings tab, tick the *Set as default locale* setting to make your new locale the default one for your Strapi application. -5. Click on the **Add locale** button to confirm the addition of your new locale. \ No newline at end of file +5. Click on the **Add locale** button to confirm the addition of your new locale. + +## Managing API tokens + +API tokens allow users to authenticate their Content API queries (see Developer Documentation). Administrators can manage API tokens through the *Global settings > API Tokens* sub-section of the settings interface. + + + +The *API Tokens* settings sub-section displays a table listing all created API tokens. + +For each API token, the table displays its name, description, type and date of creation. From the table, administrators can also: + +- Click on the edit button to edit an API token's name, description or type +- Click on the delete button to delete an API token + +### Creating a new API token + +All API tokens created by administrators of the Strapi application are permanent tokens that cannot be regenerated. + +To create a new API token: + +1. Click on the **Add new entry** button. +2. In the API token edition interface, configure the new API token: + +| Setting name | Instructions | +|--------------|-----------------------------------------------------------| +| Name | Write the name of the API token. | +| Description | (optional) Write a description for the API token. | +| Token type | Choose a token type: either *Read-only* or *Full access*. | + +3. Click on the **Save** button. The new API token will be displayed at the top of the interface, along with a copy button . + +::: caution +For security reasons, API tokens are only shown right after they have been created. When refreshing the page or navigating elsewhere in the admin panel, the newly created API token will be hidden and will not be displayed again. +::: + diff --git a/docs/user-docs/latest/users-roles-permissions/configuring-administrator-roles.md b/docs/user-docs/latest/users-roles-permissions/configuring-administrator-roles.md index 131dfa80d8..d70c1e39b3 100644 --- a/docs/user-docs/latest/users-roles-permissions/configuring-administrator-roles.md +++ b/docs/user-docs/latest/users-roles-permissions/configuring-administrator-roles.md @@ -135,6 +135,7 @@ Settings permissions can be configured for all settings accessible from *General | Plugins and Marketplace |
  • Marketplace
    • "Access the Marketplace" - gives access to the Marketplace
  • Plugins
    • "Install (only for dev env)" - allows to install new plugins when in a development environment
    • "Uninstall (only for dev env)" - allows to uninstall plugins when in a development environment
| | Webhooks |
  • General
    • "Create" - allows to create webhooks
    • "Read" - allows to see created webhooks
    • "Update" - allows to edit webhooks
    • "Delete" - allows to delete webhooks
πŸ‘‰ Path reminder to Webhook settings:
*General > Settings > Global Settings - Webhook* | | Users and Roles |
  • Users
    • "Create (invite)" - allows to create administrator accounts
    • "Read" - allows to see existing administrator accounts
    • "Update" - allows to edit administrator accounts
    • "Delete" - allows to delete administrator accounts
  • Roles
    • "Create" - allows to create administrator roles
    • "Read" - allows to see created administrator roles
    • "Update" - allows to edit administrator roles
    • "Delete" - allows to delete administrator roles
πŸ‘‰ Path reminder to the RBAC feature:
*General > Settings > Administration Panel* | +| API tokens |
  • General
    • "Create (generate)" - allows to create API tokens
    • "Read" - allows to see created API tokens
    • "Update" - allows to edit API tokens' settings
    • "Delete" - allows to delete API tokens
πŸ‘‰ Path reminder to API tokens settings:
*General > Settings > Global Settings - API Tokens* | :::