← Back to list

The Art of the Extensible Response: why your GETs should never return an array

full post: https://juanfraherrero.substack.com/p/the-art-of-the-extensible-response

Juan Francisco Herrero · 2026-05-18 17:16 · 0 claps · 3.0 min read
#api-design #software-architecture #typescript
Open on Medium ↗
Wiki topics: 🌐 · Web Development 🎙️ · Creator Economy 🏛️ · Architecture

The Art of the Extensible Response: why your GETs should never return an array

full post: https://juanfraherrero.substack.com/p/the-art-of-the-extensible-response

The incident: the Friday I broke the interface

It was 5:30 PM on a Friday. We were rolling out a minor enhancement to the admin dashboard. My task was simple: add the totalCount field to the users list response so the frontend could handle pagination.

Since the GET /users endpoint returned a flat array —because the team’s “minimalism” mandated it— I simply changed the response to an object containing the array and the new counter. Catastrophic mistake. Within ten minutes, the production frontend collapsed. Hundreds of React components were trying to .map() over an object that was no longer an array. I had broken the API contract. That day I learned that the elegance of a flat array is, in reality, a shortsighted trap that condemns you to constant breaking changes.

Envelope image for visual support

Envelope image for visual support

The problem: the false sense of the “clean API”

Plenty of Junior or Mid-level developers defend the idea of returning raw arrays because “it’s cleaner” or “it uses fewer bytes.” That’s a tutorial-grade solution that doesn’t survive first contact with a complex system.

When you return a root entity directly (especially an array), you lose sovereignty over your contract. You have nowhere to put metadata, nowhere to add contextual information, and worst of all, you force the frontend into defensive coding. A senior architect knows software is a living thing; if your interface has no room to grow, you’re designing a system with an expiration date.

Technical deep dive: the wrapping pattern and granularity

To build a robust system, we have to distinguish the endpoint’s intent and protect the response with a container object.

1. The detail (getById): generosity by default

In a getById, the network cost is usually negligible compared to the ease of development. The rule here is to return the entire entity. If the frontend needs an extra piece of data tomorrow, it already has it. Don’t force the UI team to ask you for a backend change for a field that’s already in the DB. (Remember to redact sensitive information).

2. The list (getAll): projection and efficiency

Unlike the detail, when you return n items, efficiency rules. Here we use projection DTOs to return only what the list actually needs (e.g., ID, name, and date), avoiding server over-processing and excess transfer.

3. The golden rule: the “Data” field

Every response, whether it carries a single item or a thousand, must be wrapped in an object. Always.

// THE SOLUTION: The contract that never breaks
export class APIResponse<T> {
  // The 'data' field encapsulates the entity or the array.
  // This lets us add 'metadata' or 'included' without breaking client mapping.
  data: T;
// Room for the future: pagination, HATEOAS links, alerts, etc.
  meta?: {
    count?: number;
    took?: number;
    apiVersion: string;
  };
}
// Example usage in a NestJS Controller
@Get(':id')
async findOne(@Param('id') id: string): Promise<APIResponse<User>> {
  const user = await this.userService.findById(id);
  return {
    data: user, // Full entity for frontend convenience
    meta: { apiVersion: 'v1.2' }
  };
}

Trade-offs

  • Verbosity: Yes, the JSON is a bit longer. In return, you get forward compatibility.
  • Cognitive load: The developer has to remember to always use the .data field. A tiny price compared to coordinating an emergency deploy because you changed a data type.

Impact analysis: flexibility vs. rigidity

Scenario                    | Array/Naked Response                      | Wrapped (Fielded) Response
----------------------------|-------------------------------------------|------------------------------------------
Adding pagination           | Breaking Change (you break the frontend)  | Non-Breaking (you add it under meta)
Including related entities  | Impossible without polluting the model    | Trivial (add an included field)
Debugging ease              | Hard (no API context)                     | High (context lives in the wrapper)
System growth               | Rigid and error-prone                     | Resilient and scalable

Closing reflection: the maturity of the envelope

Imagine sending a letter to a friend. You don’t stick the stamp directly onto the sheet of paper and drop it into the mailbox. You put it inside an envelope. The envelope protects the contents, but it also lets you scribble a quick note on the outside or slip in a small extra gift without warping the letter itself.

In software architecture, our JSON responses are those letters. Designing the “envelope” today gives you the technical leadership of knowing that, when product asks for a new feature tomorrow, your system will simply add a line of code rather than rebuild the entire building.


메타데이터
post_id
73040b5e6130
slug
the-art-of-the-extensible-response-why-your-gets-should-never-return-an-array-73040b5e6130
url
https://medium.com/@juanfraherrero/the-art-of-the-extensible-response-why-your-gets-should-never-return-an-array-73040b5e6130
canonical_url
https://medium.com/@juanfraherrero/the-art-of-the-extensible-response-why-your-gets-should-never-return-an-array-73040b5e6130
author_url
https://medium.com/@juanfraherrero
status
ok
fetched_at
2026-06-09 15:37:30