# Common API Anti-Patterns and How to Avoid Them ## Introduction This document outlines common anti-patterns in REST API design that can lead to poor developer experience, maintenance nightmares, and scalability issues. Each anti-pattern is accompanied by examples and recommended solutions. ## 1. Verb-Based URLs (The RPC Trap) ### Anti-Pattern Using verbs in URLs instead of treating endpoints as resources. ``` ❌ Bad Examples: POST /api/getUsers POST /api/createUser GET /api/deleteUser/123 POST /api/updateUserPassword GET /api/calculateOrderTotal/456 ``` ### Why It's Bad - Violates REST principles - Makes the API feel like RPC instead of REST - HTTP methods lose their semantic meaning - Reduces cacheability - Harder to understand resource relationships ### Solution ``` ✅ Good Examples: GET /api/users # Get users POST /api/users # Create user DELETE /api/users/123 # Delete user PATCH /api/users/123/password # Update password GET /api/orders/456/total # Get order total ``` ## 2. Inconsistent Naming Conventions ### Anti-Pattern Mixed naming conventions across the API. ```json ❌ Bad Examples: { "user_id": 123, // snake_case "firstName": "John", // camelCase "last-name": "Doe", // kebab-case "EMAIL": "john@example.com", // UPPER_CASE "IsActive": true // PascalCase } ``` ### Why It's Bad - Confuses developers - Increases cognitive load - Makes code generation difficult - Reduces API adoption ### Solution ```json ✅ Choose one convention and stick to it (camelCase recommended): { "userId": 123, "firstName": "John", "lastName": "Doe", "email": "john@example.com", "isActive": true } ``` ## 3. Ignoring HTTP Status Codes ### Anti-Pattern Always returning HTTP 200 regardless of the actual result. ```json ❌ Bad Example: HTTP/1.1 200 OK { "status": "error", "code": 404, "message": "User not found" } ``` ### Why It's Bad - Breaks HTTP semantics - Prevents proper error handling by clients - Breaks caching and proxies - Makes monitoring and debugging harder ### Solution ```json ✅ Good Example: HTTP/1.1 404 Not Found { "error": { "code": "USER_NOT_FOUND", "message": "User with ID 123 not found", "requestId": "req-abc123" } } ``` ## 4. Overly Complex Nested Resources ### Anti-Pattern Creating deeply nested URL structures that are hard to navigate. ``` ❌ Bad Example: /companies/123/departments/456/teams/789/members/012/projects/345/tasks/678/comments/901 ``` ### Why It's Bad - URLs become unwieldy - Creates tight coupling between resources - Makes independent resource access difficult - Complicates authorization logic ### Solution ``` ✅ Good Examples: /tasks/678 # Direct access to task /tasks/678/comments # Task comments /users/012/tasks # User's tasks /projects/345?team=789 # Project filtering ``` ## 5. Inconsistent Error Response Formats ### Anti-Pattern Different error response structures across endpoints. ```json ❌ Bad Examples: # Endpoint 1 {"error": "Invalid email"} # Endpoint 2 {"success": false, "msg": "User not found", "code": 404} # Endpoint 3 {"errors": [{"field": "name", "message": "Required"}]} ``` ### Why It's Bad - Makes error handling complex for clients - Reduces code reusability - Poor developer experience ### Solution ```json ✅ Standardized Error Format: { "error": { "code": "VALIDATION_ERROR", "message": "The request contains invalid data", "details": [ { "field": "email", "code": "INVALID_FORMAT", "message": "Email address is not valid" } ], "requestId": "req-123456", "timestamp": "2024-02-16T13:00:00Z" } } ``` ## 6. Missing or Poor Pagination ### Anti-Pattern Returning all results in a single response or inconsistent pagination. ```json ❌ Bad Examples: # No pagination (returns 10,000 records) GET /api/users # Inconsistent pagination parameters GET /api/users?page=1&size=10 GET /api/orders?offset=0&limit=20 GET /api/products?start=0&count=50 ``` ### Why It's Bad - Can cause performance issues - May overwhelm clients - Inconsistent pagination parameters confuse developers - No way to estimate total results ### Solution ```json ✅ Good Example: GET /api/users?page=1&pageSize=10 { "data": [...], "pagination": { "page": 1, "pageSize": 10, "total": 150, "totalPages": 15, "hasNext": true, "hasPrev": false } } ``` ## 7. Exposing Internal Implementation Details ### Anti-Pattern URLs and field names that reflect database structure or internal architecture. ``` ❌ Bad Examples: /api/user_table/123 /api/db_orders /api/legacy_customer_data /api/temp_migration_users Response fields: { "user_id_pk": 123, "internal_ref_code": "usr_abc", "db_created_timestamp": 1645123456 } ``` ### Why It's Bad - Couples API to internal implementation - Makes refactoring difficult - Exposes unnecessary technical details - Reduces API longevity ### Solution ``` ✅ Good Examples: /api/users/123 /api/orders /api/customers Response fields: { "id": 123, "referenceCode": "usr_abc", "createdAt": "2024-02-16T13:00:00Z" } ``` ## 8. Overloading Single Endpoint ### Anti-Pattern Using one endpoint for multiple unrelated operations based on request parameters. ``` ❌ Bad Example: POST /api/user-actions { "action": "create_user", "userData": {...} } POST /api/user-actions { "action": "delete_user", "userId": 123 } POST /api/user-actions { "action": "send_email", "userId": 123, "emailType": "welcome" } ``` ### Why It's Bad - Breaks REST principles - Makes documentation complex - Complicates client implementation - Reduces discoverability ### Solution ``` ✅ Good Examples: POST /api/users # Create user DELETE /api/users/123 # Delete user POST /api/users/123/emails # Send email to user ``` ## 9. Lack of Versioning Strategy ### Anti-Pattern Making breaking changes without version management. ``` ❌ Bad Examples: # Original API { "name": "John Doe", "age": 30 } # Later (breaking change with no versioning) { "firstName": "John", "lastName": "Doe", "birthDate": "1994-02-16" } ``` ### Why It's Bad - Breaks existing clients - Forces all clients to update simultaneously - No graceful migration path - Reduces API stability ### Solution ``` ✅ Good Examples: # Version 1 GET /api/v1/users/123 { "name": "John Doe", "age": 30 } # Version 2 (with both versions supported) GET /api/v2/users/123 { "firstName": "John", "lastName": "Doe", "birthDate": "1994-02-16", "age": 30 // Backwards compatibility } ``` ## 10. Poor Error Messages ### Anti-Pattern Vague, unhelpful, or technical error messages. ```json ❌ Bad Examples: {"error": "Something went wrong"} {"error": "Invalid input"} {"error": "SQL constraint violation: FK_user_profile_id"} {"error": "NullPointerException at line 247"} ``` ### Why It's Bad - Doesn't help developers fix issues - Increases support burden - Poor developer experience - May expose sensitive information ### Solution ```json ✅ Good Examples: { "error": { "code": "VALIDATION_ERROR", "message": "The email address is required and must be in a valid format", "details": [ { "field": "email", "code": "REQUIRED", "message": "Email address is required" } ] } } ``` ## 11. Ignoring Content Negotiation ### Anti-Pattern Hard-coding response format without considering client preferences. ``` ❌ Bad Example: # Always returns JSON regardless of Accept header GET /api/users/123 Accept: application/xml # Returns JSON anyway ``` ### Why It's Bad - Reduces API flexibility - Ignores HTTP standards - Makes integration harder for diverse clients ### Solution ``` ✅ Good Example: GET /api/users/123 Accept: application/xml HTTP/1.1 200 OK Content-Type: application/xml 123 John Doe ``` ## 12. Stateful API Design ### Anti-Pattern Maintaining session state on the server between requests. ``` ❌ Bad Example: # Step 1: Initialize session POST /api/session/init # Step 2: Set context (requires step 1) POST /api/session/set-user/123 # Step 3: Get data (requires steps 1 & 2) GET /api/session/user-data ``` ### Why It's Bad - Breaks REST statelessness principle - Reduces scalability - Makes caching difficult - Complicates error recovery ### Solution ``` ✅ Good Example: # Self-contained requests GET /api/users/123/data Authorization: Bearer jwt-token-with-context ``` ## 13. Inconsistent HTTP Method Usage ### Anti-Pattern Using HTTP methods inappropriately or inconsistently. ``` ❌ Bad Examples: GET /api/users/123/delete # DELETE operation with GET POST /api/users/123/get # GET operation with POST PUT /api/users # Creating with PUT on collection GET /api/users/search # Search with side effects ``` ### Why It's Bad - Violates HTTP semantics - Breaks caching and idempotency expectations - Confuses developers and tools ### Solution ``` ✅ Good Examples: DELETE /api/users/123 # Delete with DELETE GET /api/users/123 # Get with GET POST /api/users # Create on collection GET /api/users?q=search # Safe search with GET ``` ## 14. Missing Rate Limiting Information ### Anti-Pattern Not providing rate limiting information to clients. ``` ❌ Bad Example: HTTP/1.1 429 Too Many Requests { "error": "Rate limit exceeded" } ``` ### Why It's Bad - Clients don't know when to retry - No information about current limits - Difficult to implement proper backoff strategies ### Solution ``` ✅ Good Example: HTTP/1.1 429 Too Many Requests X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1640995200 Retry-After: 3600 { "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "API rate limit exceeded", "retryAfter": 3600 } } ``` ## 15. Chatty API Design ### Anti-Pattern Requiring multiple API calls to accomplish common tasks. ``` ❌ Bad Example: # Get user profile requires 4 API calls GET /api/users/123 # Basic info GET /api/users/123/profile # Profile details GET /api/users/123/settings # User settings GET /api/users/123/stats # User statistics ``` ### Why It's Bad - Increases latency - Creates network overhead - Makes mobile apps inefficient - Complicates client implementation ### Solution ``` ✅ Good Examples: # Single call with expansion GET /api/users/123?include=profile,settings,stats # Or provide composite endpoints GET /api/users/123/dashboard # Or batch operations POST /api/batch { "requests": [ {"method": "GET", "url": "/users/123"}, {"method": "GET", "url": "/users/123/profile"} ] } ``` ## 16. No Input Validation ### Anti-Pattern Accepting and processing invalid input without proper validation. ```json ❌ Bad Example: POST /api/users { "email": "not-an-email", "age": -5, "name": "" } # API processes this and fails later or stores invalid data ``` ### Why It's Bad - Leads to data corruption - Security vulnerabilities - Difficult to debug issues - Poor user experience ### Solution ```json ✅ Good Example: POST /api/users { "email": "not-an-email", "age": -5, "name": "" } HTTP/1.1 400 Bad Request { "error": { "code": "VALIDATION_ERROR", "message": "The request contains invalid data", "details": [ { "field": "email", "code": "INVALID_FORMAT", "message": "Email must be a valid email address" }, { "field": "age", "code": "INVALID_RANGE", "message": "Age must be between 0 and 150" }, { "field": "name", "code": "REQUIRED", "message": "Name is required and cannot be empty" } ] } } ``` ## 17. Synchronous Long-Running Operations ### Anti-Pattern Blocking the client with long-running operations in synchronous endpoints. ``` ❌ Bad Example: POST /api/reports/generate # Client waits 30 seconds for response ``` ### Why It's Bad - Poor user experience - Timeouts and connection issues - Resource waste on client and server - Doesn't scale well ### Solution ``` ✅ Good Example: # Async pattern POST /api/reports HTTP/1.1 202 Accepted Location: /api/reports/job-123 { "jobId": "job-123", "status": "processing", "estimatedCompletion": "2024-02-16T13:05:00Z" } # Check status GET /api/reports/job-123 { "jobId": "job-123", "status": "completed", "result": "/api/reports/download/report-456" } ``` ## Prevention Strategies ### 1. API Design Reviews - Implement mandatory design reviews - Use checklists based on these anti-patterns - Include multiple stakeholders ### 2. API Style Guides - Create and enforce API style guides - Use linting tools for consistency - Regular training for development teams ### 3. Automated Testing - Test for common anti-patterns - Include contract testing - Monitor API usage patterns ### 4. Documentation Standards - Require comprehensive API documentation - Include examples and error scenarios - Keep documentation up-to-date ### 5. Client Feedback - Regularly collect feedback from API consumers - Monitor API usage analytics - Conduct developer experience surveys ## Conclusion Avoiding these anti-patterns requires: - Understanding REST principles - Consistent design standards - Regular review and refactoring - Focus on developer experience - Proper tooling and automation Remember: A well-designed API is an asset that grows in value over time, while a poorly designed API becomes a liability that hampers development and adoption.