Throwing and Handling Errors
In this guide, you'll learn how to return errors in your MedusaGo application, and how they affect an API route's response.
Return a MedusaError#
When an error occurs in your API routes, middlewares, workflows, or any customization, return a *MedusaError from the utils/src/common package. Go has no exceptions, so return the error from your function instead of throwing it. A handler or middleware that returns an error stops the request.
The MedusaGo application's API route error handler then wraps your returned error in a uniform object and returns it in the response.
For example:
// searchGET returns an INVALID_DATA error when the q query parameter is
// missing. The error handler turns it into a 400 response.
func searchGET(req *medusahttp.MedusaRequest, res *medusahttp.MedusaResponse) error {
q, _ := queryString(req.Query, "q")
if q == "" {
return common.NewMedusaError(
common.MedusaErrorTypes.INVALID_DATA,
"The `q` query parameter is required.",
)
}
return res.JSON(map[string]any{"q": q})
}
The NewMedusaError function accepts the following parameters:
- The error's type. The
common.MedusaErrorTypesvariable has the available types, which are explained in the next section. - The message to show in the error response.
- Optionally, a code to return in the response, as a third parameter.
common.AsMedusaError to check if an error you received is a MedusaError, and read its Type.Error Object in Response#
The error object returned in the response has three properties:
type: The error's type.message: The error message, if available.code: A common snake-case code. Its values can be:invalid_request_errorfor theDUPLICATE_ERRORtype.api_errorfor theDB_ERRORtype.invalid_state_errorfor theCONFLICTerror type.unknown_errorfor any unidentified error type.- For other error types, this property won't be available unless you provide a code as a third parameter to
NewMedusaError.
For example, this API route returns an error with a code:
func quotaGET(req *medusahttp.MedusaRequest, res *medusahttp.MedusaResponse) error {
// The third argument is an optional code, returned as the code property.
return common.NewMedusaError(
common.MedusaErrorTypes.NOT_ALLOWED,
"The quota was reached.",
"quota_reached",
)
}
It responds with a 400 status code and the following body:
MedusaError Types#
Type | Description | Status Code |
|---|---|---|
| Indicates a database error. |
|
| Indicates a duplicate of a record already exists. For example, when trying to create a customer whose email is registered by another customer. |
|
| Indicates an error that occurred due to incorrect arguments or other unexpected state. |
|
| Indicates a validation error. |
|
| Indicates that a user is not authorized to perform an action or access a route. |
|
| Indicates that a user is authenticated, but isn't allowed to perform an action. |
|
| Indicates that the requested resource, such as a route or a record, isn't found. |
|
| Indicates that an operation isn't allowed. |
|
| Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored in favor of a default message. Refer to the note below for details. |
|
| Indicates an error has occurred while authorizing a payment. |
|
Other error types, and errors that aren't a | Any other error type results in an |
|
Conflict Error Messages#
The error handler replaces the message of a CONFLICT error with the following default message:
The request conflicted with another request. You may retry the request with the provided Idempotency-Key.
So, only return a CONFLICT error for internal conflicts, such as a request that conflicts with an ongoing transaction or a lock that another process holds. The client doesn't need details about these conflicts, and the default message avoids exposing internal implementation details.
NOT_ALLOWED error instead. Its message reaches the client, but the response has a 400 status code rather than 409.Database Errors#
Errors from the database layer that the application recognizes, such as a unique constraint violation or a reference to a record that doesn't exist, are converted into a MedusaError before the response is sent. For example, a unique constraint violation results in a DUPLICATE_ERROR.
Override Error Handler#
If you need to run custom logic when an error occurs, such as capturing it in a third-party service, handle the error where it's returned. For example, wrap your route handlers in a function that reports the errors they return before it returns them:
// withErrorReporting wraps a route handler: it reports the errors the
// handler returns, then returns them for the error handler to process.
func withErrorReporting(handler medusahttp.RouteHandler) medusahttp.RouteHandler {
return func(req *medusahttp.MedusaRequest, res *medusahttp.MedusaResponse) error {
err := handler(req, res)
if err != nil {
// for example, capture the error in a third-party service
slog.Error("route failed", "path", req.Path(), "error", err)
}
return err
}
}