Try free
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

PrincipleDescriptionBenefit
ConsistencySame patternsLearnability
PredictabilityExpected behaviorLess bugs
EvolvabilityChange safelyLongevity
SimplicityEasy to useAdoption

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

  1. Consistency first — Same patterns everywhere
  2. Good error messages — Actionable, clear
  3. Version carefully — Avoid breaking changes
  4. Document well — OpenAPI spec
  5. 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