
api-design-principles
Master REST API design principles to build intuitive, scalable, and maintainable APIs. Use when desi
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-patternsfor AppService code,api-response-patternsfor response wrappers,fluentvalidation-patternsfor validation.
Core Principles
1. 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}
2. 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.
3. 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
WhereIfpattern - 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 patternsassets/api-design-checklist.md- Pre-implementation checklist