Generic Response Body
In a modern front‑end / back‑end split architecture, the API is the sole bridge between the user interface and the business logic.
If every endpoint returns a different shape of data, the front‑end team must write bespoke parsing code for each call. That not only slows development but also inflates maintenance costs.
A Common Response Body solves this by imposing a single, predictable envelope around every response.
A typical envelope contains three core fields:
code– a custom integer that signals the business outcome (e.g., 200 for success, 40001 for a validation error).message– a human‑readable description of thecode, suitable for UI display or logging.data– the actual payload, which may be an object, an array, ornullwhen the operation fails.
The outer JSON structure remains identical regardless of whether the request was for user details, an order list, or a form submission.
Core Design Principles
1. Decoupling Business Status from HTTP Status
RESTful APIs traditionally rely on HTTP status codes (200, 404, 500) to convey the result of a request.
However, business logic often requires finer granularity: a successful network transfer can still mean a business failure (e.g., wrong password, frozen account, expired OTP).
By embedding a custom code inside the response body, the server can communicate detailed business errors without altering the HTTP status.
The HTTP layer remains a simple indicator of transport success, while the code carries the semantic meaning.
2. Generic Typing for Type Safety
To avoid creating a new response class for every data type, the envelope is usually defined as a generic.
In statically typed languages this looks like Result<T> or ApiResponse<T>, where T can be any payload type.
At runtime the JSON is still just an object, but compile‑time guarantees that the payload matches the expected shape.
3. Unified Front‑End Interceptor
With a standardized envelope, the front‑end can install a single interceptor (Axios, Fetch, etc.) that:
- Extracts
codeandmessage. - If
codeindicates success, forwardsdatato the caller. - If
codeindicates failure, shows a global error toast and rejects the promise.
This eliminates repetitive if (res.code !== 200) checks across the codebase.
Comparative Overview of Common Schemes
| Scheme | Structural Traits | Pros | Cons | Typical Use‑Case |
|---|---|---|---|---|
| Pure HTTP | Return raw data; rely on HTTP status | REST‑compliant; minimal payload | Cannot express complex business errors | Simple public APIs, internal microservice calls |
| Full Wrapper | Every response includes code, message, data |
Strong uniformity; front‑end logic is trivial | Adds a nesting layer; slight bandwidth cost | Enterprise dashboards, complex B‑to‑B products |
| Hybrid | Success returns raw data; failure returns wrapper | Keeps success responses lightweight | Front‑end must detect response shape | Performance‑critical consumer apps with simple endpoints |
The choice depends on the trade‑off between strict uniformity and payload efficiency.
End‑to‑End Flow
Backend Implementation
Define a Result Class
public class Result<T> { private int code; private String message; private T data; // getters, setters, constructors }Provide Factory Methods
public static <T> Result<T> success(T data) { return new Result<>(200, "Success", data); } public static <T> Result<T> error(int code, String message) { return new Result<>(code, message, null); }Global Exception Handling
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<Result<Void>> handle(Exception e) { return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Result.error(500, "Internal server error")); } }
Front‑End Handling
axios.interceptors.response.use(
response => {
const { code, message, data } = response.data;
if (code === 200) {
return data; // resolve with payload
}
// Show a global error message
toast.error(message);
return Promise.reject(new Error(message));
},
error => {
// Handle network or HTTP errors
toast.error('Network error');
return Promise.reject(error);
}
);
With this interceptor, every component can simply await apiCall() and receive the payload or an error.
Best Practices
Standardize Codes
Maintain a single, versioned list of business codes. Avoid ad‑hoc numbers that can clash across teams.Avoid Deep Nesting
The envelope should be flat. Do not wrapdatawith anothercode/messagepair inside it.Protect Sensitive Information
In production, themessagefield should be user‑friendly. Never expose stack traces or internal identifiers.Document the Contract
Publish the response schema (e.g., via OpenAPI) so front‑end developers know exactly what to expect.Consider Performance
For high‑traffic, low‑latency services, evaluate whether a hybrid approach (raw success payloads) is warranted.
Conclusion
A Common Response Body is more than a coding pattern; it is a contract that aligns the expectations of both sides of the API.
By separating transport status from business status, leveraging generics for type safety, and centralizing front‑end parsing, teams can reduce boilerplate, lower bugs, and accelerate feature delivery.
Adopting this pattern early in a project sets a solid foundation for scalable, maintainable APIs.