REST API Design Best Practices: Build APIs Like a Pro
Introduction
REST APIs power modern web and mobile applications. Yet many developers build APIs without understanding fundamental design principles, leading to confusing, hard-to-maintain, and inefficient endpoints. This guide covers battle-tested REST API design patterns used by tech giants like Google, Amazon, and Microsoft. By following these principles, you'll build APIs that are intuitive, scalable, and professional.
REST Fundamentals
What is REST?
REST (Representational State Transfer) is an architectural style using HTTP to perform CRUD (Create, Read, Update, Delete) operations on resources:
- Stateless: Each request contains all needed information
- Resource-oriented: URLs represent resources, not actions
- HTTP methods: Use appropriate verbs (GET, POST, PUT, DELETE)
- Status codes: Use standard HTTP codes for responses
URL Naming Conventions
Resources and Collections
# Collection endpoints
GET /api/users # Get all users
POST /api/users # Create new user
# Single resource endpoints
GET /api/users/123 # Get specific user
PUT /api/users/123 # Update specific user
DELETE /api/users/123 # Delete specific user
# Nested resources
GET /api/users/123/posts # Get posts by user 123
POST /api/users/123/posts # Create post for user 123
GET /api/users/123/posts/45 # Get specific postURL Best Practices
✓ Use nouns for resources (not verbs)
GET /users ✓
GET /getUsers ✗
✓ Use lowercase and hyphens
/user-profiles/123 ✓
/UserProfiles/123 ✗
✓ Use plurals for collections
GET /products ✓
GET /product ✗
✓ Use IDs in URLs
/users/123 ✓
/users/john ✗ (use IDs, not names)
✓ Keep URLs simple (1-2 levels deep)
/users/123/posts/45/comments/67 ✗ Too nestedHTTP Methods: Use Them Correctly
GET - Retrieve Data
GET /api/users # Fetch all users
GET /api/users/123 # Fetch specific user
GET /api/users?status=active&limit=10 # Filter and paginate
✓ Safe: Doesn't modify data
✓ Idempotent: Multiple calls return same result
✓ Cacheable: Response can be cachedPOST - Create Data
POST /api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
✓ Used for creation
✓ NOT idempotent: Multiple calls create multiple resources
✗ NOT safe: Modifies data
Response: 201 Created with Location headerPUT - Replace Entire Resource
PUT /api/users/123
{
"name": "Jane Doe",
"email": "jane@example.com"
}
✓ Replaces entire resource
✓ Idempotent: Multiple calls have same effect
✓ Must include all required fieldsPATCH - Partial Update
PATCH /api/users/123
{
"email": "newemail@example.com"
}
✓ Updates specific fields only
✓ More efficient than PUT
✓ Partial resource allowedDELETE - Remove Resource
DELETE /api/users/123
✓ Removes resource
✓ Idempotent: Multiple calls same result
✓ Response: 204 No ContentMethod Cheatsheet
| Method | CRUD | Idempotent | Safe |
|---|---|---|---|
| GET | Read | Yes | Yes |
| POST | Create | No | No |
| PUT | Update | Yes | No |
| PATCH | Partial Update | No | No |
| DELETE | Delete | Yes | No |
HTTP Status Codes
Success Responses (2xx)
200 OK - Request succeeded, data returned
201 Created - Resource created successfully
204 No Content - Request succeeded, no content to returnRedirect Responses (3xx)
301 Moved Permanently - Resource moved, update URL
302 Found - Temporary redirect
304 Not Modified - Cache is still validClient Error Responses (4xx)
400 Bad Request - Invalid request format
401 Unauthorized - Authentication required
403 Forbidden - Authenticated but no permission
404 Not Found - Resource doesn't exist
409 Conflict - Request conflicts (duplicate email)
422 Unprocessable Entity - Validation failed
429 Too Many Requests - Rate limitedServer Error Responses (5xx)
500 Internal Server Error - Server error
502 Bad Gateway - Upstream error
503 Service Unavailable - Server overloadedRequest and Response Format
Standard JSON Response
{
"success": true,
"data": {
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"createdAt": "2025-11-25T10:30:00Z"
},
"message": "User created successfully"
}Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{
"field": "email",
"message": "Email already exists"
},
{
"field": "age",
"message": "Age must be 18+"
}
]
}
}Pagination
{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 500,
"pages": 25,
"hasNext": true,
"hasPrev": false
}
}Advanced API Design Patterns
API Versioning
# URL versioning (explicit, easy for clients)
GET /api/v1/users
GET /api/v2/users
# Header versioning (cleaner URLs)
GET /api/users
Accept: application/vnd.api+json;version=2
# Parameter versioning
GET /api/users?version=2
# Best practice: Use URL versioning for major changesFiltering, Sorting, Pagination
# Filtering
GET /api/users?role=admin&status=active
# Sorting
GET /api/users?sort=-createdAt,name
# Pagination
GET /api/users?page=2&limit=50
# Combined
GET /api/users?role=admin&sort=-createdAt&page=1&limit=20Rate Limiting Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640000000
# Client knows: 1000 requests/hour, 999 remainingSecurity Best Practices
Authentication
# Use Bearer tokens (JWT)
GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# Never send credentials in URL
❌ GET /api/users?token=abc123
✓ Authorization: Bearer abc123HTTPS Only
✓ Always use HTTPS
✓ Set HSTS header: Strict-Transport-Security: max-age=31536000Input Validation
app.post('/api/users', (req, res) => {
const { name, email, age } = req.body;
if (!name || !email) {
return res.status(400).json({
error: "Name and email required"
});
}
if (age < 18) {
return res.status(400).json({
error: "Must be 18+"
});
}
// Process valid data
});CORS Policy
// Allow specific origins
app.use(cors({
origin: ['https://frontend.com'],
credentials: true
}));Real-World API Example
// Express.js REST API example
const express = require('express');
const app = express();
app.use(express.json());
// GET all users with pagination
app.get('/api/users', (req, res) => {
const { page = 1, limit = 20 } = req.query;
const offset = (page - 1) * limit;
// Fetch from DB
const users = getUsersFromDB(offset, limit);
res.json({
success: true,
data: users,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: getTotalUserCount()
}
});
});
// POST create user
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({
success: false,
error: { message: "Name and email required" }
});
}
const user = createUserInDB({ name, email });
res.status(201)
.set('Location', `/api/users/${user.id}`)
.json({
success: true,
data: user,
message: "User created"
});
});
// GET specific user
app.get('/api/users/:id', (req, res) => {
const user = getUserFromDB(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: { message: "User not found" }
});
}
res.json({ success: true, data: user });
});
// PATCH update user
app.patch('/api/users/:id', (req, res) => {
const user = updateUserInDB(req.params.id, req.body);
res.json({ success: true, data: user });
});
// DELETE user
app.delete('/api/users/:id', (req, res) => {
deleteUserFromDB(req.params.id);
res.status(204).send();
});
app.listen(3000);API Documentation
Use tools like Swagger/OpenAPI:
/api/users:
get:
summary: List all users
parameters:
- name: page
in: query
type: integer
responses:
200:
description: List of users
schema:
$ref: '#/definitions/UserList'Tools and Testing
- Postman: API testing and documentation
- Insomnia: Lightweight REST client
- cURL: Command-line testing
- API docs: Swagger UI, ReDoc
# cURL examples
curl -X GET https://localhost:3000/api/users
curl -X POST https://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"John","email":"john@example.com"}'Checklist for Professional APIs
- ✓ Use proper HTTP methods for operations
- ✓ Return appropriate status codes
- ✓ Implement pagination for collections
- ✓ Version your API from day one
- ✓ Use consistent JSON response format
- ✓ Implement proper error handling
- ✓ Require authentication for protected routes
- ✓ Document your API thoroughly
- ✓ Implement rate limiting
- ✓ Use HTTPS for all endpoints
- ✓ Test edge cases and errors
Conclusion
Good REST API design isn't fancy—it's following proven conventions so developers intuitively understand your API. These patterns scale from startups to enterprise, making your APIs professional, maintainable, and a pleasure to use.
Design APIs today that developers will love tomorrow! 🚀