How to desgin REST APIs that actually works
When your team sets out to build an API, it’s tempting to jump straight into writing code. But most APIs fail not because they lack fancy technology, but because they are confusing, inconsistent, or hard to maintain.
In this post, I’ll walk you through a practical approach to designing a REST API that actually works—clear to understand, easy to evolve, and a delight for developers to consume.
Let’s imagine we’re building an online bookstore API. I’ll use this example to illustrate key principles step by step.
Start With Clear Goals
Before you even name your endpoints, step back and clarify:
-
What resources do you need?
- Books
- Authors
- Orders
- Users
-
Who will consume this API?
- Mobile apps
- Web dashboards
- Third-party partners
-
What are the core workflows?
- Browsing books
- Placing orders
- Managing inventory
Taking the time to define this will save you costly redesigns later.
Use Consistent Naming
A predictable naming scheme makes your API easier to adopt:
Use nouns, not verbs
Keep names plural
Keep paths tidy
Good examples:
GET /books
POST /orders
GET /users/{id}
Problematic examples:
POST /createBook
GET /getOrders
This small detail pays dividends in clarity.
Use HTTP Methods Properly
Every operation should match an established HTTP verb:
- GET – Retrieve data
- POST – Create new records
- PATCH – Update records partially
- DELETE – Remove records
For example:
POST /books
creates a book, while:
retrieves details of one.
Authentication Matters
Always secure your API:
- Require an API Key in headers:
- Enforce HTTPS to protect sensitive data.
The Bookstore API Example
Here’s a practical sketch of what your endpoints might look like:
List Books
Query Parameters:
author_id
search
sort
(e.g.,-published_at
)page
limit
Response:
{
"data": [
{
"id": "b1",
"title": "Clean Code",
"author_id": "a1",
"published_at": "2008-08-01"
}
],
"meta": {
"total": 120,
"page": 1,
"limit": 20
}
}
Create Books
POST /books
Request
{
"title": "Domain-Driven Design",
"author_id": "a2",
"published_at": "2003-08-30",
"price": 45.0
}
Response
201 Created
{
"data": {
"id": "b2",
"title": "Domain-Driven Design"
}
}
Retrieve an Order
GET /orders/{id}
{
"data": {
"id": "o1",
"status": "processing",
"items": [
{ "book_id": "b1", "quantity": 1 },
{ "book_id": "b2", "quantity": 2 }
],
"total": 115.0
}
}
Standard Error Responses
To build trust with developers, always return consistent error messages:
Example: Not Found
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Book not found"
}
}
Example: Validation Error
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Missing required field: title"
}
}
Use Clear HTTP Status Codes
Code | Meaning |
---|---|
200 | OK |
201 | Created |
204 | No Content |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
404 | Not Found |
409 | Conflict |
422 | Validation Error |
500 | Server Error |
Version Your API
Always prefix your paths with a version number:
/v1/books
This gives you room to grow and change without breaking existing clients.
A Checklist for Reliable APIs
Here’s a quick reference you can share with your team:
✅ Clear, consistent resource names ✅ Predictable HTTP verbs ✅ Standard JSON response formats ✅ Pagination, filtering, and sorting ✅ Consistent error messages ✅ Authentication enforced ✅ Versioning in place ✅ Solid documentation
Final Thoughts
Great APIs are not about buzzwords—they’re about clarity, consistency, and trust. When you take the time to design with purpose, you empower your teams and your customers to build with confidence.