Daniel Murillo
Index

Architecture · October 20, 2026 · Daniel Murillo

REST: how to design and optimize an API

REST does not make an API fast by itself. How to design endpoints, cache, compression, and connections before changing protocol.

REST is usually an excellent starting point for an API. Its value is not in always being the fastest protocol, but in leveraging a simple, interoperable model compatible with HTTP capabilities.

Introduction

Choosing a communication technology is not about finding a universal winner. It is about understanding what the system needs, what resources it consumes, and what complexity we are willing to maintain.

REST is a solid option for web apps, mobile, integrations, and public services because it uses familiar concepts: resources, HTTP methods, status codes, headers, and URLs. It also fits well with proxies, CDNs, observability tools, and HTTP caches.

But REST does not make an API fast by itself. An endpoint can be slow because of inefficient SQL queries, oversized responses, connections created repeatedly, or external dependencies that take long to respond. Before migrating to another technology, find the bottleneck.

1. What REST is and why it exists

REST means Representational State Transfer. It is an architectural style that organizes interaction around identifiable resources and uses HTTP semantics.

For example:

GET    /api/products/123
POST   /api/products
PATCH  /api/products/123
DELETE /api/products/123

Methods express the intent of the operation, while status codes communicate its result. This uniformity lets clients, servers, proxies, and tools share common expectations.

REST does not require every endpoint to follow exactly the same shape, nor does it require JSON, even though JSON is common. Its main practical advantage is that it rests on widely understood infrastructure.

2. Designing useful endpoints

Suppose a screen needs to show the name, price, and image of twenty products. Returning every detail of each product —audit, suppliers, history, and relationships— wastes bandwidth and processing.

An API can offer pagination and filters:

GET /api/products?category=books&limit=20
GET /api/products/123

When there is a real need, it can also allow projections:

GET /api/products?fields=id,name,price,imageUrl

Field projection is a design decision, not a REST requirement. Avoid turning it into an overly complex query language if the problem does not need it.

Pagination

For small collections, page and limit can be enough. For large or constantly changing sets, cursor pagination usually offers more stable navigation.

GET /api/orders?limit=50&cursor=...

The server must validate the cursor and set a maximum result limit. Allowing unbounded queries can cause excessive memory, CPU, and database consumption.

3. Optimizing server work

A small response does not guarantee a fast response. If the server runs one query per related record, the N+1 problem can appear.

Common measures include:

  • Reviewing database execution plans and indexes.
  • Batching queries when appropriate.
  • Avoiding redundant queries.
  • Not loading relationships the operation does not need.
  • Applying cache only when data can be reused correctly.
  • Measuring database time, business logic, and serialization separately.

If the backend spends 700 ms waiting on a database, shrinking JSON from 20 KB to 10 KB will not fix the main cause.

4. Connections and HTTP versions

Creating a new connection for every request can introduce network and TLS negotiation costs. Reusing connections and HTTP clients amortizes part of that work.

HTTP/2 adds request multiplexing over one connection and header compression. HTTP/3 uses QUIC over UDP and changes stream behavior under packet loss.

These versions can improve certain traffic patterns, but they are not an automatic optimization. Geographic latency, response size, intermediaries, and the real bottleneck still matter.

Practical recommendations:

  • Reuse HTTP clients and connection pools.
  • Configure explicit timeouts.
  • Cancel operations that are no longer needed.
  • Avoid unnecessary chains of calls between services.
  • Measure HTTP/2 and HTTP/3 on the real infrastructure before assuming one version is superior.

5. HTTP cache: an important REST advantage

Resource responses can leverage well-defined HTTP policies.

Cache-Control: public, max-age=60
ETag: "product-123-v4"
Vary: Accept-Encoding

Cache-Control expresses the storage policy. ETag identifies a version of the resource and enables conditional validation via If-None-Match. If the resource has not changed, the server can respond 304 Not Modified without resending the body.

Not every response should be public. Personal data, permission-dependent responses, and sensitive content need appropriate policies. The cache must consider authentication, content variations, and invalidation.

6. Compression without wasting CPU

Gzip and Brotli can shrink textual responses such as JSON, HTML, CSS, and JavaScript. Zstandard can be useful in compatible environments, but you should not assume every browser and proxy accepts it for HTTP.

For dynamic responses, Gzip at an intermediate level —for example, 5 or 6— is a reasonable starting point, not a universal rule. Brotli can compress certain content more, although high levels can cost more CPU.

It is worth:

  • Setting a minimum size to compress.
  • Measuring original size, compressed size, and compression time.
  • Not recompressing images, video, and other already compressed formats.
  • Checking whether the proxy or CDN already performs compression.
  • Using Vary: Accept-Encoding when appropriate.

7. Static assets and dynamic responses are not the same

Static files generated during the build can be precompressed with Gzip or Brotli. That way, the server delivers the compressed file without repeating the work on every request.

assets/
├── app.js
├── app.js.gz
└── app.js.br

The server or CDN must correctly negotiate the variant according to Accept-Encoding.

Content-hashed resources can have a long cache:

Cache-Control: public, max-age=31536000, immutable

This is appropriate if the file name changes when its content changes. For dynamic API responses, by contrast, compression usually happens during the request and its CPU cost affects latency.

8. When to choose REST

REST is usually a good option when:

  • Domain resources and operations are clear.
  • You need a public API or one consumed by different clients.
  • Interoperability matters.
  • You want to leverage HTTP, CDN, cache, and mature tooling.
  • GraphQL or gRPC complexity does not provide enough benefit.

It may not be the best tool for every problem: interfaces with highly variable data needs may benefit from GraphQL; internal communication with strict contracts may fit gRPC; and continuous bidirectional events may require WebSockets.

9. How to decide with data

Measure latency p50, p95, and p99, throughput, errors, CPU, memory, bytes transferred, and database time. Compare cold and warm cache, new and reused connections, compressed and uncompressed responses.

A fair benchmark compares the same operation, the same data, and equivalent conditions. It is not valid to compare an unoptimized REST implementation with another carefully tuned technology.

Conclusion

REST is a pragmatic starting point because it is interoperable, familiar, and leverages mature HTTP capabilities. The biggest improvement usually comes from eliminating unnecessary work, reducing network round trips, optimizing queries, and configuring cache and compression well.

Before migrating an API, measure what makes it slow. Optimize the real cause and change the communication model when there is a concrete need that justifies it.

In this series

  1. REST: how to design and optimize an API (this note)
  2. REST vs GraphQL
  3. gRPC vs REST
  4. WebSockets, SSE, and Webhooks
  5. Cache and compression
  6. Benchmarks and architecture decisions

References