Error Handling

In ZSA, errors are handled using the ZSAError class. This class extends the built-in Error class and adds some additional properties to make it easier to handle errors in your server actions.

Throwing Errors

Just throw a string!

If all you care about is the error message, you can just throw a string:

throw "You are not authorized to perform this action"

This string will be accessible in the data property of the error object.

const [data, error] = await myAction()

if (error) {
  console.log(error.data) // "You are not authorized to perform this action"
  console.log(error.message) // "You are not authorized to perform this action"
  console.log(error.code) // "ERROR"
}

Throw a ZSAError

To throw an error in a server action with a code, you can simply throw a new instance of the ZSAError class:

throw new ZSAError(
  "NOT_AUTHORIZED",
  "You are not authorized to perform this action"
)

The first argument to the ZSAError constructor is the error code, and the second argument is the error data. The error data can be any value, but it is typically a string or an object.

If the error data is an instance of the built-in Error class, the ZSAError instance will inherit the message, name, and cause properties from the error data.

If the error data is a string and the message property is not set, the message property will be set to the error data.

Error Codes

ZSA defines a set of error codes that can be used to identify the type of error that occurred. These error codes are defined in the ERROR_CODES enum:

const ERROR_CODES = {
  INPUT_PARSE_ERROR: "INPUT_PARSE_ERROR",
  OUTPUT_PARSE_ERROR: "OUTPUT_PARSE_ERROR",
  ERROR: "ERROR",
  NOT_AUTHORIZED: "NOT_AUTHORIZED",
  TIMEOUT: "TIMEOUT",
  INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR",
  FORBIDDEN: "FORBIDDEN",
  NOT_FOUND: "NOT_FOUND",
  CONFLICT: "CONFLICT",
  PRECONDITION_FAILED: "PRECONDITION_FAILED",
  PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE",
  METHOD_NOT_SUPPORTED: "METHOD_NOT_SUPPORTED",
  UNPROCESSABLE_CONTENT: "UNPROCESSABLE_CONTENT",
  TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS",
  CLIENT_CLOSED_REQUEST: "CLIENT_CLOSED_REQUEST",
  INSUFFICIENT_CREDITS: "INSUFFICIENT_CREDITS",
  PAYMENT_REQUIRED: "PAYMENT_REQUIRED",
} as const

Typed Errors

ZSA also provides a typed version of the ZSAError class called TZSAError. This class is a generic type that takes a Zod schema as a type parameter. The schema represents the input schema of the server action.

If the error code is INPUT_PARSE_ERROR, the TZSAError instance will have additional properties that provide information about the validation errors that occurred:

  • fieldErrors: An object that maps field names to arrays of error messages.
  • formErrors: An array of error messages that apply to the entire form.
  • formattedErrors: A formatted version of the validation errors.

These properties are inferred from the Zod schema that is passed to the TZSAError type.

Handling Errors

When calling a server action, you can handle errors by destructuring the result and checking for an error:

const [data, error] = await myAction()

if (error) {
  if (error.code === "NOT_AUTHORIZED") {
    // Handle not authorized error
  } else if (error.code === "INPUT_PARSE_ERROR") {
    // Handle input validation errors
    console.log(error.fieldErrors)
    console.log(error.formErrors)
    console.log(error.formattedErrors)
  } else {
    // Handle other errors
  }
}

By using the error code and the additional error properties, you can handle different types of errors in different ways.

OpenAPI Error Status Codes

zsa-openapi maps ZSA error codes to HTTP status codes. This can be useful when returning errors from an API endpoint:

switch (error.code) {
  case "INTERNAL_SERVER_ERROR":
    return 500
  case "INPUT_PARSE_ERROR":
    return 400
  case "OUTPUT_PARSE_ERROR":
    return 500
  case "ERROR":
    return 500
  case "NOT_AUTHORIZED":
    return 401
  case "TIMEOUT":
    return 408
  case "FORBIDDEN":
    return 403
  case "NOT_FOUND":
    return 404
  case "CONFLICT":
    return 409
  case "PRECONDITION_FAILED":
    return 412
  case "PAYLOAD_TOO_LARGE":
    return 413
  case "METHOD_NOT_SUPPORTED":
    return 405
  case "UNPROCESSABLE_CONTENT":
    return 422
  case "TOO_MANY_REQUESTS":
    return 429
  case "CLIENT_CLOSED_REQUEST":
    return 499
  case "INSUFFICIENT_CREDITS":
  case "PAYMENT_REQUIRED":
    return 402
  default:
    return 500
}