REST API Design: Lessons I Learned the Hard Way

Let me start with a confession.

The first “REST API” I designed wasn’t RESTful. It wasn’t elegant. It wasn’t scalable.

It worked.

Until it didn’t.

If you’ve ever shipped an API that started clean and slowly turned into a jungle of endpoints like this, you know what I mean:

/createUser
/deleteUser
/updateUserDetails
/getUserInfoByEmail

That is what I call API entropy.

This is not a theoretical explanation of REST. These are practical lessons learned while building APIs in real systems, from enterprise platforms to customer-facing cloud services where mistakes are expensive.


The Moment I Realised JSON over HTTP Isn’t REST

Early in my career, I thought REST meant three things:

  • HTTP
  • JSON
  • Done

Then I actually read Roy Fielding’s original work.

REST is not about JSON. It is an architectural style built on constraints that give you important system properties.

ConstraintWhat You Get
StatelessnessHorizontal scalability
Uniform interfaceEvolvability
CacheabilityLower cost and better performance
Layered systemSecurity and abstraction boundaries

When you ignore these constraints, your API still works. Over time, it becomes fragile.

The Day Verbs Betrayed Me

I once inherited an API that looked like this:

POST /approveVendor
POST /rejectVendor
POST /validateVendor

It felt expressive. It quickly became difficult to maintain.

Every new workflow required a new verb. Over time, the surface area grew unnecessarily.

We redesigned it by modelling resources instead of actions:

POST /vendors
GET /vendors/{id}
PATCH /vendors/{id}

Approval became state instead of a dedicated endpoint:

PATCH /vendors/{id}
{
"status": "APPROVED"
}

Once you think in nouns, the system becomes predictable. Predictability makes scaling teams and integrations much easier.

Idempotency Is Not Academic

We once had a simple endpoint:

POST /orders

Under load, clients retried requests. Sometimes the network dropped after submission.

The result was duplicate orders.

Nothing clarifies idempotency faster than explaining to finance why someone was charged twice.

HTTP semantics matter.

  • PUT is idempotent.
  • POST is not.
  • Retries will happen whether you plan for them or not.

In distributed systems, idempotency is a resilience feature.

Pagination and the Cost of Growth

An endpoint worked perfectly returning a few hundred records.

Then a large customer onboarded and suddenly there were hundreds of thousands of rows.

Response times increased. Memory pressure increased. Database scans became painful.

We started with offset pagination:

GET /users?page=400&limit=50

At high offsets, performance degraded significantly.

We moved to cursor-based pagination:

GET /users?cursor=eyJpZCI6MTIzfQ==

It required more careful implementation but scaled much better.

Status Codes Are Operational Signals

Returning 200 OK for every response might seem harmless.

200 OK
{
"error": "Invalid email"
}

Monitoring systems rely on status codes to detect problems. If everything is 200, dashboards become misleading.

Correct usage matters:

  • 201 Created for successful creation
  • 400 for validation issues
  • 404 when a resource does not exist
  • 409 for conflicts

Versioning Is Inevitable

Every API begins as version one.

Eventually requirements change, and the original contract no longer fits.

In practice, URL versioning has been the simplest to manage:

/v1/users
/v2/users

It is straightforward to route, document, and deprecate.

Caching Is Often Ignored

In one system, the majority of requests were read operations. We were not caching.

After implementing proper headers:

Cache-Control
ETag
If-None-Match

Database load dropped noticeably and latency improved.

REST explicitly supports caching. It is one of the simplest performance improvements available.

Design Errors for Humans and Machines

{
"error": "Something went wrong"
}

This tells neither the developer nor the system anything useful.

{
"code": "USER_ALREADY_EXISTS",
"message": "A user with this email already exists.",
"correlationId": "abc-123"
}

Structured errors make debugging and monitoring significantly easier.

Security Is Part of the Design

Every API is an attack surface.

Use proper authentication standards, scope tokens carefully, expire them appropriately, and log access. Security decisions should be made at design time.


What I Eventually Realised

Good API design is less about cleverness and more about discipline.

When done well, an API becomes predictable, observable, evolvable, and stable.

The best APIs feel almost invisible. They do not surprise you. They scale with your organisation.

Most of these lessons are learned by cleaning up earlier mistakes. That is part of the process.

Leave a Reply

Scroll to Top

Discover more from Cloudopian

Subscribe now to keep reading and get access to the full archive.

Continue reading