REST API Best Practices

Design patterns and guidelines for building scalable, maintainable APIs

Last Updated:

URL Design

Well-designed URLs are intuitive, predictable, and follow REST conventions.

๐Ÿ”— Use Nouns for Resources

URLs should represent resources (things), not actions. Use HTTP methods to define the action.

โœ… Good GET /users - Get all users
GET /users/123 - Get user 123
POST /users - Create a user
DELETE /users/123 - Delete user 123
โŒ Bad GET /getUsers
POST /createUser
GET /deleteUser/123
POST /users/delete

๐Ÿ“ Use Plural Nouns

Consistently use plural nouns for resource collections, even when accessing a single resource.

โœ… Good /users
/users/123
/products
/orders/456/items
โŒ Bad /user
/user/123
/product
/order/456/item

๐Ÿ—๏ธ Use Kebab-Case

Use lowercase letters and hyphens for multi-word resource names.

โœ… Good /user-profiles
/order-items
/api-keys
โŒ Bad /userProfiles
/UserProfiles
/user_profiles

๐Ÿ”„ Use Hierarchical Relationships

Express relationships between resources through URL hierarchy.

โœ… Good GET /users/123/orders - User's orders
GET /orders/456/items - Order's items
GET /posts/789/comments - Post's comments
โŒ Bad GET /getOrdersByUser?userId=123
GET /orderItems?orderId=456

URL Design Anti-Patterns

Avoid these common URL design mistakes that make APIs harder to use and maintain.

Anti-Pattern Reference Table

Anti-Pattern Bad Example Better Alternative
Verbs in URLs POST /createUser POST /users
Singular resource names GET /user/123 GET /users/123
Mixed case or underscores GET /UserProfiles / /user_profiles GET /user-profiles
Deeply nested resources GET /users/1/orders/2/items/3/reviews GET /reviews?item_id=3
File extensions in URLs GET /users.json Accept: application/json header
Encoding actions in query params POST /users?action=delete DELETE /users/123
Inconsistent ID formats /users/123 vs /orders/ORD-456 Pick one format (numeric or slug) and apply consistently

The "Too Deep" Nesting Problem

Nesting beyond two levels creates brittle URLs that are hard to refactor. When you need to access a sub-resource directly, prefer a flat URL with a filter parameter:

Avoid GET /companies/1/departments/2/teams/3/members/4/tasks
Prefer GET /tasks?member_id=4
GET /members/4/tasks

As a rule of thumb: limit nesting to two levels (/resource/{id}/sub-resource) and use query parameters for deeper filtering.

API Versioning

Version your APIs to allow evolution without breaking existing clients. See our comprehensive versioning guide for detailed strategies.

URL Path Versioning

Include the version in the URL path. Most explicit and widely used approach.

GET /api/v1/users
GET /api/v2/users

Pros: Clear, cacheable, easy to implement

Cons: Not technically RESTful (versions aren't resources)

Header Versioning

Use a custom header to specify the API version.

GET /api/users
API-Version: 2

Pros: Clean URLs, more RESTful

Cons: Less visible, harder to test in browser

Query Parameter Versioning

Pass the version as a query parameter.

GET /api/users?version=2

Pros: Easy to add, explicit

Cons: Can be omitted, caching issues

Accept Header Versioning

Use content negotiation with the Accept header.

GET /api/users
Accept: application/vnd.api.v2+json

Pros: Most RESTful approach

Cons: Complex, easy to get wrong

Pagination

Always paginate large collections to improve performance and usability. See our pagination guide for cursor, offset, and keyset strategies.

Offset-Based Pagination

Use page and limit (or offset) parameters.

GET /api/users?page=2&limit=20
{
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 150,
    "total_pages": 8,
    "has_next": true,
    "has_prev": true
  },
  "links": {
    "self": "/api/users?page=2&limit=20",
    "first": "/api/users?page=1&limit=20",
    "prev": "/api/users?page=1&limit=20",
    "next": "/api/users?page=3&limit=20",
    "last": "/api/users?page=8&limit=20"
  }
}

Pros: Simple, allows jumping to any page

Cons: Performance degrades with large offsets, inconsistent with data changes

Cursor-Based Pagination

Use an opaque cursor for more efficient pagination.

GET /api/users?cursor=eyJpZCI6MTAwfQ&limit=20
{
  "data": [...],
  "pagination": {
    "limit": 20,
    "has_more": true,
    "next_cursor": "eyJpZCI6MTIwfQ",
    "prev_cursor": "eyJpZCI6ODB9"
  }
}

Pros: Consistent performance, handles real-time data

Cons: Can't jump to arbitrary pages

Filtering, Sorting & Field Selection

๐Ÿ” Filtering

Use query parameters for filtering resources.

GET /api/users?status=active&role=admin&created_after=2023-01-01

For complex queries, consider:

  • ?filter[status]=active - Bracketed notation
  • ?q=search+term - Full-text search
  • ?price_gte=100&price_lte=500 - Range filters

