Bad-request vs Unprocessable-entity

Icons made by Freepik, Kiranshastry, and Smashicons from Flaticon.

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.

Introduction

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:

POST /process?userId=email@gmail.com&accountId=account-id-1

The rules to call this endpoint are:

Format-rules:

  • The query parameter userId MUST be an email.
  • The query parameter accountId CAN NOT be a number.

Business-rules:

  • 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.
Request and response provided by the example service using Postman.
  • When there is a business error, only the first occurrence is provided to the client to obtain a fast (and cheap) response.
Unprocessable entity response with error code and description when user status is inactive.

Implementation

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.
How to manage input format verification with ModelState class.
  • 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.
Business exception middleware.
Injection of the BusinessExceptionMiddleware.

After injecting your middleware, you can launch business exceptions that will be caught by your middleware. Example:

Launching exceptions when business rules are not verified.

Conclusions

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? 😊

Happy coding!

Proud teacher-volunteer at Migracode and Cloud-engineer at Ohpen where I keep pushing my AWS+Serverless knowledge every day.