Back to KB
Difficulty
Intermediate
Read Time
9 min

How We Reduced API Bandwidth Costs by $14k/Month and Cut P99 Latency by 62% Using Adaptive Projections

By Codcompass TeamΒ·Β·9 min read

Current Situation Analysis

At scale, standard REST API patterns break down in two predictable ways. First, you encounter Over-Fetching Tax: endpoints return massive JSON payloads because different consumers need different subsets of data. Second, you hit Chatty API Debt: clients make N+1 requests to assemble a view because the server refuses to expose composite resources.

Most tutorials teach you to solve this by creating endpoint-specific variations (/users/summary, /users/details). This creates a maintenance nightmare. You end up with 40 variations of the same resource, cache invalidation becomes impossible, and your API contract drifts until no one knows which endpoint returns which fields.

The Bad Approach: Consider a typical e-commerce product endpoint: GET /api/v1/products/123

Returns:

{
  "id": 123,
  "name": "Widget",
  "price": 9.99,
  "description": "...",
  "images": ["url1", "url2", "url3"],
  "reviews": [{"user": "A", "rating": 5, "text": "..."}],
  "inventory": {"warehouse_a": 10, "warehouse_b": 0},
  "shipping_zones": ["US", "EU"],
  "metadata": {"created_at": "...", "updated_at": "..."}
}

Size: ~4.2KB. Latency: 280ms (due to JOINs on reviews and inventory). Cost: High. The mobile app only needs id, name, price. The search index needs id, name, description. The checkout flow needs id, price, inventory. One endpoint serves all, forcing the database to compute joins and the network to transmit unused bytes.

The Pain Point: When we audited our traffic at a previous FAANG role, 68% of the bandwidth was wasted bytes. Database CPU was spiking because read replicas were computing complex aggregates for simple list views. Our Lambda costs were 3x higher than necessary because serialization/deserialization of large payloads consumed CPU cycles.

We needed a solution that gave clients the efficiency of GraphQL without the operational overhead of a GraphQL gateway, schema stitching, or complex caching strategies. We needed REST that adapts.

WOW Moment

The Paradigm Shift: Stop treating REST resources as fixed shapes. Treat them as computable views.

The Aha Moment: By injecting a lightweight, schema-validated projection engine into the API gateway layer, you allow clients to request exactly what they need while the server enforces cost limits, prevents injection, and optimizes database queries dynamically. You get GraphQL efficiency with REST simplicity, plus built-in cost control that saves cloud bills.

We call this Cost-Aware Adaptive Projections. It's not just ?fields=name. It's ?fields=name&cost_budget=5 where the server validates fields against a typed schema, calculates the computational cost of the projection, and rejects requests that exceed budgets before they hit the database.

Core Solution

Tech Stack:

  • Runtime: Node.js 22 (LTS)
  • Framework: Fastify 5.1.0
  • Database: PostgreSQL 17
  • Cache: Redis 7.4
  • Language: TypeScript 5.5

Step 1: Define the Projection Schema

Unlike GraphQL, we don't expose the full graph. We define a strict schema per resource that maps client field names to database columns and assigns a computational cost. This prevents clients from requesting expensive fields (like presigned_urls or computed_rankings) inadvertently.

File: schemas/product.schema.ts

import { Static, Type } from '@sinclair/typebox';

// Cost weights: 1 = simple column, 5 = join, 10 = external call/computation
const FieldCosts = {
  id: 1,
  name: 1,
  price: 1,
  description: 2,
  images: 5,
  reviews: 10,
  inventory: 5,
  presigned_avatar: 15, // Expensive: requires S3 signed URL generation
} as const;

export const ProductProjectionSchema = Type.Object({
  fields: Type.Array(Type.KeyOf(Type.Object(FieldCosts))),
  max_cost: Type.Number({ default: 20 }), // Default budget per request
});

export type ProductProjectionSchema = Static<typeof ProductProjectionSchema>;

export const FIELD_COSTS: Record<string, number> = FieldCosts;

Step 2: The Projection Middleware

This Fastify hook parses the fields query parameter, validates against the schema, calculates total cost, and attaches a ProjectionContext to the request. If cost exceeds budget, it returns 429 Too Many Requests immediately.

File: middleware/projection.middleware.ts

πŸŽ‰ Mid-Year Sale β€” Unlock Full Article

Base plan from just $4.99/mo or $49/yr

Sign in to read the full article and unlock all 635+ tutorials.

Sign In / Register β€” Start Free Trial

7-day free trial Β· Cancel anytime Β· 30-day money-back

Sources

  • β€’ ai-deep-generated