Generic error messages suck.
As a user, we encounter them all the time. “An error occurred, please try again later“. Often just to find out the problem wasn't some server outage, but something on our side. A typo in our email - and the server couldn't send that verification email, resulting in a 500 internal server error.
Yes, there are myriads of edge-cases if you develop anything for the internet. But there's an optimum somewhere between "if something's wrong we'll just send a 500" and over-analyzing every edge-case.
Here's a simple but effective way to API error design.
Wikipedia lists 60+ official and 40+ unofficial HTTP status codes.
How do you know when to use which?
I'll just focus on what's important for developing web-facing REST APIs. Other API types may need more or other status codes.
There are four relevant categories of status codes:
For API design, you'll likely not use 3xx status codes. And if you are, I'm sure you know what you're doing.
Here are the 6 HTTP status codes you'll use most often:
2xx
status codes you may be using are 201
to indicate a resource has been created, or 204
when you intentionally return no content. But most clients will gladly accept any 2xx
status code, so you can also just stick to 200
.502 Bad Gateway
, but web frontends usually don't care what exactly the error was. 500
is fine.Validate forms in your frontend. It's so much nicer to the user and your backend. The user gets helpful error messages for their inputs immediately, and the backend doesn't get unnecessary requests. Check e.g. that phone numbers contain numbers, and email addresses look like email addresses.
Validate all incoming requests in your backend. Validation in this context means to check if the request has the right shape. Does it contain all necessary properties? Are string lengths ok? Is that email formatted like an email? Check everything you can check synchronously. The format of this response doesn't really matter, as long as its descriptive and explains the error. Validation on backend-side should never be relevant in production as the frontend also validates stuff. It's just relevant to protect the backend and as a support for the frontend developers integrating against the API.
Use 401, 403, and 404 appropriately. Always tell the user if they're accessing a non-existing resource, or if there are unauthorized or unauthenticated.
Use 400 with custom error codes in the response to foreseeable request errors. These errors will occur while processing a request. E.g. if your API looks up geo coordinates for an address, you can return a 400 Bad Request
error with a JSON body of { "error": "Bad Request", "statusCode": 400, "message": [{ "code": "GEOCOORDINATES_NOT_FOUND", "description": "we couldn't find geo coordinates for the given address" }]
. Using an array makes sure you can return all relevant input errors for one request.
Everything else is unforeseeable. For that just make sure your backend logs such things properly so you can fix them. Also make sure the backend doesn't return the stack trace, just a generic 500 internal server error, and that the frontend informs the user of a technical error.
This approach improves backend security, frontend developer experience, and most important the user experience for your service.
Take 20 minutes and go through the most important input errors a user can make that you can't detect in your frontend.
Then make sure you are returning a proper (custom) error code for this case.
Then add frontend code to handle these (custom) error codes and to inform the user.