API Design Frontend Teams Don't Hate
Practical HTTP API shapes, naming, pagination, and versioning choices that keep web clients productive without endless coordination meetings.
Frontend engineers do not hate APIs because JSON is hard. They hate guessing: ambiguous errors, inconsistent field names, surprise breaking changes, and list endpoints that return mystery blobs. Backend teams that treat the web app as the first API consumer ship contracts people actually want to use—and spend less time in Slack decoding responses.
This post is for full-stack and backend developers building HTTP APIs consumed by React or Next.js clients. It complements designing APIs with types that survive, ties validation at the boundary to Zod and runtime truth at the edges, and pairs with error contracts when things go wrong.
Design for the client workflow, not the database
Your users table has forty columns; the settings screen needs six. List screens need fewer still. Resist exposing the ORM model as the public JSON shape. Frontend teams build screens—give them list DTOs and detail DTOs with stable names that match UI copy, not internal abbreviations.
When a screen loads, count the round trips. If the product needs organization name beside every member row, either embed a minimal organization: { id, name } on the list item or document why a second request is intentional. Hidden N+1 patterns in the API become visible N+1 waterfalls in the browser; MDN documents the Fetch API well, but no amount of fetch polish fixes chatty endpoints.
Naming and casing at one boundary
Pick one JSON convention—typically camelCase for TypeScript-heavy teams—and transform at the server edge. Mixed user_id and userId in the same API forces mappers in every client. If you integrate with a snake_case upstream, convert once in the adapter layer and never leak both spellings.
Boolean fields should read affirmatively in UI: isActive beats disabled inverted in the client. Timestamps as ISO 8601 strings in UTC are boring and correct; document that rule in your API guide so date libraries on the frontend do not guess.
Pagination clients can implement once
Offset pagination (?page=3&limit=20) is easy to abuse on large tables but simple for admin UIs. Cursor pagination scales better for feeds; type the cursor as an opaque string and document sort order. Whatever you choose, return a consistent envelope:
{
"items": [],
"nextCursor": null,
"hasMore": false
}
Avoid data wrappers with no metadata—frontend code ends up special-casing every resource. If you filter, accept explicit query params (status=active) instead of a generic filter JSON string unless power users truly need it. Parse query strings in one place; see TypeScript for full-stack teams that ship for sharing parsers between Route Handlers and client helpers.
Errors humans and code can read
Return machine-readable code fields and stable HTTP status semantics. 401 means unauthenticated; 403 means authenticated but not allowed—do not swap them because middleware defaulted wrong. Human message strings are for logs and toast copy; clients should branch on code. Deeper patterns live in the dedicated error-contract post; the frontend rule is simple: never includes("not found") on English prose.
Versioning without surprising Friday deploys
Prefer additive changes: new optional fields, new endpoints, new enum values clients ignore. When you must break, use URL prefixes (/v2/) or explicit Accept-Version headers and announce deprecation windows. Coordinate with mobile or partner consumers who cannot deploy hourly. Gradual typing strategies apply to JSON evolution—run dual readers during migrations.
Auth headers and CORS that match reality
Browser clients send cookies or Authorization headers depending on your session model; align with auth session patterns for SaaS. Configure CORS deliberately for known web origins—wildcard credentialed access is a footgun. Preflight failures show up as opaque browser errors; document required headers for internal SPAs.
Idempotency for writes users repeat
Double-clicks on “Save” or “Pay” should not double-charge. Accept Idempotency-Key on POST endpoints with side effects; store keys with response snapshots for a TTL. Stripe’s idempotent requests documentation is a solid reference for semantics even if you are not using their API.
Documentation that matches production
OpenAPI or schema-generated docs beat wiki tables. Include worked examples for pagination, error cases, and auth. Link OWASP API Security Top 10 considerations when exposing public endpoints—rate limits, object-level authorization, and mass assignment matter to frontend teams when IDs are guessable in URLs.
Review checklist for frontend happiness
- Can a new engineer build a list + detail screen without asking what a field means?
- Are list and detail shapes intentionally different?
- Do errors use stable codes?
- Is pagination consistent across resources?
- Did we avoid breaking renames without a version bump?
Bulk operations and partial success
Admin UIs often need “select many, apply one action.” Design bulk endpoints with explicit id lists and per-item results rather than failing the entire batch on one bad row. Return 200 with a body that lists successes and failures, or use 207 Multi-Status only if your clients already handle it—consistency beats HTTP pedantry if the frontend team has one parser.
Document maximum batch sizes so the client can chunk requests and show progress. Timeouts on large batches should be predictable; prefer accepting the job and polling status via background jobs you can trust when work exceeds a few seconds.
File uploads and content types
Multipart uploads need documented size limits, allowed MIME types, and whether the API returns a direct URL or an id for a follow-up attach call. Clients need to know when to use FormData versus JSON with a signed upload URL. Mismatched expectations here generate the worst kind of bug report: “it works in Postman.”
Real-time and optimistic UI hooks
If the product uses optimistic updates, responses should echo server-assigned ids and timestamps so the client can reconcile. ETag or updatedAt fields help invalidate caches consistently with caching and revalidation on the Next.js side. Event streams or websockets belong in the contract doc too—frontend teams should not discover them from a side channel.
OpenAPI and codegen in the workflow
Publish OpenAPI from Zod or hand-maintained specs in CI. Frontend teams generate hooks or types and catch drift in pull requests instead of during QA. When codegen is too heavy, a shared internal package with manually curated types still beats ad hoc interfaces in every feature branch. Document which resources are stable versus experimental so generated clients can flag beta routes.
Rate limits as part of the contract
Document per-key and per-IP limits in the same doc as pagination. Return 429 with Retry-After when limits hit so client backoff is deterministic. Sudden throttling without headers forces frontend engineers to add random sleeps—bad for users and impossible to test.
Closing
APIs frontend teams do not hate are predictable: lean shapes for each screen, one casing convention, honest pagination, stable errors, and versioning that respects deploy cadence. Invest in shared types and schema checks so the compiler and CI nag you before the web app does. The best API review question is still: “Could I build this screen without a meeting?” When in doubt, prototype the worst list screen against your API in a spike branch before locking the schema—cheap discovery beats expensive rewrites. Schedule a quarterly “frontend pain” review where web leads flag the three slowest or most confusing endpoints; that backlog pays down faster than abstract API governance meetings. Keep a short changelog of breaking HTTP changes linked from your developer portal so integrators know what shipped.