๐Ÿ“Š Sorting

Use a sort parameter with field names and direction.

GET /api/users?sort=-created_at,name

Common conventions:

  • sort=name - Ascending by name
  • sort=-name - Descending by name (minus prefix)
  • sort=name:asc - Explicit direction
  • sort=-created_at,name - Multiple fields

๐Ÿ“‹ Field Selection (Sparse Fieldsets)

Allow clients to request only specific fields to reduce payload size.

GET /api/users?fields=id,name,email
{
  "data": [
    {"id": 1, "name": "John", "email": "john@example.com"},
    {"id": 2, "name": "Jane", "email": "jane@example.com"}
  ]
}

Error Handling

Consistent error responses help clients understand and handle failures gracefully. See our error handling guide for standard formats and best practices.

Standard Error Response Format

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request could not be validated",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Email must be a valid email address"
      },
      {
        "field": "age",
        "code": "OUT_OF_RANGE",
        "message": "Age must be between 18 and 120"
      }
    ],
    "request_id": "abc123-def456",
    "timestamp": "2023-06-20T14:30:00Z",
    "documentation_url": "https://api.example.com/docs/errors#VALIDATION_ERROR"
  }
}

๐ŸŽฏ Use Appropriate Status Codes

Match HTTP status codes to error types. 4xx for client errors, 5xx for server errors.

๐Ÿ“ Provide Actionable Messages

Error messages should help developers understand what went wrong and how to fix it.

๐Ÿ”— Include Documentation Links

Link to relevant documentation for common errors to help developers resolve issues quickly.

๐Ÿ” Include Request IDs

Return a unique request ID for tracking and debugging purposes.

Authentication & Security

๐Ÿ”‘ API Keys

Simple authentication for server-to-server communication.

X-API-Key: your-api-key-here

Best for: Public APIs, simple integrations

๐ŸŽซ JWT (Bearer Tokens)

Self-contained tokens for stateless authentication.

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Best for: User authentication, mobile apps

๐Ÿ” OAuth 2.0

Industry standard for delegated authorization.

Best for: Third-party integrations, user consent flows

Security Best Practices

๐Ÿ”’ Always Use HTTPS

Never transmit sensitive data over plain HTTP. Enforce HTTPS everywhere.

โฑ๏ธ Rate Limiting

Protect your API from abuse with rate limits. Return 429 when exceeded.

โœ… Input Validation

Validate all input data. Never trust client input.

๐Ÿ›ก๏ธ CORS Configuration

Configure CORS headers appropriately for browser clients.

Response Design

๐Ÿ“ฆ Consistent Response Envelope

Wrap responses in a consistent structure.

// Success response
{
  "data": {...},
  "meta": {
    "request_id": "abc123",
    "timestamp": "2023-06-20T14:30:00Z"
  }
}

// Collection response
{
  "data": [...],
  "pagination": {...},
  "meta": {...}
}

// Error response
{
  "error": {...}
}

๐Ÿ• Use ISO 8601 for Dates

Always use ISO 8601 format with timezone for dates.

โœ… Good "2023-06-20T14:30:00Z"
"2023-06-20T14:30:00+02:00"
โŒ Bad "June 20, 2023"
"20/06/2023"
1687271400 (Unix timestamp without context)

๐Ÿ Use camelCase or snake_case Consistently

Pick one naming convention and stick with it throughout your API.

โœ… snake_case {"user_id": 1, "created_at": "..."}
โœ… camelCase {"userId": 1, "createdAt": "..."}
โŒ Mixed {"userId": 1, "created_at": "..."}

Documentation

Good documentation is essential for API adoption and developer experience.

๐Ÿ“– OpenAPI/Swagger

Use OpenAPI specification to document your API. It enables automated documentation, client generation, and testing.

๐Ÿ’ก Include Examples

Provide complete request and response examples for every endpoint.

๐Ÿงช Interactive Testing

Allow developers to test API calls directly from the documentation.

๐Ÿ“ Keep It Updated

Documentation should be versioned and updated alongside your API code.

Frequently Asked Questions

Should REST API URLs use nouns or verbs?

URLs should use nouns to represent resources. HTTP methods (GET, POST, PUT, DELETE, PATCH) express the action. So POST /users creates a user โ€” not POST /createUser. Using verbs in URLs duplicates information already conveyed by the HTTP method and makes the API inconsistent.

What is the best API versioning strategy?

URL path versioning (e.g., /api/v1/users) is the most practical choice for most APIs. It is explicit, easy to test in a browser, and works seamlessly with caches and proxies. Header versioning and content negotiation are theoretically more RESTful but are harder for consumers to discover and debug.

Cursor pagination vs. offset pagination โ€” which should I use?

Use cursor-based pagination for large or frequently-changing datasets: it provides consistent O(1) performance and avoids the "skip N rows" problem. Use offset pagination when clients need to jump to an arbitrary page or the dataset is small and relatively static. Many APIs offer both.