API Design Principles: Creating Developer-Friendly REST APIs

API Design Principles: Creating Developer-Friendly REST APIs

BySanjay Goraniya
3 min read
Share:

API Design Principles: Creating Developer-Friendly REST APIs

A well-designed API is a joy to use. A poorly designed one is a constant source of frustration. After designing and consuming dozens of APIs, I've learned what makes an API great. Let me share the principles that have proven most valuable.

The Goal: Developer Experience

A good API should be:

  • Intuitive - Developers can guess how to use it
  • Consistent - Same patterns throughout
  • Well-documented - Clear examples and explanations
  • Reliable - Predictable behavior
  • Fast - Quick responses

URL Design

Use Nouns, Not Verbs

Code
# Bad
GET /getUsers
POST /createUser
PUT /updateUser
DELETE /deleteUser

# Good
GET /users
POST /users
PUT /users/:id
DELETE /users/:id

Use Plural Nouns

Code
# Good
GET /users
GET /orders
GET /products

# Avoid
GET /user
GET /order
GET /product

Use Hierarchical Structure

Code
# Good: Hierarchical
GET /users/:userId/orders
GET /users/:userId/orders/:orderId/items

# Avoid: Flat
GET /orders?userId=123
GET /order-items?orderId=456

Use Hyphens, Not Underscores

Code
# Good
GET /user-profiles
GET /order-items

# Avoid
GET /user_profiles
GET /orderItems

HTTP Methods

Use Methods Correctly

  • GET - Retrieve resources (idempotent, safe)
  • POST - Create resources (not idempotent)
  • PUT - Update/replace resources (idempotent)
  • PATCH - Partial updates (idempotent)
  • DELETE - Delete resources (idempotent)

Idempotency

Code
# PUT is idempotent - same request, same result
PUT /users/123
{ "name": "John" }

# Multiple requests = same result
PUT /users/123
{ "name": "John" }

# POST is not idempotent - creates new resource each time
POST /users
{ "name": "John" }
# Creates user with id 1

POST /users
{ "name": "John" }
# Creates user with id 2

Status Codes

Use Appropriate Status Codes

Code
# Success
200 OK - Successful GET, PUT, PATCH
201 Created - Successful POST
204 No Content - Successful DELETE

# Client Errors
400 Bad Request - Invalid request
401 Unauthorized - Not authenticated
403 Forbidden - Not authorized
404 Not Found - Resource doesn't exist
409 Conflict - Resource conflict
422 Unprocessable Entity - Validation error

# Server Errors
500 Internal Server Error - Server error
503 Service Unavailable - Service down

Consistent Error Format

Code
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      }
    ]
  }
}

Request/Response Format

Use JSON

Code
Content-Type: application/json

{
  "name": "John Doe",
  "email": "john@example.com"
}

Consistent Response Structure

Code
// Single resource
{
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com"
  }
}

// Collection
{
  "data": [
    { "id": "1", "name": "John" },
    { "id": "2", "name": "Jane" }
  ],
  "meta": {
    "total": 100,
    "page": 1,
    "perPage": 20
  }
}

Pagination

Code
GET /users?cursor=eyJpZCI6IjEyMyJ9&limit=20

{
  "data": [...],
  "pagination": {
    "nextCursor": "eyJpZCI6IjE0MyJ9",
    "hasMore": true
  }
}

Offset-Based Pagination

Code
GET /users?page=1&perPage=20

{
  "data": [...],
  "meta": {
    "page": 1,
    "perPage": 20,
    "total": 100,
    "totalPages": 5
  }
}

Filtering, Sorting, Searching

Consistent Query Parameters

Code
# Filtering
GET /users?status=active&role=admin

# Sorting
GET /users?sort=name&order=asc

# Searching
GET /users?search=john

# Combining
GET /users?status=active&sort=created_at&order=desc&limit=10

Versioning

Code
GET /v1/users
GET /v2/users

Header Versioning

Code
GET /users
Accept: application/vnd.api+json;version=1

Authentication

Use Standard Headers

Code
Authorization: Bearer <token>

Return 401 for Invalid Tokens

Code
# Invalid token
GET /users
Authorization: Bearer invalid-token

# Response
401 Unauthorized
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or expired token"
  }
}

Rate Limiting

Communicate Limits

Code
GET /users

# Response headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1609459200

Return 429 When Exceeded

Code
429 Too Many Requests
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded",
    "retryAfter": 60
  }
}

Documentation

Use OpenAPI/Swagger

Code
openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users:
    get:
      summary: List users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
      responses:
        '200':
          description: Success

Provide Examples

Code
{
  "example": {
    "name": "John Doe",
    "email": "john@example.com"
  }
}

Error Handling

Detailed Error Messages

Code
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format",
        "value": "invalid-email"
      }
    ]
  }
}

Don't Expose Internals

Code
// Bad
{
  "error": "SQLException: Connection timeout at line 123"
}

// Good
{
  "error": {
    "code": "DATABASE_ERROR",
    "message": "Unable to process request. Please try again later."
  }
}

Real-World Example

API: E-commerce REST API

Design Decisions:

  1. URL Structure: /v1/products, /v1/orders, /v1/users
  2. Pagination: Cursor-based for large datasets
  3. Filtering: Query parameters (?status=active&category=electronics)
  4. Error Format: Consistent error object with code and message
  5. Versioning: URL-based (/v1/, /v2/)
  6. Documentation: OpenAPI with examples
  7. Rate Limiting: 1000 requests/hour per API key

Result:

  • Developer adoption: High
  • Support requests: Low
  • Integration time: Reduced by 50%

Best Practices Summary

  1. Be consistent - Same patterns throughout
  2. Use RESTful conventions - Follow HTTP standards
  3. Version your API - Allow evolution
  4. Document thoroughly - Examples are key
  5. Handle errors gracefully - Clear error messages
  6. Respect HTTP semantics - Use methods correctly
  7. Design for change - APIs evolve
  8. Think about developers - Make it easy to use

Conclusion

Good API design is about empathy—understanding how developers will use your API and making it as easy as possible. The principles I've shared are starting points, but the most important thing is to:

  • Get feedback - Ask developers what they think
  • Iterate - APIs improve over time
  • Document - Good docs are essential
  • Test - Use your own API

Remember: A well-designed API is invisible—developers can focus on building features, not fighting with your API.

What API design challenges have you faced? What patterns have worked best for your APIs?

Share:

Related Posts