Best practices for API error handling

Avatar

Error handling is a crucial part of working with APIs. When an API encounters an issue, such as invalid input data or missing resources, it can lead to errors. It is essential that these errors are correctly handled and clearly presented to the client or end-user.

The API lifecycle involves both the API’s producer and consumer. The API producer works on the server side and is responsible for API design and development. The API consumer, on the other hand, works on the client side and is responsible for integrating the API into various systems that end-users interact with. Errors can occur at any stage of this process. These errors, if not handled properly, can lead to downtime, bad user experiences, and lost money and time.

In this article, we’ll start by reviewing some best practices for handling API errors on the client and server-side—without focusing on any specific API architecture. We will then discuss architecture-specific best practices for API error handling—and explore how the Postman API Platform can help teams handle errors more efficiently.

What are some best practices for handling API errors on the server side?

There are multiple API architectural patterns, and each one has a unique approach of handling errors. That being said, all API errors need to be presented in a uniform and logical way. It’s therefore important to adhere to the following best practices for server-side error handling—regardless of your API’s architectural pattern:

  • Provide a clear and consistent structure for your error response: When an error occurs, your API error response should follow an established structure that is consistent across all requests. Additionally, your API responses should be idempotent to make the structure and response more predictable.
  • Use descriptive error messages: Error messages should be clear and descriptive. The consumer of your API should be able to understand the problem and how to fix it from reading the error messages.
  • Don’t leak sensitive data: Be careful not to leak any sensitive information in your error messages. Error messages should only include information about the problem and possible fixes.
  • Document common errors:  A clear and well-documented API should include details about the structure of its error messages. The API documentation should include possible error codes, common error messages, and remediation suggestions.
  • Implement logging and monitoring: Some errors can be difficult to debug because they are the result of a series of multiple API calls. It is therefore important to implement API monitoring and logging, which makes it easier to trace API interactions and debug errors. In some APIs, the requestId and timestamp parameters are returned as part of the error response, and this information can assist with logging and debugging.

What are some best practices for handling API errors on the client side?

Developers often run into errors during the API integration process. These errors might be caused by an incorrect implementation, a user action, or an internal server error on the API itself. It is important that developers handle these errors properly and present them to end-users in a direct, non-technical manner. The following best practices can lay the foundation for successful error handling during API integration—regardless of the API’s architectural pattern:

  • Validate user input: Users sometimes provide invalid input data, which can lead to errors. Client-side validations help prevent this issue. Validations not only ensure that the user can see and fix the problem quicker, but also help the client and server conserve resources that would otherwise be expended on extra network traffic.
  • Provide user-friendly messages: It’s important to avoid presenting error messages from the server directly to the end-user. Instead, these technical error messages should be simplified and made more user-friendly. They should also clearly tell the user how to fix the error.
  • Handle multiple edge cases: Developers should understand the full range of errors an API can produce so that they can handle every edge case. An unhandled edge case can result in a default or vague error message that might not help the user fix the issue.

Different API architectural patterns have different approaches to API error handling, and developer communities have established error handling best practices for these different architectures. In this section, we’ll explore these architecture-specific best practices for API error handling.

Error handling for REST

REST provides a simple and uniform interface for sharing resources between a client and a server. It uses standard HTTP methods to perform CRUD (CREATE, READ, UPDATE, and DELETE) operations on resources. REST APIs make use of standard HTTP status codes that indicate the outcome of the request. These status codes can immediately be used to determine if the request was successful or if an error occurred.

Here are some best practices for handling errors when working with REST APIs:

  • Use HTTP status codes properly: REST APIs rely heavily on standard HTTP status codes to communicate the nature of an error. It’s important to ensure you use the appropriate status code for each scenario.
  • Provide enough detail in error messages: REST is stateless in nature. This means that all error messages must provide the necessary information for the client to understand the error without relying on the context of previous requests.
  • Use a standardized error response format: Maintain a consistent standard for error messages. For example, most REST APIs include fields like error, message, code (an internal error code specific to your application), and sometimes details for additional information.

As an example, let’s consider the following HTTP REST API request that is supposed to fetch information about a specific user by its ID:

curl -i -X GET https://api.example.com/api/v1/users/12345

If the provided user ID was wrong and a user with the given ID does not exist in the database, a properly formatted and appropriately detailed error message would look like this:

{
  "status": "error", // This can be ‘error’ or ‘success’
  "statusCode": 404,
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "The requested resource was not found.",
    "details": "The user with the ID '12345' does not exist in our records.",
    "timestamp": "2023-12-08T12:30:45Z",
    "path": "/api/v1/users/12345",
    "suggestion": "Please check if the user ID is correct or refer to our documentation at https://api.example.com/docs/errors#RESOURCE_NOT_FOUND for more information."
  },
  "requestId": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
  "documentation_url": "https://api.example.com/docs/errors"
}

This response is effective because it:

  • Uses the status field to let you see at a glance whether the request failed or succeeded.
  • Provides a clear reason for the error, additional details, and a suggested fix, as well as a link to the documentation page for errors.
  • Has a clear, consistent, and standardized structure.

Error handling for GraphQL

GraphQL is a query language for APIs and a server-side runtime for executing queries using a type system you define for your data. It exposes a single endpoint for querying and mutating data—and allows clients to request and receive the specific data they need without chaining requests together. GraphQL uses three operations—mutations, queries, and subscriptions—for mutating, querying, and facilitating real-time communication between a client and a server.

