diff --git a/docs/developer-docs/latest/development/backend-customization/controllers.md b/docs/developer-docs/latest/development/backend-customization/controllers.md index 30da0080c4..324fe9236e 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#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); + exampleAction: async (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 = { + index: async (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/setup-deployment-guides/file-structure.md b/docs/developer-docs/latest/setup-deployment-guides/file-structure.md index c1bc0e2c57..475af2a35f 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/file-structure.md +++ b/docs/developer-docs/latest/setup-deployment-guides/file-structure.md @@ -89,6 +89,7 @@ The default structure of a Strapi project created without the starter CLI looks │ │ │ └──── src │ │ │ └ index.js │ │ ├──── server +│ │ ├──── controllers │ │ ├──── policies │ │ ├ package.json │ │ ├ strapi-admin.js