THEJORD LogoTHEJORD

REST API Best Practices 2025: Complete Guide

THEJORD Team••6 min read
apirestbackendsecuritydevelopment

REST API best practices in 2025: resource design, versioning, OAuth 2.1 authentication.

REST API Best Practices 2025: Complete Guide

Introduction to REST API Design

REST (Representational State Transfer) is the dominant architectural style for web APIs. Well-designed REST APIs are intuitive, consistent, and scalable. This guide covers best practices for designing and implementing REST APIs that developers love to use.

Whether you're building a new API or improving an existing one, these principles will help you create interfaces that are easy to understand, maintain, and evolve.

REST Fundamentals

Core Principles

  • Stateless: Each request contains all information needed to process it
  • Client-Server: Separation between UI and data storage
  • Cacheable: Responses indicate cacheability
  • Uniform Interface: Consistent resource naming and methods
  • Layered System: Client doesn't need to know if connected directly to server

Resources and URLs

Resources are the fundamental concept in REST. URLs should represent resources (nouns), not actions (verbs):

# Good: Resources as nouns
GET /users
GET /users/123
GET /users/123/orders
POST /orders

# Bad: Actions as verbs
GET /getUsers
POST /createUser
GET /getUserOrders?userId=123

HTTP Methods

Standard Methods

MethodPurposeIdempotentSafe
GETRetrieve resourceYesYes
POSTCreate resourceNoNo
PUTReplace resourceYesNo
PATCHPartial updateNoNo
DELETERemove resourceYesNo

Method Usage Examples

# Retrieve all users
GET /users

# Retrieve specific user
GET /users/123

# Create new user
POST /users
Content-Type: application/json
{"name": "John", "email": "john@example.com"}

# Replace entire user
PUT /users/123
Content-Type: application/json
{"name": "John Smith", "email": "john.smith@example.com"}

# Partial update
PATCH /users/123
Content-Type: application/json
{"email": "newemail@example.com"}

# Delete user
DELETE /users/123

URL Design Best Practices

Naming Conventions

# Use plural nouns for collections
/users (not /user)
/orders (not /order)

# Use lowercase with hyphens
/user-profiles (not /userProfiles or /user_profiles)

# Represent relationships in URLs
/users/123/orders
/orders/456/items

# Avoid deep nesting (max 2-3 levels)
# Bad: /users/123/orders/456/items/789/details
# Better: /order-items/789

Query Parameters

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

# Sorting
GET /users?sort=created_at&order=desc
GET /users?sort=-created_at  # Alternative: prefix with minus for desc

# Pagination
GET /users?page=2&limit=20
GET /users?offset=20&limit=20  # Alternative: offset-based

# Field selection
GET /users?fields=id,name,email

# Searching
GET /users?search=john
GET /users?q=john  # Common shorthand

HTTP Status Codes

Success Codes (2xx)

200 OK - Successful GET, PUT, PATCH, or DELETE
201 Created - Successful POST that created a resource
204 No Content - Successful DELETE with no response body

Client Error Codes (4xx)

400 Bad Request - Invalid request syntax or parameters
401 Unauthorized - Missing or invalid authentication
403 Forbidden - Authenticated but not authorized
404 Not Found - Resource doesn't exist
405 Method Not Allowed - HTTP method not supported for this resource
409 Conflict - Request conflicts with current state
422 Unprocessable Entity - Valid syntax but semantic errors
429 Too Many Requests - Rate limit exceeded

Server Error Codes (5xx)

500 Internal Server Error - Unexpected server error
502 Bad Gateway - Invalid response from upstream server
503 Service Unavailable - Server temporarily unavailable
504 Gateway Timeout - Upstream server didn't respond in time

Request and Response Design

Request Body

# POST /users
{
  "name": "John Smith",
  "email": "john@example.com",
  "role": "user"
}

# Don't include read-only fields in requests
# Bad: { "id": 123, "created_at": "..." }
# Server generates these automatically

Response Structure

# Single resource
{
  "id": 123,
  "name": "John Smith",
  "email": "john@example.com",
  "created_at": "2025-01-15T10:30:00Z"
}

