Building Better APIs: Lessons from a Decade of REST
Get new posts by email. No spam, just an email when I publish something new.
TL;DR
- Design APIs from the consumer's point of view, and don't let your database schema leak into them.
- Versioning strategy barely matters. Pick URL versioning (/api/v1/) and move on.
- Return errors that say what went wrong, where, and how to fix it.
- Add rate limiting from day one, with proper 429 responses and Retry-After headers.
After building and maintaining APIs for over a decade, I've seen patterns come and go. Some lasted, others were just noise. This is what I've learned.
Start with the consumer
Design your API from the outside in. Before writing any code, sketch out how a client (the app calling your API) would use it. What calls do they need to make? What data do they need back?
// Bad: exposing your internal model
GET /api/v1/user_account_records?include_deleted=false&active_status=1
// Good: consumer-focused
GET /api/users/active
This seems obvious, but we still let database schemas (how your tables are laid out) leak into API designs all the time.
Versioning: keep it simple
I've seen teams agonise over versioning strategies. URL versioning, header versioning, content negotiation: each has trade-offs. But it doesn't matter that much.
Pick URL versioning (/api/v1/) and move on. You can see it in the URL, it caches easily and everyone understands it. The time you save not debating this can go into building features.
Error responses that help
Your errors should tell developers exactly what went wrong and how to fix it:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "email",
"issue": "Must be a valid email address",
"received": "not-an-email"
}
],
"documentation": "https://api.example.com/docs/errors#validation"
}
}
Compare this to {"error": "Bad Request"}. Which would you rather debug at 2am?
Rate limiting from day one
Don't wait until you have a scaling problem. Add rate limiting early:
- It forces you to think about what each request costs you.
- It protects you from accidental abuse.
- It's much harder to add later without breaking clients.
Return proper 429 (too many requests) responses with Retry-After headers, so clients know when to try again.
What to optimise for
The best API is the one developers enjoy using. So optimise for clarity, consistency and helpful error messages. Everything else is secondary.