REST principles, URL design, HTTP methods, versioning, authentication, and best practices
Resources are nouns; HTTP methods express the action
// Good
GET /users
POST /users
GET /users/123
// Bad
GET /getUsers
POST /createUser
GET /fetchUserById?id=123Collections should always use the plural form
/users ✓
/user ✗
/posts/123 ✓
/post/123 ✗Express ownership/relationship through nesting (max 2 levels)
/users/123/posts # posts by user 123
/users/123/posts/456 # specific post by user
// Avoid deep nesting:
/users/123/posts/456/comments/789/likes ✗Use for filtering, sorting, pagination — not resource identity
GET /posts?status=published&author=alice
GET /posts?sort=created_at&order=desc
GET /posts?page=2&limit=20
GET /users?search=aliceMap CRUD operations to HTTP methods
Create → POST /resources → 201
Read → GET /resources → 200
Read → GET /resources/:id → 200
Update → PUT /resources/:id → 200
Update → PATCH /resources/:id → 200
Delete → DELETE /resources/:id → 204Version in the URL — most visible, easiest to test
/api/v1/users
/api/v2/users
// Easy to route to different handlers
app.use("/api/v1", v1Router)
app.use("/api/v2", v2Router)Version via Accept or custom header — keeps URLs clean
GET /api/users
Accept: application/vnd.myapi.v2+json
// or custom header
API-Version: 2Standard for stateless API authentication
GET /api/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...Simple key-based auth for server-to-server
// Header (preferred)
X-API-Key: abc123def456
// Query param (avoid for sensitive APIs)
GET /api/data?api_key=abc123def456Base64 encoded username:password — only over HTTPS
Authorization: Basic dXNlcjpwYXNzd29yZA==
# = base64("user:password")Efficient for large datasets; use opaque cursor token
GET /posts?cursor=abc123&limit=20
// Response
{
"data": [...],
"pagination": {
"nextCursor": "def456",
"hasMore": true
}
}Simple page/offset-based — good for small datasets
GET /posts?page=3&limit=20
// Response
{
"data": [...],
"pagination": { "page": 3, "limit": 20, "total": 150 }
}Always return the same error structure
// 422 Validation Error
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Validation failed",
"details": [
{ "field": "email", "message": "Invalid format" }
]
}
}
// 404 Not Found
{
"error": {
"code": "NOT_FOUND",
"message": "User 999 not found"
}
}Complete URL design for a blog API
# Collections
GET /api/v1/posts # list posts
POST /api/v1/posts # create post
# Single resources
GET /api/v1/posts/:id # get post
PUT /api/v1/posts/:id # replace post
PATCH /api/v1/posts/:id # update post
DELETE /api/v1/posts/:id # delete post
# Nested resources (max 2 levels)
GET /api/v1/posts/:id/comments # list comments
POST /api/v1/posts/:id/comments # add comment
# Actions (non-CRUD)
POST /api/v1/posts/:id/publish # publish post
POST /api/v1/auth/login # login actionStandard paginated list response envelope
{
"data": [
{ "id": 1, "title": "First Post" },
{ "id": 2, "title": "Second Post" }
],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"totalPages": 8,
"hasNext": true,
"hasPrev": false
},
"links": {
"self": "/api/v1/posts?page=1&limit=20",
"next": "/api/v1/posts?page=2&limit=20",
"last": "/api/v1/posts?page=8&limit=20"
}
}Consistent error response format
// All error responses follow this shape
{
"error": {
"code": "RESOURCE_NOT_FOUND", // machine-readable
"message": "Post 123 not found", // human-readable
"details": [], // optional field-level errors
"timestamp": "2024-01-15T14:30:00Z",
"path": "/api/v1/posts/123"
}
}
// Validation error (422)
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Request validation failed",
"details": [
{ "field": "title", "message": "Required" },
{ "field": "content", "message": "Must be at least 10 characters" }
]
}
}Use nouns for resources — HTTP methods express the action, not the URL
Return consistent error shapes so clients can handle errors uniformly across all endpoints
Version your API from day one — adding /v1/ later is painful
Use HTTPS everywhere — never send API keys or tokens over plain HTTP
Document your API with OpenAPI/Swagger — it generates interactive docs automatically