TL;DR -> Checkout this repository to see how the bad-request and unprocessable-entity responses are managed at different levels of the asp.net core application.
One of the most important aspects of a great API is the way in which it responds to its clients. How it returns the expected results and how errors are reported with proper (and unique) codes and descriptions are a couple of topics to care about while you are developing an API.
During the development of a REST API, there are mainly two types of errors that our clients care about:
- Bad request responses: We provide information about the input format. For example, if a text input was meant to be an email, we simply tell the client that.
- Unprocessable entity responses: This type of response aims to tell the client “your input has the right format, BUT somehow the server was not able to process the request”.
For example, let’s imaging we have an API with a single endpoint:
The rules to call this endpoint are:
- The query parameter userId MUST be an email.
- The query parameter accountId CAN NOT be a number.
- To start the so-called process, the user MUST be ACTIVE.
- To start the so-called process, the account MUST be OPEN.
The last couple of rules are meant to provide context about a business process. For the sake of this example, let’s say that there are two internal services that allow us to check those statements.
public interface IAccountRepository
Task<bool> IsAccountOpen(string accountId);
}public interface IUserRepository
Task<bool> IsUserActive(string userId);
As you can imagine, there is a difference in terms of cost to check the format-rules (very fast and we don’t need any extra call) and the business-rules (Their cost is high since requiring extra operations inside our system).
How we do it
Compromising between speed and user-experience, we decided to:
- All format errors are provided to the client. We follow the asp.net core 3.1 format, which is quite handy. Also, the format errors do not have a dedicated error code.
- When there is a business error, only the first occurrence is provided to the client to obtain a fast (and cheap) response.
The implementation is quite straight-forward:
- For the format rules, you need to make use of the asp.net class called “ModelState”. This class allows storing errors that would be shown to the user as the content of the bad-request response.
- For the business rules, my advice is to use a dedicated “BusinessExceptionMiddleware” that catches business exceptions. You can add it to your middleware pipeline and, at any point inside your service, launch business exceptions.
After injecting your middleware, you can launch business exceptions that will be caught by your middleware. Example:
We’ve seen our way to respond to our clients. We separate our responses into Bad-request and Unprocessable-entity. The first one provides all the errors the client did in an array as a response. The second one provides a single occurrence of the problem to avoid expensive flows and fast response.
And you? How do you do it? 😊