DEV Community

Atlas Whoff
Atlas Whoff

Posted on

API Design Best Practices: RESTful Patterns That Stand the Test of Time

API Design Best Practices: RESTful Patterns That Stand the Test of Time

A well-designed API is a pleasure to consume. A poorly designed one creates support tickets forever.

Resource Naming

# Good: nouns, plural, hierarchical
GET    /users              List users
GET    /users/:id          Get user
POST   /users              Create user
PUT    /users/:id          Replace user
PATCH  /users/:id          Update user fields
DELETE /users/:id          Delete user

GET    /users/:id/orders   List user's orders
POST   /users/:id/orders   Create order for user

# Bad: verbs in URLs
POST /createUser
GET  /getUserById
POST /user/delete
Enter fullscreen mode Exit fullscreen mode

HTTP Status Codes

// Use the right codes
200 OK           // Successful GET, PATCH, DELETE
201 Created      // Successful POST (return new resource)
204 No Content   // Successful DELETE (no body)
400 Bad Request  // Validation error (return field errors)
401 Unauthorized // Not authenticated
403 Forbidden    // Authenticated but not authorized
404 Not Found    // Resource doesn't exist
409 Conflict     // Duplicate resource (email already taken)
422 Unprocessable// Validation failed (business rule, not format)
429 Too Many     // Rate limited
500 Server Error // Something broke
Enter fullscreen mode Exit fullscreen mode

Consistent Error Format

// Every error response uses the same shape
interface ApiError {
  error: {
    code: string;       // Machine-readable: 'USER_NOT_FOUND'
    message: string;    // Human-readable description
    details?: Record<string, string[]>; // Field-level errors
  };
}

// 400 validation error
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Request validation failed",
    "details": {
      "email": ["Invalid email format"],
      "password": ["Must be at least 8 characters"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Pagination

// Cursor-based (preferred for large datasets)
GET /orders?cursor=eyJpZCI6MTIzfQ&limit=20

// Response includes next cursor
{
  "data": [...],
  "pagination": {
    "hasMore": true,
    "nextCursor": "eyJpZCI6MTQzfQ"
  }
}
Enter fullscreen mode Exit fullscreen mode

Versioning and Filtering

// Filtering via query params
GET /orders?status=pending&createdAfter=2024-01-01&limit=50

// Sparse fieldsets (only return what the client needs)
GET /users?fields=id,name,email

// Embedding related resources
GET /orders/:id?include=user,items
Enter fullscreen mode Exit fullscreen mode

Idempotency Keys

// Client sends idempotency key with POST requests
app.post('/api/orders', async (req, res) => {
  const idempotencyKey = req.headers['idempotency-key'];

  if (idempotencyKey) {
    // Check if we've already processed this request
    const existing = await redis.get(`idem:${idempotencyKey}`);
    if (existing) return res.json(JSON.parse(existing));
  }

  const order = await createOrder(req.body);

  if (idempotencyKey) {
    await redis.setex(`idem:${idempotencyKey}`, 86400, JSON.stringify(order));
  }

  res.status(201).json(order);
});
Enter fullscreen mode Exit fullscreen mode

Well-designed REST APIs with consistent error handling, pagination, and idempotency are the foundation of the backend in the AI SaaS Starter Kit.

Top comments (0)