REST API Design Best Practices
Build RESTful APIs that are intuitive, scalable, and maintainable.
A Simple Analogy
A well-designed REST API is like a well-organized library. Books (resources) are organized logically, labeled clearly, and there's a standard system to find and retrieve them. Clients know exactly how to locate what they need without guidance.
What Makes a Good REST API?
A good REST API is intuitive, consistent, scalable, and predictable. Clients can understand and use it without extensive documentation because it follows conventions.
Core REST Principles
| Principle | Meaning | |-----------|---------| | Resources | Everything is a resource (users, orders, products) | | HTTP Methods | Use GET, POST, PUT, DELETE appropriately | | Status Codes | Communicate success/failure clearly | | Stateless | Each request is independent | | Representations | Resources returned as JSON/XML |
Resource Design
Good:
GET /api/users # List users
POST /api/users # Create user
GET /api/users/:id # Get specific user
PUT /api/users/:id # Update user
DELETE /api/users/:id # Delete user
Bad:
GET /api/getUsers
POST /api/createUser
GET /api/getUserById
POST /api/updateUser
POST /api/deleteUser
HTTP Status Codes
// Success
200 OK // Request succeeded
201 Created // Resource created
204 No Content // Success, no response body
// Client Errors
400 Bad Request // Invalid input
401 Unauthorized // Authentication required
403 Forbidden // Authenticated but not allowed
404 Not Found // Resource doesn't exist
409 Conflict // Request conflicts (duplicate)
// Server Errors
500 Internal Error // Server error
503 Service Unavailable // Server temporarily down
Practical Example
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet("{id}")]
public async Task<ActionResult<OrderDto>> GetOrder(int id)
{
var order = await _orderService.GetOrderAsync(id);
if (order == null)
return NotFound(); // 404
return Ok(order); // 200
}
[HttpPost]
public async Task<ActionResult<OrderDto>> CreateOrder(CreateOrderDto dto)
{
var order = await _orderService.CreateAsync(dto);
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order); // 201
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateOrder(int id, UpdateOrderDto dto)
{
var success = await _orderService.UpdateAsync(id, dto);
if (!success)
return NotFound(); // 404
return NoContent(); // 204
}
}
Best Practices
- Use nouns, not verbs:
/ordersnot/getOrders - Consistent naming: Plurals, lowercase, hyphens:
/users,/product-categories - Pagination:
GET /users?page=1&limit=10 - Filtering:
GET /orders?status=pending&date=2025-02-20 - Versioning:
GET /api/v1/usersor header-based - Documentation: Clear error messages and swagger
- Error responses: Consistent format
{ "error": "Invalid input", "code": 400, "details": "Email is required" }
Real-World Example
User Orders API
GET /api/v1/users/:userId/orders
Returns: [{ id, date, status, total }]
POST /api/v1/users/:userId/orders
Body: { items: [...], shippingAddress: {...} }
Returns: 201 + { id, orderNumber, ... }
GET /api/v1/orders/:orderId
Returns: { id, items, status, total, dates }
PUT /api/v1/orders/:orderId
Body: { status: "shipped", trackingNumber: "..." }
Returns: 204
Related Concepts to Explore
- GraphQL (alternative to REST)
- API versioning strategies
- Rate limiting and throttling
- HATEOAS (Hypermedia)
- OpenAPI/Swagger documentation
Summary
Good REST API design follows HTTP conventions, uses intuitive resource naming, and clear status codes. This makes APIs discoverable, maintainable, and a pleasure to use.