Walid Chennit

The shape of a great REST API

Naming, versioning, errors, idempotency. A field guide pulled from four production NestJS backends.

May 30, 2025·9 min read·REST · Developer Experience

Naming is a promise

Across RDIS, e-KYC, Meeting Board and Diar Dzair's backend surfaces, the APIs that aged well all shared one trait: resource names that describe a noun, not an action. /purchase-requests/:id/approve beats /approvePurchaseRequest?id=, not for taste but because it composes — the same resource naturally supports /purchase-requests/:id/reject, /purchase-requests/:id/history, without inventing a new verb-shaped endpoint every time a new operation shows up.

Errors are part of the API, not an afterthought

A REST API's error shape is as much a contract as its success shape, and it's the part teams design last and regret first. We standardized on one envelope everywhere — a machine-readable error code, a human message, and a field-level detail array for validation failures — so a frontend can branch on error.code instead of string-matching a message meant for a human. That single decision, applied consistently, killed an entire category of 'why is the error toast blank' bugs.

Idempotency is not optional past a certain point

Any endpoint that changes money, state that triggers a notification, or anything a mobile client might retry on a flaky connection needs to be safe to call twice. We support this with a client-supplied idempotency key on mutating endpoints, checked against a short-lived store before the mutation runs. It's a small addition to the request shape that removes an entire class of production incidents around double-submission.

Versioning: plan the exit before you need it

We version at the URL (/v1/...) from day one, even for the very first release, because retrofitting a version scheme onto a live API with real clients is far more painful than reserving the URL segment when nobody's using it yet. It costs nothing early and buys freedom later.