Retour aux articles
Backend Development

Your API Is Lying to Your Frontend

10 mars 2026
6 min
Your API Is Lying to Your Frontend

Not intentionally. But every time your endpoint returns a 47-field object when the screen only needs 6, something goes wrong — silently, gradually, at scale.

The user sees a slow screen. The frontend developer sees a sluggish response. The backend developer sees nothing, because the endpoint technically works. The real problem sits in the gap between what the API can return and what the client actually needs — and most teams never close that gap deliberately.

The cost nobody accounts for

When an API returns more data than the client needs, the waste is not just bandwidth. It's a chain.

The database query fetches unnecessary columns. The ORM maps them into objects. The serializer converts those objects into JSON. The network transmits the payload. The client downloads it. The JSON parser deserializes it. The application maps it into its own data structures. The unused fields go nowhere — they were processed by every layer of the stack for no reason.

Each step in that chain has a cost: CPU, memory, time. On a single request, negligible. On a mobile device on a 3G connection making dozens of requests per session, it compounds into a measurably worse experience. On a service handling thousands of concurrent requests, it becomes an infrastructure cost.

A slow API is not always a database problem. Often it is a discipline problem — the discipline of asking "what does the client actually need?" before writing the response.

How it happens

The pattern is almost always the same. A developer builds a User model. It has everything: id, first name, last name, email, phone, avatar URL, address, billing details, preferences, creation date, last login, status, role, internal flags.

The API returns User. All of it. On every endpoint that touches a user.

The screen that displays a list of users in an admin panel needs: id, full name, avatar URL, status. Six fields. It receives twenty-five. Nineteen are downloaded, parsed, and discarded on every page load, for every user in the list, every time the page is opened.

Nobody flagged this because the endpoint works. The data is correct. The list renders. The problem is invisible until someone measures it — or until a user on a slow connection files a complaint that the app feels slow.

The fix: design your responses around the client's job

The same principle from UX applies to APIs. Start with what the client needs to accomplish, not with what the model contains.

For every endpoint, ask: What is the client doing with this response?

A list view needs identifiers, display names, status indicators — enough to render each row and navigate to the detail. It does not need nested objects, computed fields, or internal metadata.

A detail view needs more, but still not everything. Billing information has no place on a profile page. Internal timestamps have no place in a mobile response.

A search endpoint needs the minimum to identify and distinguish results. Often just an id, a name, and a thumbnail.

Design the response for that specific job. Nothing more.

// ❌ What most APIs return for a user list
{
  "id": 1,
  "first_name": "Ada",
  "last_name": "Lovelace",
  "email": "ada@example.com",
  "phone": "+1-555-0100",
  "avatar_url": "https://cdn.example.com/avatars/1.jpg",
  "address": { "street": "...", "city": "...", "country": "..." },
  "billing": { "plan": "pro", "card_last4": "4242", "next_billing_date": "..." },
  "preferences": { "notifications": true, "theme": "dark", "language": "en" },
  "role": "admin",
  "status": "active",
  "created_at": "2023-01-15T10:30:00Z",
  "last_login_at": "2024-03-08T14:22:00Z",
  "internal_flags": { "beta_tester": true, "migrated": false }
}

// ✅ What the list screen actually needs
{
  "id": 1,
  "full_name": "Ada Lovelace",
  "avatar_url": "https://cdn.example.com/avatars/1.jpg",
  "status": "active",
  "role": "admin"
}

The second response is not an incomplete version of the first. It is the correct response for that client context.

The tools: DTOs, projections, and response shaping

The implementation varies by stack, but the principle is the same everywhere: separate your internal data model from your API response model.

DTOs (Data Transfer Objects) are the cleanest way to enforce this separation. Instead of serializing your entity directly, you define a dedicated response class that contains exactly the fields the client needs. Your endpoint maps the entity to the DTO. The serializer outputs the DTO.

// The full entity — your internal representation
interface User {
  id: number;
  firstName: string;
  lastName: string;
  email: string;
  phone: string;
  avatarUrl: string;
  address: Address;
  billing: BillingInfo;
  preferences: UserPreferences;
  role: string;
  status: string;
  createdAt: Date;
  lastLoginAt: Date;
  internalFlags: Record<string, boolean>;
}

// The list DTO — what the list screen receives
interface UserListItemDto {
  id: number;
  fullName: string;
  avatarUrl: string;
  status: string;
  role: string;
}

// The detail DTO — what the profile screen receives
interface UserDetailDto {
  id: number;
  fullName: string;
  email: string;
  phone: string;
  avatarUrl: string;
  status: string;
  role: string;
  memberSince: string;
}

Database projections take this further. If you're selecting only five fields in your DTO, there's no reason to fetch all twenty-five from the database. Query only what you need:

-- ❌ Fetching everything, discarding most of it
SELECT * FROM users WHERE status = 'active';

-- ✅ Fetching only what the response needs
SELECT id, first_name, last_name, avatar_url, status, role
FROM users
WHERE status = 'active';

That query is faster. It uses less memory. It transfers less data between your database and your application. The savings compound across every request.

Nested objects deserve the same treatment

The waste in API responses is rarely just top-level fields. It's the nested objects that carry the real weight.

A post endpoint that includes the full author object — with all their profile fields — when the client only needs the author's name and avatar is a common example. An order endpoint that embeds the full product catalog entry for each line item, when the client only needs the product name and quantity, is another.

Every nested object is a multiplication. If a list returns 20 items and each item contains a nested object with 15 fields, you're transmitting 300 field values of nested data per request. If the client uses 2 of those 15 fields, 260 values were processed by every layer of the stack for nothing.

Flatten what can be flattened. Trim what can be trimmed. Embed only what the client will use.

One API, multiple clients: the case for response shaping

Mobile clients and desktop clients often have different data needs for the same resource. A mobile list screen is space-constrained — it shows less. A desktop dashboard might show more columns. An analytics export needs everything.

Rather than building separate endpoints for each client, consider response shaping: letting the client declare what it needs, and having the API deliver exactly that.

GraphQL was built around this idea. But you don't need GraphQL to implement the principle. A simple fields query parameter on REST endpoints can accomplish the same for most use cases:

GET /api/users?fields=id,full_name,avatar_url,status

The endpoint receives the field list, projects accordingly, and returns exactly what was requested. Every client gets what it needs. Nothing more.

The discipline that makes the difference

Designing API responses intentionally is not a complex technical problem. It's a discipline problem — the habit of asking "what does the client need here?" at design time instead of defaulting to "return the object."

That habit, applied consistently, produces APIs that are faster to consume, cheaper to run, and easier to evolve. The frontend is faster. The user experience is better. The infrastructure bill is lower.

All because someone, before writing the serializer, asked a simple question: does the client actually need all of this?

Tags

API Backend performance rest-api