5.3 KiB
Error Handling in Medusa
Medusa provides the MedusaError class for consistent error responses across your API routes and custom code.
Contents
Using MedusaError
Use MedusaError in API routes, workflows, and custom modules to throw errors that Medusa will automatically format and return to clients:
import { MedusaError } from "@medusajs/framework/utils"
// Throw an error
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
"Product not found"
)
Error Types
NOT_FOUND
Use when a requested resource doesn't exist:
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
"Product with ID 'prod_123' not found"
)
HTTP Status: 404
INVALID_DATA
Use when request data fails validation or is malformed:
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Email address is invalid"
)
HTTP Status: 400
UNAUTHORIZED
Use when authentication is required but not provided:
throw new MedusaError(
MedusaError.Types.UNAUTHORIZED,
"Authentication required to access this resource"
)
HTTP Status: 401
NOT_ALLOWED
Use when the user is authenticated but doesn't have permission:
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You don't have permission to delete this product"
)
HTTP Status: 403
CONFLICT
Use when the operation conflicts with existing data:
throw new MedusaError(
MedusaError.Types.CONFLICT,
"A product with this handle already exists"
)
HTTP Status: 409
DUPLICATE_ERROR
Use when trying to create a duplicate resource:
throw new MedusaError(
MedusaError.Types.DUPLICATE_ERROR,
"Email address is already registered"
)
HTTP Status: 422
INVALID_STATE
Use when the resource is in an invalid state for the operation:
throw new MedusaError(
MedusaError.Types.INVALID_STATE,
"Cannot cancel an order that has already been fulfilled"
)
HTTP Status: 400
Error Response Format
Medusa automatically formats errors into a consistent JSON response:
{
"type": "not_found",
"message": "Product with ID 'prod_123' not found"
}
Best Practices
1. Use Specific Error Types
Choose the most appropriate error type for the situation:
// ✅ GOOD: Uses specific error types
export async function GET(req: MedusaRequest, res: MedusaResponse) {
const { id } = req.params
const query = req.scope.resolve("query")
const { data } = await query.graph({
entity: "product",
fields: ["id", "title"],
filters: { id },
})
if (!data || data.length === 0) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Product with ID '${id}' not found`
)
}
return res.json({ product: data[0] })
}
// ❌ BAD: Uses generic error
export async function GET(req: MedusaRequest, res: MedusaResponse) {
const { id } = req.params
const query = req.scope.resolve("query")
const { data } = await query.graph({
entity: "product",
fields: ["id", "title"],
filters: { id },
})
if (!data || data.length === 0) {
throw new Error("Product not found") // Generic error
}
return res.json({ product: data[0] })
}
2. Provide Clear Error Messages
Error messages should be descriptive and help users understand what went wrong:
// ✅ GOOD: Clear, specific message
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Cannot create product: title must be at least 3 characters long"
)
// ❌ BAD: Vague message
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Invalid input"
)
3. Include Context in Error Messages
// ✅ GOOD: Includes relevant context
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Product with ID '${productId}' not found`
)
// ✅ GOOD: Includes field name
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Invalid email format: '${email}'`
)
4. Handle Workflow Errors
When calling workflows from API routes, catch and transform errors:
// ✅ GOOD: Catches and transforms workflow errors
export async function POST(req: MedusaRequest, res: MedusaResponse) {
const { data } = req.validatedBody
try {
const { result } = await myWorkflow(req.scope).run({
input: { data },
})
return res.json({ result })
} catch (error) {
// Transform workflow errors into API errors
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Failed to create resource: ${error.message}`
)
}
}
5. Use Validation Middleware
Let validation middleware handle input validation errors:
// ✅ GOOD: Middleware handles validation
// middlewares.ts
const MySchema = z.object({
email: z.string().email("Invalid email address"),
age: z.number().min(18, "Must be at least 18 years old"),
})
export const myMiddlewares: MiddlewareRoute[] = [
{
matcher: "/store/my-route",
method: "POST",
middlewares: [validateAndTransformBody(MySchema)],
},
]
// route.ts - No need to validate again
export async function POST(req: MedusaRequest, res: MedusaResponse) {
const { email, age } = req.validatedBody // Already validated
// Your logic here
}