API Design Principles
Master REST API design principles to build intuitive, scalable, and maintainable APIs. This skill focuses on design theory - for implementation patterns, see abp-api-implementation or abp-service-patterns .
When to Use This Skill
-
Designing new REST API contracts
-
Reviewing API specifications before implementation
-
Establishing API design standards for your team
-
Planning API versioning and evolution strategy
-
Creating developer-friendly API documentation
Audience
-
Backend Architects - API contract design
-
Tech Leads - Standards and review
-
Business Analysts - Understanding API capabilities
For Implementation: Use abp-service-patterns for AppService code, api-response-patterns for response wrappers, fluentvalidation-patterns for validation.
Core Principles
- Resource-Oriented Design
APIs expose resources (nouns), not actions (verbs).
Concept Good Bad
Resource naming /patients , /appointments
/getPatients , /createAppointment
Actions via HTTP methods POST /patients
POST /createPatient
Plural for collections /patients
/patient
Consistent casing kebab-case or camelCase
Mixed styles
Resource Hierarchy:
/api/v1/patients # Collection /api/v1/patients/{id} # Single resource /api/v1/patients/{id}/appointments # Nested collection /api/v1/appointments/{id} # Direct access to nested resource
Avoid Deep Nesting (max 2 levels):
Good - Shallow
GET /api/v1/patients/{id}/appointments GET /api/v1/appointments/{id}
Bad - Too deep
GET /api/v1/clinics/{id}/doctors/{id}/patients/{id}/appointments/{id}
- HTTP Methods Semantics
Method Purpose Idempotent Safe Request Body
GET
Retrieve resource(s) Yes Yes No
POST
Create resource No No Yes
PUT
Replace entire resource Yes No Yes
PATCH
Partial update Yes* No Yes
DELETE
Remove resource Yes No No
Idempotent: Multiple identical requests produce same result. Safe: Does not modify server state.
- HTTP Status Codes
Success (2xx):
Code Meaning Use When
200 OK
Success GET, PUT, PATCH succeeded
201 Created
Resource created POST succeeded
204 No Content
Success, no body DELETE succeeded
Client Errors (4xx):
Code Meaning Use When
400 Bad Request
Malformed request Invalid JSON, missing required headers
401 Unauthorized
Not authenticated Missing or invalid token
403 Forbidden
Not authorized Valid token, insufficient permissions
404 Not Found
Resource doesn't exist ID not found
409 Conflict
State conflict Duplicate email, version mismatch
422 Unprocessable Entity
Validation failed Business rule violations
429 Too Many Requests
Rate limited Exceeded request quota
Server Errors (5xx):
Code Meaning Use When
500 Internal Server Error
Unexpected error Unhandled exception
503 Service Unavailable
Temporarily down Maintenance, overload
Design Patterns
Pattern 1: Pagination
Always paginate collections - Never return unbounded lists.
Offset-Based (simple, good for small datasets):
GET /api/v1/patients?page=2&pageSize=20
Response: { "items": [...], "totalCount": 150, "pageNumber": 2, "pageSize": 20, "totalPages": 8 }
Cursor-Based (efficient for large datasets, real-time data):
GET /api/v1/patients?cursor=eyJpZCI6MTIzfQ&limit=20
Response: { "items": [...], "nextCursor": "eyJpZCI6MTQzfQ", "hasMore": true }
Approach Pros Cons Best For
Offset Simple, supports jumping to page Slow on large datasets, inconsistent with real-time data Admin panels, reports
Cursor Fast, consistent Can't jump to arbitrary page Infinite scroll, feeds
Pattern 2: Filtering and Sorting
Query Parameters for Filtering:
GET /api/v1/patients?status=active GET /api/v1/patients?status=active&createdAfter=2025-01-01 GET /api/v1/patients?doctorId=abc-123
Sorting:
GET /api/v1/patients?sorting=name GET /api/v1/patients?sorting=createdAt desc GET /api/v1/patients?sorting=lastName,firstName
Searching:
GET /api/v1/patients?filter=john GET /api/v1/patients?search=john doe
Design Decisions:
-
Use WhereIf pattern - only apply filter if parameter provided
-
Define allowed sort fields (security - don't expose internal fields)
-
Set maximum page size (prevent abuse)
Pattern 3: Error Response Design
Consistent Structure:
{ "error": { "code": "VALIDATION_ERROR", "message": "One or more validation errors occurred.", "details": [ { "field": "email", "message": "Invalid email format." }, { "field": "dateOfBirth", "message": "Date of birth cannot be in the future." } ], "traceId": "00-abc123-def456-00" } }
Error Codes (for client handling):
Code HTTP Status Meaning
VALIDATION_ERROR
422 Input validation failed
NOT_FOUND
404 Resource doesn't exist
UNAUTHORIZED
401 Authentication required
FORBIDDEN
403 Permission denied
CONFLICT
409 State conflict
RATE_LIMITED
429 Too many requests
Pattern 4: Versioning Strategy
URL Versioning (Recommended for ABP):
/api/v1/patients /api/v2/patients
Strategy Example Pros Cons
URL Path /api/v1/
Clear, easy routing Multiple URLs
Header Api-Version: 1
Clean URLs Hidden, harder to test
Query Param ?version=1
Easy testing Can be forgotten
Versioning Policy:
-
Major version for breaking changes
-
Support N-1 version minimum
-
Deprecation notice 6+ months before removal
-
Document migration path
Pattern 5: Resource Relationships
Embedding vs Linking:
// Embedded (fewer requests, larger payload) { "id": "patient-123", "name": "John Doe", "doctor": { "id": "doctor-456", "name": "Dr. Smith" } }
// Linked (smaller payload, more requests) { "id": "patient-123", "name": "John Doe", "doctorId": "doctor-456" }
// Hybrid (with expand parameter) GET /api/v1/patients/123?expand=doctor,appointments
Decision Criteria:
Use Embedding Use Linking
Related data always needed Related data rarely needed
Few relationships Many relationships
Related data is small Related data is large
API Contract Checklist
Resource Design
-
Resources are nouns, not verbs
-
Plural names for collections
-
Consistent naming convention
-
Max 2 levels of nesting
-
All CRUD mapped to correct HTTP methods
Request/Response
-
All collections paginated
-
Default and max page size defined
-
Filter parameters documented
-
Sorting parameters documented
-
Consistent error response format
Security
-
Authentication method defined
-
Authorization on all mutating endpoints
-
Rate limiting configured
-
Sensitive data not in URLs
-
CORS configured
Documentation
-
OpenAPI/Swagger spec
-
All endpoints documented
-
Request/response examples
-
Error responses documented
Anti-Patterns to Avoid
Anti-Pattern Problem Solution
Verb endpoints POST /createPatient
POST /patients
Ignoring HTTP methods Using POST for everything Use appropriate method
No pagination Returning 10,000 items Always paginate
Inconsistent errors Different formats per endpoint Standardize error structure
Exposing internals Database columns in API Design API contract separately
No versioning Breaking changes break clients Version from day one
Deep nesting /a/{id}/b/{id}/c/{id}/d
Flatten, max 2 levels
Integration with Other Skills
Need Skill
AppService implementation abp-service-patterns
Response wrappers api-response-patterns
Input validation fluentvalidation-patterns
Query optimization linq-optimization-patterns
Technical design docs technical-design-patterns
References
-
references/rest-best-practices.md
-
Detailed REST patterns
-
assets/api-design-checklist.md
-
Pre-implementation checklist