6 min read • Guide 349 of 877
API Design Best Practices
Good API design makes consumers' lives easier and reduces support burden. Bad API design leads to confusion, bugs, and frustrated developers. This guide covers practical approaches to API design that stand the test of time.
API Principles
| Principle | Description | Benefit |
|---|---|---|
| Consistency | Same patterns | Learnability |
| Predictability | Expected behavior | Less bugs |
| Evolvability | Change safely | Longevity |
| Simplicity | Easy to use | Adoption |
RESTful Design
Resource-Oriented API
RESTFUL API DESIGN
══════════════════
RESOURCES:
─────────────────────────────────────
Think in nouns, not verbs:
├── /users (not /getUsers)
├── /projects (not /createProject)
├── /tasks (not /deleteTask)
├── Resources are things
└── HTTP methods are actions
CRUD OPERATIONS:
─────────────────────────────────────
HTTP Method │ Operation │ Example
────────────┼───────────┼──────────────────
GET │ Read │ GET /users
POST │ Create │ POST /users
PUT │ Replace │ PUT /users/123
PATCH │ Update │ PATCH /users/123
DELETE │ Delete │ DELETE /users/123
RESOURCE HIERARCHY:
─────────────────────────────────────
Nested resources:
├── GET /projects/123/tasks
├── POST /projects/123/tasks
├── GET /projects/123/tasks/456
├── Clear relationships
└── Logical hierarchy
URL STRUCTURE:
─────────────────────────────────────
Consistent naming:
├── Plural nouns: /users, /tasks
├── Lowercase: /user-profiles
├── Hyphens for multi-word
├── No trailing slashes
├── No file extensions
└── Clean, predictable
Request/Response
Data Formatting
REQUEST/RESPONSE DESIGN
═══════════════════════
CONSISTENT JSON:
─────────────────────────────────────
Always same structure:
Success response:
{
"data": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
}
List response:
{
"data": [
{ "id": 123, "name": "John" },
{ "id": 124, "name": "Jane" }
],
"meta": {
"total": 100,
"page": 1,
"perPage": 20
}
}
NAMING CONVENTIONS:
─────────────────────────────────────
Consistent casing:
├── camelCase for JSON: firstName
├── snake_case also common: first_name
├── Pick one, use everywhere
├── Match your language conventions
└── Consistency matters most
FIELD FILTERING:
─────────────────────────────────────
Let clients request fields:
├── GET /users?fields=id,name,email
├── Reduce over-fetching
├── Better performance
├── GraphQL approach for REST
└── Optional but helpful
Error Handling
Clear Error Responses
ERROR HANDLING
══════════════
ERROR RESPONSE FORMAT:
─────────────────────────────────────
Consistent error structure:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request was invalid",
"details": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "password",
"message": "Must be at least 8 characters"
}
]
}
}
HTTP STATUS CODES:
─────────────────────────────────────
Use correctly:
├── 200: Success
├── 201: Created
├── 204: No content (delete success)
├── 400: Bad request (client error)
├── 401: Unauthorized (not logged in)
├── 403: Forbidden (no permission)
├── 404: Not found
├── 409: Conflict (duplicate, etc.)
├── 422: Unprocessable (validation)
├── 500: Server error
└── Meaningful status codes
ERROR MESSAGES:
─────────────────────────────────────
Good error messages:
├── Human-readable message
├── Machine-readable code
├── Specific field errors
├── Actionable guidance
├── No stack traces in prod
└── Helpful, not cryptic
✅ "Email is already registered"
❌ "Error: duplicate key violation"
Pagination
Handling Large Lists
PAGINATION PATTERNS
═══════════════════
OFFSET PAGINATION:
─────────────────────────────────────
GET /users?page=2&perPage=20
Response:
{
"data": [...],
"meta": {
"total": 500,
"page": 2,
"perPage": 20,
"totalPages": 25
}
}
Pros: Simple, familiar
Cons: Performance on large offsets
CURSOR PAGINATION:
─────────────────────────────────────
GET /users?cursor=abc123&limit=20
Response:
{
"data": [...],
"cursors": {
"next": "xyz789",
"previous": "abc122"
}
}
Pros: Better performance
Cons: Can't jump to page
BEST PRACTICE:
─────────────────────────────────────
├── Default page size (20)
├── Max page size (100)
├── Include total count
├── Include navigation links
├── Document pagination approach
└── Consistent across endpoints
Versioning
API Evolution
API VERSIONING
══════════════
URL VERSIONING:
─────────────────────────────────────
/v1/users
/v2/users
Pros:
├── Explicit and clear
├── Easy to understand
├── Good for caching
├── Easy to test
└── Most common
HEADER VERSIONING:
─────────────────────────────────────
Accept: application/vnd.api+json;version=1
Pros:
├── Cleaner URLs
├── Same resource, different representation
Cons:
├── Less visible
├── Harder to test
VERSIONING STRATEGY:
─────────────────────────────────────
├── Don't version too early
├── Version for breaking changes only
├── Maintain old versions (6-12 months)
├── Deprecation warnings
├── Migration guides
├── Clear sunset dates
└── Plan for change
AVOIDING BREAKING CHANGES:
─────────────────────────────────────
Non-breaking changes:
├── Add new fields (optional)
├── Add new endpoints
├── Add new enum values
├── Relax validation
└── Extend, don't change
Breaking changes (avoid):
├── Remove fields
├── Rename fields
├── Change field types
├── Require new fields
├── Change behavior
└── Requires new version
Documentation
API Documentation
API DOCUMENTATION
═════════════════
OPENAPI/SWAGGER:
─────────────────────────────────────
Standard documentation:
├── Machine-readable spec
├── Auto-generated docs
├── Try-it-out functionality
├── Client generation
├── Single source of truth
└── Industry standard
DOCUMENTATION INCLUDES:
─────────────────────────────────────
For each endpoint:
├── URL and method
├── Description
├── Request parameters
├── Request body schema
├── Response schemas
├── Example requests
├── Example responses
├── Error codes
├── Authentication
└── Complete picture
GOOD EXAMPLES:
─────────────────────────────────────
Show real-world usage:
## Create User
POST /v1/users
Request:
{
"name": "John Doe",
"email": "john@example.com",
"password": "securePassword123"
}
Response: 201 Created
{
"data": {
"id": "user_abc123",
"name": "John Doe",
"email": "john@example.com",
"createdAt": "2024-01-15T10:30:00Z"
}
}
KEEP DOCS UPDATED:
─────────────────────────────────────
├── Generate from code
├── Part of build process
├── CI validates spec
├── Outdated docs are harmful
└── Treat as first-class
Project Integration
API Tasks in GitScrum
GITSCRUM FOR API PROJECTS
═════════════════════════
API TASK STRUCTURE:
─────────────────────────────────────
Task: "Create user registration endpoint"
├── Acceptance criteria:
│ ├── POST /v1/users
│ ├── Validates email/password
│ ├── Returns 201 with user data
│ └── Errors follow standard format
├── Documentation updated
├── Tests included
└── Clear definition
TRACKING:
─────────────────────────────────────
├── Label: api, endpoint
├── Link to API spec
├── Version tag
├── Related frontend tasks
└── Organized work
API DOCUMENTATION:
─────────────────────────────────────
├── OpenAPI spec in repo
├── Link in NoteVault
├── Changes tracked
├── Version history
└── Findable documentation
Best Practices
For API Design
- Consistency first — Same patterns everywhere
- Good error messages — Actionable, clear
- Version carefully — Avoid breaking changes
- Document well — OpenAPI spec
- Paginate always — Don't return everything
Anti-Patterns
API DESIGN MISTAKES:
✗ Inconsistent naming
✗ Breaking changes without version
✗ Generic error messages
✗ No pagination
✗ Returning entire database
✗ Outdated documentation
✗ No rate limiting
✗ Poor authentication