May 22, 2025Mansoury9 min read

API Design Principles That Frontend Teams Will Actually Thank You For

Backend engineers often optimise API design for the database layer rather than the consumers. We explore the patterns — consistent error shapes, pagination contracts, field selection — that reduce frontend boilerplate and accelerate delivery.

The relationship between backend and frontend engineers on a product team is one of the most important and most frequently strained interfaces in software development. The strain usually follows the same pattern: the backend team delivers an API that makes perfect sense given their data model and the constraints of their framework, and the frontend team discovers that using it in practice requires a significant amount of adaptation work — normalising response shapes, handling inconsistent error formats, implementing pagination logic that the API makes unnecessarily complex.

The root cause is almost always the same: API design optimised for the producing layer rather than the consuming layer. At wecopars, we have worked extensively on both sides of this boundary, and the patterns we have settled on are driven by a single principle — the API should make the frontend team's job easier, not just represent the database faithfully.

Consistent Error Shapes Across Every Endpoint

The single improvement that has the highest frontend impact with the lowest backend effort is a consistent error response format across every endpoint. A frontend team that can write a single error handler and have it work correctly for every API call has eliminated an entire category of boilerplate and a significant source of user-facing bugs.

Our standard error shape is: { error: { code: string, message: string, details?: Record<string, string[]> } }. The code field is a machine-readable identifier that the frontend can branch on programmatically. The message field is a human-readable description suitable for logging. The optional details field contains field-level validation errors for form submissions, keyed by field name.

The temptation to vary this shape for 'special cases' — returning just a string for simple errors, or a different structure for 404 responses — should be resisted firmly. Every variation in the error shape is a special case that the frontend team must handle explicitly. Consistency has compounding value: once the frontend error handling is correct, it stays correct as new endpoints are added.

Pagination Contracts That Clients Can Actually Use

Pagination is one of the areas where API design decisions have the most visible impact on frontend complexity. Cursor-based pagination (returning an opaque cursor that the client passes to get the next page) is more robust than offset-based pagination for data that changes frequently, but it requires explicit 'next page' UX — you cannot link directly to page 5.

Offset-based pagination remains appropriate for many enterprise use cases: admin tables where total count matters, search results with page numbers, data that changes infrequently. When using offset pagination, always return the total count alongside the results. A frontend rendering a paginated table needs to know how many pages there are to render the pagination UI correctly.

Regardless of which pagination style you choose, the response envelope should be consistent. We use: { data: T[], meta: { total?: number, page?: number, pageSize?: number, nextCursor?: string } }. The meta field is always present, and the frontend team knows exactly where to find pagination information regardless of which endpoint they are calling.

Field Selection and Response Shape Design

Over-fetching — returning more data than the client needs — is a performance concern that is often deprioritised relative to feature work, but it compounds over time. A response that includes 30 fields when the UI uses 8 increases payload sizes, slows down serialisation on the server, and adds latency that compounds with page complexity.

For REST APIs, the most pragmatic approach to field selection is endpoint versioning by use case rather than generic field filtering. Rather than implementing a generic fields query parameter that returns any subset of the resource, design endpoints that match the actual consumption patterns: a list endpoint that returns the fields needed for list rendering, a detail endpoint that returns the full resource, and a search endpoint optimised for the search results display.

This 'Backend for Frontend' approach produces more endpoints but dramatically simpler clients. Each endpoint has a clear contract that the frontend team can type explicitly, and the backend team can optimise each endpoint's query independently.

All articlesWritten by Mansoury