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 the code, suitable for UI display or logging.
  • data – the actual payload, which may be an object, an array, or null when 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:

  1. Extracts code and message.
  2. If code indicates success, forwards data to the caller.
  3. If code indicates 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

  1. Define a Result Class

    public class Result<T> {
        private int code;
        private String message;
        private T data;
        // getters, setters, constructors
    }
    
  2. 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);
    }
    
  3. 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 wrap data with another code/message pair inside it.

  • Protect Sensitive Information
    In production, the message field 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.