# Collection with metadata
{
  "data": [
    {"id": 1, "name": "John"},
    {"id": 2, "name": "Jane"}
  ],
  "pagination": {
    "total": 100,
    "page": 1,
    "limit": 20,
    "pages": 5
  }
}

# Error response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request data",
    "details": [
      {"field": "email", "message": "Invalid email format"},
      {"field": "name", "message": "Name is required"}
    ]
  }
}

Pagination Patterns

Offset Pagination

GET /users?offset=0&limit=20   # Page 1
GET /users?offset=20&limit=20  # Page 2

# Response
{
  "data": [...],
  "pagination": {
    "offset": 20,
    "limit": 20,
    "total": 150
  }
}

Cursor Pagination

Better for large datasets and real-time data:

GET /users?limit=20
GET /users?cursor=eyJpZCI6MjB9&limit=20

# Response
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6NDB9",
    "has_more": true
  }
}

Link Headers (HATEOAS)

Link: </users?page=1>; rel="first",
      </users?page=2>; rel="prev",
      </users?page=4>; rel="next",
      </users?page=10>; rel="last"

Versioning

URL Versioning (Recommended)

GET /v1/users
GET /v2/users

Header Versioning

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

Query Parameter

GET /users?version=2

Versioning Strategy

  • Don't version too often—changes should be backward compatible when possible
  • Support old versions for a deprecation period
  • Communicate deprecation clearly with sunset headers

Authentication and Authorization

Bearer Token (JWT)

GET /users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

API Keys

# In header (preferred)
X-API-Key: your-api-key

# In query parameter (less secure)
GET /users?api_key=your-api-key

OAuth 2.0 Scopes

# Request specific permissions
Authorization: Bearer token-with-scope:read:users,write:users

Error Handling

Consistent Error Format

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "User with ID 123 not found",
    "request_id": "req_abc123",
    "documentation_url": "https://api.example.com/docs/errors#RESOURCE_NOT_FOUND"
  }
}

Validation Errors

# 422 Unprocessable Entity
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "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"
      }
    ]
  }
}

Rate Limiting

Response Headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 1640995200
Retry-After: 3600  # When limit exceeded (429 response)

Rate Limit Response

# 429 Too Many Requests
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 3600 seconds.",
    "retry_after": 3600
  }
}

Caching

Cache Headers

# Response headers
Cache-Control: max-age=3600, public
ETag: "abc123"
Last-Modified: Wed, 15 Jan 2025 10:30:00 GMT

# Conditional requests
If-None-Match: "abc123"        # Returns 304 if unchanged
If-Modified-Since: Wed, 15 Jan 2025 10:30:00 GMT

Cacheable Resources

# Highly cacheable
GET /products/123              # Static product data
GET /categories                # Rarely changes

# Don't cache
GET /users/me                  # User-specific
GET /notifications             # Real-time data
POST, PUT, DELETE              # State-changing

Documentation

OpenAPI (Swagger)

openapi: 3.0.0
info:
  title: Users API
  version: 1.0.0

paths:
  /users:
    get:
      summary: List users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
      responses:
        '200':
          description: List of users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email

Testing APIs

Validate your API responses with proper tools:

Security Best Practices

  • Always use HTTPS
  • Validate all input on the server
  • Use parameterized queries to prevent SQL injection
  • Implement rate limiting
  • Don't expose sensitive data in URLs (use headers/body)
  • Return minimal error information in production
  • Use CORS properly to restrict origins

Performance Tips

  • Use pagination for large collections
  • Support field selection to reduce payload size
  • Implement ETag/conditional requests
  • Use compression (gzip, brotli)
  • Consider GraphQL for complex data requirements

Conclusion

Well-designed REST APIs follow consistent patterns that make them predictable and easy to use. By applying these best practices—proper URL design, meaningful status codes, consistent error handling, and thorough documentation—you'll create APIs that developers enjoy integrating with.

Key takeaways:

  • Use nouns for resources, HTTP methods for actions
  • Be consistent with naming and response formats
  • Provide meaningful error messages
  • Document everything with OpenAPI
  • Implement proper authentication and rate limiting

For more developer resources, explore our free online tools. For comprehensive API design guidance, see RESTful API design principles and the OpenAPI specification.