Convert all poet attribution lines to use an em dash, English name format, and consistent spacing. Re-indent the blog single page template for readability and adjust the typewriter element layout.
77 lines
2.3 KiB
Markdown
77 lines
2.3 KiB
Markdown
---
|
||
title: "REST API Design: Mistakes I've Made So You Don't Have To"
|
||
date: 2026-05-13T09:00:00+08:00
|
||
draft: false
|
||
poet: |
|
||
Form follows function.
|
||
— Louis Sullivan, US, 1856–1924
|
||
description: "Lessons from real-world API design. Why version early, paginate everything, standardize error responses, and add idempotency keys. Plus a practical deprecation strategy with Sunset headers."
|
||
tags:
|
||
- API
|
||
- REST
|
||
- Backend
|
||
---
|
||
|
||
I've designed, built, and maintained enough REST APIs to have made most of the classic mistakes. Here's what I wish someone had told me upfront.
|
||
|
||
## 1. Versioning Early
|
||
|
||
Don't wait until you need it. Version your API from day one, either in the URL (`/v1/users`) or via the `Accept` header. Retrofitting versioning onto an unversioned API is painful for everyone involved.
|
||
|
||
My preference: **URL-based versioning**. It's visible, impossible to forget, and trivially cacheable.
|
||
|
||
## 2. Pagination by Default
|
||
|
||
Every list endpoint should be paginated. Every. Single. One.
|
||
|
||
```json
|
||
{
|
||
"data": [...],
|
||
"pagination": {
|
||
"cursor": "eyJpZCI6MTIzfQ==",
|
||
"has_more": true,
|
||
"total": 847
|
||
}
|
||
}
|
||
```
|
||
|
||
I've moved from offset-based to cursor-based pagination almost everywhere. Cursors handle real-time data changes gracefully and perform better on large datasets.
|
||
|
||
## 3. Error Responses Should Be Consistent
|
||
|
||
A predictable error shape means less client-side code:
|
||
|
||
```json
|
||
{
|
||
"error": {
|
||
"code": "INSUFFICIENT_BALANCE",
|
||
"message": "Account balance too low for this transaction",
|
||
"details": {
|
||
"required": 29.99,
|
||
"available": 12.50
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Never expose stack traces in production. Never leak internal IDs without intent.
|
||
|
||
## 4. Idempotency Keys
|
||
|
||
For any mutating endpoint, support an `Idempotency-Key` header. Your payment team will thank you, and so will your users when a retry doesn't double-charge them.
|
||
|
||
## 5. Think in Terms of Deprecation
|
||
|
||
Add a `Deprecation` and `Sunset` header to old endpoints:
|
||
|
||
```
|
||
Deprecation: true
|
||
Sunset: Sat, 01 Aug 2026 00:00:00 GMT
|
||
```
|
||
|
||
Give consumers time to migrate, then actually follow through on removal. Keeping deprecated endpoints around "just in case" is technical debt with interest.
|
||
|
||
---
|
||
|
||
Good API design is mostly about **empathy for the consumer**. Ask yourself: would *you* enjoy integrating with this?
|