Skip to content
Merged
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
345 changes: 339 additions & 6 deletions docs/developer-docs/latest/developer-resources/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ Errors thrown by the GraphQL API are included in the [response](/developer-docs/

## 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.
### Controllers & Middlewares

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) or [middleware](/developer-docs/latest/development/backend-customization/) 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 lower camel-cased to be used by Strapi (e.g. `badRequest`).

Expand All @@ -87,16 +89,23 @@ module.exports = {
}
}

```
// path: ./src/api/[api-name]/middlewares/my-middleware.js

module.exports = async (ctx, next) => {
const newName = ctx.request.body.name;
if (!newName) {
return ctx.badRequest('name is missing', { foo: 'bar' })
}
await next();
}

```

</code-block>

<code-block title="TYPESCRIPT">


```js

// path: ./src/api/[api-name]/controllers/my-controller.ts

export default {
Expand All @@ -109,12 +118,336 @@ export default {
}
}

// path: ./src/api/[api-name]/middlewares/my-middleware.ts

export default async (ctx, next) => {
const newName = ctx.request.body.name;
if (!newName) {
return ctx.badRequest('name is missing', { foo: 'bar' })
}
await next();
}

```

</code-block>
</code-group>

### Services and Model Lifecycles

Once you are working at a deeper layer than the controllers or middlewares there are dedicated error classes that can be used to throw errors. These classes are extensions of [Node `Error` class](https://nodejs.org/api/errors.html#errors_class_error) and are specifically targeted for certain use-cases.

These error classes are imported through the `@strapi/utils` package and can be called from several different layers. The following examples use the service layer but error classes are not just limited to services and model lifecycles. When throwing errors in the model lifecycle layer, it's recommended to use the `ApplicationError` class so that proper error messages are shown in the admin panel.

::: note
See the [default error classes](#default-error-classes) section for more information on the error classes provided by Strapi.
:::

::: details Example: Throwing an error in a service
This example shows wrapping a [core service](/developer-docs/latest/development/backend-customization/services.md#extending-core-services) and doing a custom validation on the `create` method:

<code-group>
<code-block title="JAVASCRIPT">

```js
// path: ./src/api/restaurant/services/restaurant.js

const utils = require('@strapi/utils');
const { ApplicationError } = utils.errors;
const { createCoreService } = require('@strapi/strapi').factories;

module.exports = createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
async create(params) {
let okay = false;

// Throwing an error will prevent the restaurant from being created
if (!okay) {
throw new ApplicationError('Something went wrong', { foo: 'bar' });
}

const result = await super.create(params);

return result;
}
});

```

</code-block>

<code-block title="TYPESCRIPT">

```js
// path: ./src/api/[api-name]/policies/my-policy.ts

import utils from '@strapi/utils';
import { factories } from '@strapi/strapi';

const { ApplicationError } = utils.errors;

export default factories.createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
async create(params) {
let okay = false;

// Throwing an error will prevent the restaurant from being created
if (!okay) {
throw new ApplicationError('Something went wrong', { foo: 'bar' });
}

const result = await super.create(params);

return result;
}
}));

```

</code-block>
</code-group>

:::

::: details Example: Throwing an error in a model lifecycle
This example shows building a [custom model lifecyle](/developer-docs/latest/development/backend-customization/models.md#lifecycle-hooks) and being able to throw an error that stops the request and will return proper error messages to the admin panel. Generally you should only throw an error in `beforeX` lifecycles, not `afterX` lifecycles.

<code-group>
<code-block title="JAVASCRIPT">

```js
// path: ./src/api/[api-name]/content-types/[api-name]/lifecycles.js

const utils = require('@strapi/utils');
const { ApplicationError } = utils.errors;

module.exports = {
beforeCreate(event) {
let okay = false;

// Throwing an error will prevent the entity from being created
if (!okay) {
throw new ApplicationError('Something went wrong', { foo: 'bar' });
}
},
};

```

</code-block>

<code-block title="TYPESCRIPT">

```js
// path: ./src/api/[api-name]/content-types/[api-name]/lifecycles.ts

import utils from '@strapi/utils';
const { ApplicationError } = utils.errors;

export default {
beforeCreate(event) {
let okay = false;

// Throwing an error will prevent the entity from being created
if (!okay) {
throw new ApplicationError('Something went wrong', { foo: 'bar' });
}
},
};

```

</code-block>
</code-group>

:::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.
:::

### Policies

[Policies](/developer-docs/latest/development/backend-customization/policies.md) are a special type of middleware that are executed before a controller. They are used to check if the user is allowed to perform the action or not. If the user is not allowed to perform the action and a `return false` is used then a generic error will be thrown. As an alternative, you can throw a custom error message using a nested class extensions from the Strapi `ForbiddenError` class, `ApplicationError` class (see [Default error classes](#default-error-classes) for both classes), and finally the [Node `Error` class](https://nodejs.org/api/errors.html#errors_class_error).

The `PolicyError` class is available from `@strapi/utils` package and accepts 2 parameters:

- the first parameter of the function is the error `message`
- (optional) the second parameter is the object that will be set as `details` in the response received; a best practice is to set a `policy` key with the name of the policy that threw the error.

::: details Example: Throwing a PolicyError in a custom policy
This example shows building a [custom policy](/developer-docs/latest/development/backend-customization/policies.md) that will throw a custom error message and stop the request.

<code-group>
<code-block title="JAVASCRIPT">

```js
// path: ./src/api/[api-name]/policies/my-policy.js

const utils = require('@strapi/utils');
const { PolicyError } = utils.errors;

module.exports = (policyContext, config, { strapi }) => {
let isAllowed = false;

if (isAllowed) {
return true;
} else {
throw new PolicyError('You are not allowed to perform this action', {
policy: 'my-policy',
myCustomKey: 'myCustomValue',
});
}
}

```

</code-block>

<code-block title="TYPESCRIPT">

```js
// path: ./src/api/[api-name]/policies/my-policy.ts

const utils = require('@strapi/utils');
const { PolicyError } = utils.errors;

export default (policyContext, config, { strapi }) => {
let isAllowed = false;

if (isAllowed) {
return true;
} else {
throw new PolicyError('You are not allowed to perform this action', {
policy: 'my-policy',
myCustomKey: 'myCustomValue',
});
}
};

```

</code-block>
</code-group>

:::

### Default error classes

The default error classes are available from the `@strapi/utils` package and can be imported and used in your code. Any of the default error classes can be extended to create a custom error class. The custom error class can then be used in your code to throw errors.

:::: tabs card

::: tab Application

The `ApplicationError` class is a generic error class for application errors and is generally recommended as the default error class. This class is specifically designed to throw proper error messages that the admin panel can read and show to the user. It accepts the following parameters:

| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `An application error occured` |
| `details` | `object` | Object to define additional details | `{}` |

```js
throw new ApplicationError('Something went wrong', { foo: 'bar' });
```

:::

<!-- Not sure if it's worth keeping this tab or not as it's very specific to Strapi internal use-cases -->
<!-- ::: tab Validation

The `ValidationError` and `YupValidationError` classes are specific error classes designed to be used with the built in validations system and specifically format the errors coming from [Yup](https://www.npmjs.com/package/yup). The `ValidationError` does not accept any parameters but the `YupValidationError` accepts the following parameters:

| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | - |
| `details` | `object` | Object to define additional details | `{ yupErrors }` |

```js

```js
throw new PolicyError('Something went wrong', { policy: 'my-policy' });
```

::: -->

::: tab Pagination

The `PaginationError` class is a specific error class that is typically used when parsing the pagination information from [REST](/developer-resources/database-apis-reference/rest/sort-pagination.md#pagination), [GraphQL](/developer-resources/database-apis-reference/graphql-api.md#pagination), or the [Entity Service](/developer-resources/database-apis-reference/entity-service/order-pagination.md#pagination). It accepts the following parameters:

| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Invalid pagination` |

```js
throw new PaginationError('Exceeded maximum pageSize limit');
```

:::

::: tab NotFound

The `NotFoundError` class is a generic error class for throwing `404` status code errors. It accepts the following parameters:

| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Entity not found` |

```js
throw new NotFoundError('These are not the droids you are looking for');
```

:::

::: tab Forbidden

The `ForbiddenError` class is a specific error class used when a user either doesn't provide any or the correct authentication credentials. It accepts the following parameters:

| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Forbidden access` |

```js
throw new ForbiddenError('Ah ah ah, you didn\'t say the magic word');
```

:::

::: tab Unauthorized

The `UnauthorizedError` class is a specific error class used when a user doesn't have the proper role or permissions to perform a specific action, but has properly authenticated. It accepts the following parameters:

| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Unauthorized` |

```js
throw new UnauthorizedError('You shall not pass!');
```

:::

::: tab PayloadTooLarge

The `PayloadTooLargeError` class is a specific error class used when the incoming request body or attached files exceed the limits of the server. It accepts the following parameters:

| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Entity too large` |

```js
throw new PayloadTooLargeError('Uh oh, the file too big!');
```

:::

::: tab Policy

The `PolicyError` class is a specific error designed to be used with [route policies](/developer-docs/latest/development/backend-customization/policies.md). The best practice recommendation is to ensure the name of the policy is passed in the `details` parameter. It accepts the following parameters:

| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Policy Failed` |
| `details` | `object` | Object to define additional details | `{}` |

```js
throw new PolicyError('Something went wrong', { policy: 'my-policy' });
```

:::

::::