Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions docusaurus/docs/cms/backend-customization/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ module.exports = {
{
method: 'GET',
path: '/hello',
handler: 'hello.index',
handler: 'api::hello.hello.index',
}
]
}
```

```js "title="./src/api/hello/controllers/hello.js"
```js title="./src/api/hello/controllers/hello.js"

module.exports = {
async index(ctx, next) { // called by GET /hello
Expand All @@ -183,14 +183,14 @@ module.exports = {

<TabItem value="ts" label="TypeScript">

```js "title="./src/api/hello/routes/hello.ts"
```js title="./src/api/hello/routes/hello.ts"

export default {
routes: [
{
method: 'GET',
path: '/hello',
handler: 'hello.index',
handler: 'api::hello.hello.index',
}
]
}
Expand Down Expand Up @@ -219,6 +219,37 @@ When a new [content-type](/cms/backend-customization/models#content-types) is cr
To see a possible advanced usage for custom controllers, read the [services and controllers](/cms/backend-customization/examples/services-and-controllers) page of the backend customization examples cookbook.
:::

### Controllers & Routes: How routes reach controller actions

- Core mapping is automatic: when you generate a content-type, Strapi creates the matching controller and a router file that already targets the standard actions (`find`, `findOne`, `create`, `update`, and `delete`). Overriding any of these actions inside the generated controller does not require touching the router — the route keeps the same handler string and executes your updated logic.
- Adding a route should only be done for new actions or paths. If you introduce a brand-new method such as `exampleAction`, create or update a route entry whose `handler` points to the action so HTTP requests can reach it. Use the fully-qualified handler syntax `<scope>::<api-or-plugin-name>.<controllerName>.<actionName>` (e.g. `api::restaurant.restaurant.exampleAction` for an API controller or `plugin::menus.menu.exampleAction` for a plugin controller).
- Regarding controller and route filenames: the default controller name comes from the filename inside `./src/api/[api-name]/controllers/`. Core routers created with `createCoreRouter` adopt the same name, so the generated handler string matches automatically. Custom routers can follow any file naming scheme, as long as the `handler` string references an exported controller action.

The example below adds a new controller action and exposes it through a custom route without duplicating the existing CRUD route definitions:

```js title="./src/api/restaurant/controllers/restaurant.js"
const { createCoreController } = require('@strapi/strapi').factories;

module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
async exampleAction(ctx) {
const specials = await strapi.service('api::restaurant.restaurant').find({ filters: { isSpecial: true } });
return this.transformResponse(specials.results);
},
}));
```

```js title="./src/api/restaurant/routes/01-custom-restaurant.js"
module.exports = {
routes: [
{
method: 'GET',
path: '/restaurants/specials',
handler: 'api::restaurant.restaurant.exampleAction',
},
],
};
```

### Sanitization and Validation in controllers {#sanitization-and-validation-in-controllers}

:::warning
Expand Down
23 changes: 18 additions & 5 deletions docusaurus/docs/cms/backend-customization/routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ Requests sent to Strapi on any URL are handled by routes. By default, Strapi gen

Once a route exists, reaching it executes some code handled by a controller (see [controllers documentation](/cms/backend-customization/controllers)). To view all existing routes and their hierarchal order, you can run `yarn strapi routes:list` (see [CLI reference](/cms/cli)).

:::tip
If you only customize the default controller actions (`find`, `findOne`, `create`, `update`, or `delete`) that Strapi generates for a content-type, you can leave the router as-is. Those core routes already target the same handler names and will run your new controller logic. Add or edit a route only when you need a brand-new HTTP path/method or want to expose a custom controller action.
:::

<figure style={{width: '100%', margin: '0'}}>
<img src="/img/assets/backend-customization/diagram-routes.png" alt="Simplified Strapi backend diagram with routes highlighted" />
<em><figcaption style={{fontSize: '12px'}}>The diagram represents a simplified version of how a request travels through the Strapi back end, with routes highlighted. The backend customization introduction page includes a complete, <a href="/cms/backend-customization#interactive-diagram">interactive diagram</a>.</figcaption></em>
Expand Down Expand Up @@ -174,7 +178,7 @@ Creating custom routers consists in creating a file that exports an array of obj
| -------------------------- | -------------------------------------------------------------------------------- | -------- |
| `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.<br/>Should follow this syntax: `<controllerName>.<actionName>` | `String` |
| `handler` | Function to execute when the route is reached.<br/>Use the fully-qualified syntax `api::api-name.controllerName.actionName` (or `plugin::plugin-name.controllerName.actionName`). The short `<controllerName>.<actionName>` form for legacy projects also works. | `String` |
| `config`<br /><br />_Optional_ | Configuration to handle [policies](#policies), [middlewares](#middlewares) and [public availability](#public-routes) for the route<br/><br/> | `Object` |

<br/>
Expand All @@ -185,6 +189,15 @@ Dynamic routes can be created using parameters and regular expressions. These pa
Routes files are loaded in alphabetical order. To load custom routes before core routes, make sure to name custom routes appropriately (e.g. `01-custom-routes.js` and `02-core-routes.js`).
:::

:::info Controller handler naming reference
The `handler` string acts as a pointer to the controller action that should run for the route. Strapi supports the following formats:

- API controllers: `api::<api-name>.<controllerName>.<actionName>` (e.g. `api::restaurant.restaurant.exampleAction`). The `<controllerName>` comes from the controller filename inside `./src/api/<api-name>/controllers/`.
- Plugin controllers: `plugin::<plugin-name>.<controllerName>.<actionName>` when the controller lives in a plugin.

For backwards compatibility, Strapi also accepts a short `<controllerName>.<actionName>` string for API controllers, but using the fully-qualified form makes the route more explicit and avoids naming collisions across APIs and plugins.
:::

<details>

<summary>Example of a custom router using URL parameters and regular expressions for routes</summary>
Expand All @@ -202,12 +215,12 @@ module.exports = {
{ // Path defined with an URL parameter
method: 'POST',
path: '/restaurants/:id/review',
handler: 'restaurant.review',
handler: 'api::restaurant.restaurant.review',
},
{ // Path defined with a regular expression
method: 'GET',
path: '/restaurants/:category([a-z]+)', // Only match when the URL parameter is composed of lowercase letters
handler: 'restaurant.findByCategory',
handler: 'api::restaurant.restaurant.findByCategory',
}
]
}
Expand All @@ -224,12 +237,12 @@ export default {
{ // Path defined with a URL parameter
method: 'GET',
path: '/restaurants/:category/:id',
handler: 'Restaurant.findOneByCategory',
handler: 'api::restaurant.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',
handler: 'api::restaurant.restaurant.findOneByRegion',
}
]
}
Expand Down