Many issues in web applications do not arise from overly complex features, but rather from a lack of clear agreements between the frontend and backend. The backend sends varying responses, the frontend guesses the meaning of each status, and minor errors turn into hours of debugging.
An API or Application Programming Interface is a communication pathway between parts of an application. In modern websites, APIs typically send and receive data via HTTP in formats like JSON. However, a good API must do more than just return data. It should also have rules that are easy to understand, consistent, and can help developers identify issues.
APIs Need a Clear “Contract”
Imagine an API as a service counter. Users need to know what documents to bring, the format of requests accepted, and the form of responses that will be given. If each staff member provides different rules, the process will be confusing.
An API contract is an agreement on several important aspects, including:
- Available endpoints, for example,
GET /api/products. - Required parameters or data to be sent.
- Response format when the request is successful.
- HTTP status used.
- Error message format when an error occurs.
- Authentication rules and access limitations.
This contract does not have to be written in a lengthy document. Even simple examples of requests and responses in project documentation are far better than having no rules at all.
Use HTTP Status According to Its Meaning
HTTP status helps the client understand the result of a request without having to read the entire response. The problem is, some APIs use the status 200 OK for almost all conditions, including when data is not found or validation fails.
A more maintainable pattern is to use statuses according to context:
200 OKfor successful requests that generate a response.201 Createdwhen a new resource is successfully created.204 No Contentwhen the operation is successful but there is no content to send.400 Bad Requestwhen the request format is invalid.401 Unauthorizedwhen the user is not authenticated.403 Forbiddenwhen the user is recognized but does not have permission.404 Not Foundwhen the requested resource is not available.422 Unprocessable Contentwhen the data format is correct, but the content fails validation.500 Internal Server Errorfor unexpected failures on the server side.
HTTP status is not a substitute for error messages, but it serves as a very useful initial signal. The frontend can distinguish when to display validation forms, prompt users to log in again, or show server disruption messages.
Standardize Successful and Error Response Formats
Consistent responses reduce specific logic on the frontend. For example, a successful response always has a data object, while errors always have an error with a clear code and message.
{
"data": {
"id": 42,
"name": "Wireless Keyboard"
},
"meta": {}
}For errors, use a structure that is machine-processable yet still easy for humans to read:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The submitted data is not valid.",
"details": {
"email": "Email format is incorrect."
},
"request_id": "req_8f21ab"
}
}The code field should be stable and not dependent on phrases that may change. The frontend can use VALIDATION_ERROR to determine behavior, while the message is displayed to the user. If an error occurs in production, the request_id helps the team find the same occurrence in the server logs.
Differentiate Messages for Users and Details for Developers
Overly technical error messages do not help users. Conversely, overly generic messages make it difficult for developers to identify the cause.
For example, users do not need to see messages like SQLSTATE[23000]: Integrity constraint violation. This message can be translated to “The product code has already been used.” The technical details should still be logged on the server, not sent raw to the browser.
Avoid sending:
- Database connection information.
- File paths on the server.
- Complete SQL queries.
- Stack traces in production environments.
- Tokens, passwords, or personal data.
This is not just about convenience. Leaked internal details can help unauthorized parties understand the application structure and look for security vulnerabilities.
Validation Should Be Done in More Than One Place
The frontend can perform validation to provide quick feedback, but the backend must still validate again. Data from the browser should not be trusted because requests can be made using other tools, not just through the website interface.
For example, the frontend checks that the price cannot be negative. The backend must still perform the same check before saving data to the database. For critical fields, validation should also be reinforced with database rules, such as unique constraints or foreign keys.
This layered approach does add a bit of work, but it prevents data corruption when the API is called by mobile applications, automated scripts, or third-party integrations.
Add Request IDs and Targeted Logging
When users report “there was an error earlier,” the team usually lacks information. The time of occurrence may be unclear, the endpoint called is unknown, and the error message may have already disappeared.
A request ID helps link the API response with records on the server. Each request can be assigned a unique ID, which is then sent back in the response and stored in the logs along with important information such as:
- Request time.
- Endpoint and HTTP method.
- Response status.
- Processing duration.
- User ID if available and safe to log.
- Error summary without sensitive data.
Good logging does not mean storing everything. Personal data and credentials should be masked or not logged at all.
Test APIs with Real Scenarios
API testing should not stop at one successful example. At a minimum, prepare scenarios for empty data, missing parameters, failed authentication, unauthorized access, resource not found, overly long input, and external service failures.
Here’s a simple checklist:
- Does the HTTP status match the actual condition?
- Is the response format consistent?
- Are errors understandable by the frontend?
- Is sensitive data not included in the response?
- Does the request ID appear in the response and logs?
- Is the API secure when called without going through the UI?
What Does This Mean for Us?
A well-structured API does not mean all endpoints must be perfect from day one. What’s more important is having a repeatable pattern. Start with one response format, a list of agreed HTTP statuses, backend validation, and error messages that do not leak internal details.
After that, document examples of requests and responses. If the team grows or the application starts connecting with other services, this contract will save communication time and reduce the risk of small changes breaking many parts.
Ultimately, the quality of an API is evident not only when everything runs smoothly but also when errors occur. A good API informs what happened, does not leak what should remain confidential, and helps the right people fix issues quickly.
– Rio Yotto @rioyotto