GraphQL is transport agnostic, and while HTTP is its most commonly used transport protocol, it can also use other transport protocols such as WebSockets. This means that status codes are not used to communicate the state of a GraphQL API request, and even an error response can have a 200 OK status code if HTTP is used as the transport protocol.

The GraphQL specification clearly defines how GraphQL errors should be handled, structured, and returned in response to requests. An error in GraphQL is returned as an errors field that is an array of error objects. Each object can contain the following fields:

  • message: A string that describes the error. This field is required and provides a human-readable description of the error.
  • locations (optional): A list of locations, where each location is an object with the fields line and column. Both of these fields are positive integers. The locations field is used to indicate the location in the GraphQL document at which the error occurred.
  • path (optional): A list of path segments, where each is either a field name or an integer. This field helps identify which part of the query is responsible for the error.
  • extensions (optional): The server can use this field to add additional information related to the error. It is an object, and it often includes error codes or other context-specific details.

Some best practices for handling errors when working with GraphQL APIs include:

  • Handle field-level errors: GraphQL can return data and errors in the same response, so it’s important to handle scenarios where you can return data along with errors for parts of the query that failed. See the example below.
  • Carefully implement retry logic: When it comes to retrying requests in response to transient errors, it’s important to treat queries and mutations differently. For example, a failed query might trigger a retry, while a failed mutation might not—at least not without strict validation.
  • Use error extensions: GraphQL extensions allow you to include additional details in your error responses that the GraphQL specification does not inherently provide. For instance, error extensions can be used to provide details like error codes, timestamps, or a documentation url.

Let’s consider the following GraphQL query to the https://api.example.com/graphql endpoint, which fetches information about a user with the user’s ID:

query {
  user(id: "12345") {
    id
    name
    email
  }
}

If the provided user ID was wrong and a user with the given ID does not exist in the database, a properly formatted and appropriately detailed error message would look like this:

{
  "data": {
    "user": null
  },
  "errors": [
    {
      "message": "User not found",
      "locations": [{"line": 1, "column": 8}],
      "path": ["user"],
      "extensions": {
        "code": "USER_NOT_FOUND",
        "timestamp": "2023-12-08T12:30:45Z",
        "details": "The user with the ID '12345' does not exist in our records.",
        "documentation_url": "https://api.example.com/docs/errors#USER_NOT_FOUND"
      }
    }
  ]
}

This error is effective because it:

  • Complies with the GraphQL specification. It returns errors in an array, specifies information about the error location and path, and provides a short but descriptive error message.
  • Uses extensions to provide additional information about the error. This includes a custom error code (which is helpful for clients integrating this API), a timestamp, a more detailed description of the error, and a link to documentation where you can learn more about similar errors.
  • Includes a data field with null for the user field, which indicates no data could be retrieved.

If you’d like to learn more about error handling in GraphQL, check out this article in our Learning Center.

Error handling for gRPC

gRPC is a schema-driven framework that facilitates service-to-service communication in distributed environments. It is a language-agnostic implementation of the RPC (Remote Procedure Call) protocol that supports streaming and strongly typed service contracts using HTTP/2 and Protocol Buffers (Protobuf).

gRPC uses its own set of status codes to represent various error states in a gRPC call. These status codes can be used in combination with an optional error message that provides additional information about the error. However, the use of both is still quite limited and does not provide the ability to include enough information about an error. The official gRPC documentation says that people who use Protobuf should leverage the richer error model developed by Google, but this approach has its own limitations. For instance, implementations of this error model can vary from language to language, and it isn’t fully supported in all languages.

Here are some recommended best practices for working with errors in gRPC:

  • Use appropriate status codes: gRPC provides its own standard status codes (like OK, INVALID_ARGUMENT, NOT_FOUND, INTERNAL, and UNAVAILABLE). Ensure you return a status code that adequately describes the nature of the error. For example, use INVALID_ARGUMENT for client-side parameter errors and UNAVAILABLE for network errors.
  • Use metadata for additional context: While the official documentation recommends using Google’s error model, it’s also important to leverage gRPC’s custom metadata feature to send additional information about errors. This can include application-specific error codes or contextual details that help with debugging. Additionally, you should ensure the metadata has a consistent and well-structured format.
  • Implement robust error handling on the client side: The client side should always account for all gRPC standard error codes. For instance, it’s important to implement retry logic for errors that are likely transient, such as UNAVAILABLE. However, retries should be context-sensitive and used cautiously, especially for non-idempotent operations.

How can Postman help you handle API errors more efficiently?

The Postman API Platform includes many features that can help API producers and consumers handle errors more efficiently. With Postman, you can:

  • Use scripts to validate API responses: Postman scripts allow you to write Mocha and Chai-like tests that validate your API responses against specific assertions. This makes it easier to catch client-side errors while testing APIs in Postman.
  • Check for errors at scheduled intervals: Postman Monitors automatically run your Postman collections at scheduled intervals—and notify you when any of your test assertions fail. Monitors also provide additional information about your requests, including status codes, timestamps, latency, failure rates, log statements, and more.
  • Save error response examples: Postman enables users to save examples of different error responses or scenarios. These examples help developers know what responses to expect for specific request scenarios. They can also be used to create mock servers, and they can be included in published collection documentation.

What do you think about this topic? Tell us in a comment below.

Comment

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.