Post

🌊 REST API: Deep Dive & Best Practices

Concise, clear, and validated revision notes on REST API β€” practical best practices for beginners and practitioners.

🌊 REST API: Deep Dive & Best Practices

Table of Contents

  1. πŸ“š Introduction
    1. What is REST?
    2. Why REST?
  2. πŸ—οΈ REST Architectural Constraints
    1. 1. Client-Server Architecture
    2. 2. Statelessness
    3. 3. Cacheable
    4. 4. Uniform Interface
      1. 4.1 Resource Identification in Requests
      2. 4.2 Manipulation Through Representations
      3. 4.3 Self-Descriptive Messages
      4. 4.4 HATEOAS (Hypermedia as the Engine of Application State)
    5. 5. Layered System
    6. 6. Code on Demand (Optional)
  3. πŸ“Š Richardson Maturity Model
    1. Level 0: The Swamp of POX (Plain Old XML)
    2. Level 1: Resources
    3. Level 2: HTTP Verbs
    4. Level 3: Hypermedia Controls (HATEOAS)
  4. πŸ”€ HTTP Methods and Status Codes
    1. HTTP Methods (Verbs)
      1. GET - Retrieve Resource
      2. POST - Create Resource
      3. PUT - Update/Replace Resource
      4. PATCH - Partial Update
      5. DELETE - Remove Resource
    2. HTTP Status Codes
      1. 2xx Success Codes
      2. 3xx Redirection Codes
      3. 4xx Client Error Codes
      4. 5xx Server Error Codes
  5. 🎯 REST API Design Best Practices
    1. 1. Use Nouns for Resources, Not Verbs
    2. 2. Use Plural Nouns for Collections
    3. 3. Use Hierarchical Structure for Relationships
    4. 4. Use Query Parameters for Filtering, Sorting, and Pagination
    5. 5. Use Consistent Naming Conventions
    6. 6. Provide Filtering for Sub-Resources
    7. 7. Use Standard HTTP Methods Correctly
    8. 8. Accept and Respond with JSON
    9. 9. Handle Errors Gracefully
    10. 10. Use Proper MIME Types
  6. πŸ” REST API Security
    1. Authentication vs Authorization
    2. Authentication Methods
      1. 1. Basic Authentication
      2. 2. API Keys
      3. 3. JWT (JSON Web Tokens)
      4. 4. OAuth 2.0
    3. Authorization Strategies
      1. Role-Based Access Control (RBAC)
      2. Attribute-Based Access Control (ABAC)
    4. Security Best Practices
      1. 1. Always Use HTTPS
      2. 2. Validate Input
      3. 3. Implement Rate Limiting
      4. 4. Use CORS Properly
      5. 5. Implement Security Headers
      6. 6. Don’t Expose Sensitive Information
      7. 7. SQL Injection Prevention
      8. 8. Implement Logging and Monitoring
  7. πŸ“– API Versioning
    1. Why Version Your API?
    2. Versioning Strategies
      1. 1. URI Versioning (Most Common)
      2. 2. Header Versioning
      3. 3. Query Parameter Versioning
      4. 4. Content Negotiation Versioning
    3. Versioning Best Practices
  8. πŸ“ API Documentation
    1. What to Document
    2. OpenAPI Specification (formerly Swagger)
    3. Documentation Tools
      1. Swagger UI
      2. Postman
      3. ReDoc
  9. 🎨 API Design Patterns
    1. 1. Pagination Patterns
      1. Offset-Based Pagination
      2. Cursor-Based Pagination
      3. Page-Based Pagination
    2. 2. Filtering and Searching
      1. Simple Filtering
      2. Advanced Filtering (Query Language)
      3. Full-Text Search
    3. 3. Sorting
    4. 4. Field Selection (Sparse Fieldsets)
    5. 5. Resource Expansion
    6. 6. Bulk Operations
      1. Bulk Create
      2. Bulk Update
    7. 7. Async Operations
  10. πŸ“Š API Lifecycle Terminology Tables
    1. Table 1: Lifecycle Phases - Different Terminologies
    2. Table 2: Hierarchical Differentiation of Lifecycle Stages
    3. Table 3: Cross-Reference - Industry Standard Terminology
  11. πŸ”„ Complete REST API Implementation Example
    1. Project Structure
    2. Database Models
    3. Middleware Implementation
    4. Controllers
    5. Routes
    6. Main Application
    7. Environment Configuration
  12. πŸ§ͺ Testing REST APIs
    1. Unit Testing
    2. Integration Testing
  13. πŸ“ˆ Performance Optimization
    1. 1. Database Query Optimization
    2. 2. Caching
    3. 3. Response Compression
    4. 4. Pagination Best Practices
  14. πŸ” API Monitoring and Analytics
    1. Key Metrics to Track
    2. Logging Best Practices
  15. 🎯 REST API Best Practices Summary
    1. Design Principles
    2. Common Pitfalls to Avoid
  16. πŸ“š Additional Resources and Tools
    1. Development Tools
    2. Security Tools
    3. Code Examples in Different Languages
      1. Python (Flask)
      2. Java (Spring Boot)
      3. Go (Gin Framework)
  17. πŸŽ“ Learning Path and Next Steps
    1. Beginner Level
    2. Intermediate Level
    3. Advanced Level
  18. 🎯 Conclusion
  19. πŸ“– References

πŸ“š Introduction

REST (Representational State Transfer) has become the de facto standard for designing web APIs in modern software development. Understanding REST principles, architectural constraints, and best practices is crucial for building scalable, maintainable, and developer-friendly APIs.

This comprehensive guide covers everything from fundamental concepts to advanced implementation techniques, providing both theoretical knowledge and practical insights for mastering REST API design.

What is REST?

REST is an architectural style for designing networked applications, introduced by Roy Fielding in his doctoral dissertation in 2000. It provides a set of constraints and principles that, when followed, enable the creation of scalable, stateless, and loosely coupled web services.

Key Point: REST is not a protocol or standardβ€”it’s an architectural style. During development, REST can be implemented in various ways while adhering to its core principles.

REST API Design Illustrated REST API Design Illustrated

Why REST?

REST APIs have gained widespread adoption due to several compelling advantages:

  • Language Agnostic: Works with any programming language that supports HTTP
  • Stateless Communication: Each request contains all necessary information
  • Cacheable Responses: Built-in support for HTTP caching mechanisms
  • Scalability: Stateless nature enables horizontal scaling
  • Simplicity: Leverages standard HTTP methods and status codes
  • Platform Independence: Client and server can evolve independently

πŸ—οΈ REST Architectural Constraints

Roy Fielding defined six architectural constraints that characterize a RESTful system. Following these constraints ensures desirable non-functional properties such as performance, scalability, simplicity, modifiability, visibility, portability, and reliability.

1. Client-Server Architecture

Principle: Separation of concerns between the client and server.

The client-server constraint mandates that the user interface concerns are separated from data storage concerns. This separation:

  • Improves Portability: The user interface can work across multiple platforms
  • Enhances Scalability: Server components can scale independently
  • Enables Independent Evolution: Client and server can be developed and deployed independently
1
2
3
4
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Client  β”‚ ◄─── HTTP ────────► β”‚ Server  β”‚
β”‚  (UI)   β”‚                    β”‚  (API)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example:

  • Client: React single-page application
  • Server: Node.js REST API
  • They communicate via HTTP, but can be updated independently

2. Statelessness

Principle: Each request from client to server must contain all necessary information.

The server should not store any client context between requests. Session state is kept entirely on the client side.

Characteristics:

  • Server treats each request independently
  • No session information stored on the server
  • Request contains all information needed for processing
  • Scalability improves as servers don’t manage session state

Example Request (Stateless):

1
2
3
4
GET /api/users/123/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/json

Advantage: Any server can handle any request, enabling seamless load balancing and horizontal scaling.

3. Cacheable

Principle: Responses must explicitly indicate whether they can be cached.

REST allows responses to define themselves as cacheable or non-cacheable, enabling clients to reuse response data for equivalent requests within a specified period.

Cache-Control Headers:

1
2
3
4
5
6
7
8
# Cacheable for 1 hour
Cache-Control: public, max-age=3600

# Not cacheable
Cache-Control: no-cache, no-store, must-revalidate

# Private cache (user-specific)
Cache-Control: private, max-age=600

Benefits:

  • Reduces server load
  • Improves response times
  • Decreases bandwidth usage
  • Partially or completely eliminates some client-server interactions

4. Uniform Interface

Principle: A consistent, standardized way of interacting with resources.

The uniform interface constraint is fundamental to REST design and simplifies the overall system architecture by improving the visibility of interactions. This constraint is achieved through four sub-constraints:

4.1 Resource Identification in Requests

Each resource must be uniquely identified in requests using URIs (Uniform Resource Identifiers).

Examples:

1
2
3
https://api.example.com/users/123
https://api.example.com/products/456
https://api.example.com/orders/789/items

4.2 Manipulation Through Representations

Resources should have uniform representations (JSON, XML, etc.) that contain enough information to modify the resource state on the server.

Example JSON Representation:

1
2
3
4
5
6
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "role": "admin"
}

4.3 Self-Descriptive Messages

Each resource representation should carry enough information to describe how to process the message and provide information on additional actions the client can perform.

Example:

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 142
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 15 Nov 2025 12:45:26 GMT

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

4.4 HATEOAS (Hypermedia as the Engine of Application State)

The client should have only the initial URI and should dynamically drive all other resources and interactions through hyperlinks provided in responses.

Example with HATEOAS:

1
2
3
4
5
6
7
8
9
10
11
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "_links": {
    "self": {"href": "/users/123"},
    "orders": {"href": "/users/123/orders"},
    "edit": {"href": "/users/123", "method": "PUT"},
    "delete": {"href": "/users/123", "method": "DELETE"}
  }
}

5. Layered System

Principle: Architecture composed of hierarchical layers.

Each component cannot see beyond the immediate layer with which it is interacting. A client cannot ordinarily tell whether it is connected directly to the end server or to an intermediary.

1
2
3
4
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Client  │───►│   Load   │───►│   API    │───►│ Database β”‚
β”‚          β”‚    β”‚ Balancer β”‚    β”‚  Server  β”‚    β”‚          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Benefits:

  • Improved security through layer isolation
  • Load balancing capabilities
  • Shared caching at intermediary layers
  • Legacy system encapsulation

Example Layers:

  • Authentication Server
  • API Gateway
  • Application Server
  • Database Server
  • Caching Layer

6. Code on Demand (Optional)

Principle: Servers can temporarily extend client functionality by transferring executable code.

This is the only optional constraint. Servers can send executable code (JavaScript, applets) to clients.

Example:

1
2
3
4
5
6
7
8
9
{
  "widget": {
    "type": "chart",
    "script": "https://cdn.example.com/chart-widget.js",
    "config": {
      "data": [10, 20, 30, 40]
    }
  }
}

Note: This constraint is rarely used in modern REST APIs due to security concerns and increased complexity.


πŸ“Š Richardson Maturity Model

The Richardson Maturity Model (RMM), developed by Leonard Richardson, provides a framework for evaluating the maturity of web services in terms of their adherence to RESTful principles. It consists of four levels (0-3), with each higher level representing more complete adherence to REST design.

graph TD
    A[Level 0: The Swamp of POX] --> B[Level 1: Resources]
    B --> C[Level 2: HTTP Verbs]
    C --> D[Level 3: Hypermedia Controls]
    
    style A fill:#ff6b6b
    style B fill:#ffd93d
    style C fill:#6bcf7f
    style D fill:#4d96ff

Level 0: The Swamp of POX (Plain Old XML)

Level zero represents the most basic form of service-oriented applications, using a single URI and a single HTTP method (typically POST).

Characteristics:

  • Single endpoint for all operations
  • HTTP used only as transport mechanism
  • Operation details in request body
  • Similar to SOAP and XML-RPC

Example:

1
2
3
4
5
6
7
POST /api HTTP/1.1
Content-Type: application/json

{
  "action": "getUserDetails",
  "userId": 123
}

Level 1: Resources

Level one introduces resources and allows requests for individual URIs instead of exposing one universal endpoint.

Characteristics:

  • Multiple resource-specific URIs
  • Still primarily uses POST
  • Resources have unique identifiers
  • Better organization than Level 0

Example:

1
2
3
4
5
6
POST /users/123 HTTP/1.1
Content-Type: application/json

{
  "action": "getDetails"
}

Level 2: HTTP Verbs

Level 2 introduces using HTTP verbs and HTTP response codes correctly, which is the most popular implementation of REST principles.

Characteristics:

  • Proper use of HTTP methods (GET, POST, PUT, DELETE, PATCH)
  • Meaningful HTTP status codes
  • Multiple resources with appropriate verbs
  • Standard HTTP conventions

Example:

1
2
3
4
5
6
7
8
9
10
GET /users/123 HTTP/1.1
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 123,
  "name": "John Doe"
}
HTTP MethodOperationExample
GETRetrieveGET /users/123
POSTCreatePOST /users
PUTUpdate/ReplacePUT /users/123
PATCHPartial UpdatePATCH /users/123
DELETERemoveDELETE /users/123

Level 3: Hypermedia Controls (HATEOAS)

Level three represents the pinnacle of RESTful maturity, fully embracing URIs, HTTP methods, and HATEOAS.

Characteristics:

  • Responses include hypermedia links
  • Clients discover actions dynamically
  • Self-descriptive API
  • True RESTful implementation

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "status": "active",
  "_links": {
    "self": {
      "href": "/users/123"
    },
    "orders": {
      "href": "/users/123/orders"
    },
    "deactivate": {
      "href": "/users/123/deactivate",
      "method": "POST"
    },
    "update": {
      "href": "/users/123",
      "method": "PUT"
    }
  }
}

Benefits of Level 3:

  • Reduced coupling between client and server
  • API evolution without breaking clients
  • Improved discoverability
  • Self-documenting capabilities

Reality Check: While Level 3 is theoretically ideal, most production APIs operate at Level 2. Level 3 requires significant implementation effort and isn’t always necessary for practical applications.


πŸ”€ HTTP Methods and Status Codes

HTTP Methods (Verbs)

REST APIs use HTTP methods to indicate the action to be performed on resources. Understanding proper usage is crucial for RESTful design.

GET - Retrieve Resource

Purpose: Retrieve resource representation without modifying it.

Properties:

  • Safe: Does not modify server state
  • Idempotent: Multiple identical requests have the same effect
  • Cacheable: Responses can be cached

Examples:

1
2
3
4
5
6
7
8
# Get all users
GET /api/users HTTP/1.1

# Get specific user
GET /api/users/123 HTTP/1.1

# Get with query parameters
GET /api/users?role=admin&status=active HTTP/1.1

Response:

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Content-Type: application/json

{
  "users": [
    {"id": 123, "name": "John Doe"},
    {"id": 124, "name": "Jane Smith"}
  ],
  "total": 2,
  "page": 1
}

POST - Create Resource

Purpose: Create a new resource.

Properties:

  • Not Safe: Modifies server state
  • Not Idempotent: Multiple requests create multiple resources
  • Not Cacheable: Responses typically not cached

Examples:

1
2
3
4
5
6
7
8
POST /api/users HTTP/1.1
Content-Type: application/json

{
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "role": "user"
}

Response:

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 201 Created
Location: /api/users/125
Content-Type: application/json

{
  "id": 125,
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "role": "user",
  "createdAt": "2025-11-17T10:30:00Z"
}

PUT - Update/Replace Resource

Purpose: Replace entire resource or create if doesn’t exist.

Properties:

  • Not Safe: Modifies server state
  • Idempotent: Multiple identical requests have same effect
  • Not Cacheable: Responses not cached

Examples:

1
2
3
4
5
6
7
8
PUT /api/users/123 HTTP/1.1
Content-Type: application/json

{
  "name": "John Updated",
  "email": "john.new@example.com",
  "role": "admin"
}

Response:

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 123,
  "name": "John Updated",
  "email": "john.new@example.com",
  "role": "admin",
  "updatedAt": "2025-11-17T10:35:00Z"
}

PATCH - Partial Update

Purpose: Partially update a resource.

Properties:

  • Not Safe: Modifies server state
  • Not Idempotent: Depends on implementation
  • Not Cacheable: Responses not cached

Examples:

1
2
3
4
5
6
PATCH /api/users/123 HTTP/1.1
Content-Type: application/json

{
  "email": "john.new2@example.com"
}

Response:

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 123,
  "name": "John Updated",
  "email": "john.new2@example.com",
  "role": "admin",
  "updatedAt": "2025-11-17T10:40:00Z"
}

DELETE - Remove Resource

Purpose: Delete a resource.

Properties:

  • Not Safe: Modifies server state
  • Idempotent: Multiple identical requests have same effect
  • Not Cacheable: Responses not cached

Examples:

1
DELETE /api/users/123 HTTP/1.1

Response:

1
HTTP/1.1 204 No Content

HTTP Status Codes

HTTP status codes provide a standardized way for the server to communicate the outcome of a request to the client.

2xx Success Codes

CodeNameUsage
200OKSuccessful GET, PUT, PATCH request with response body
201CreatedSuccessful POST request creating new resource
202AcceptedRequest accepted for processing (async operations)
204No ContentSuccessful operation with no response body (DELETE)
206Partial ContentPartial GET request (range requests)

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 200 OK
HTTP/1.1 200 OK
Content-Type: application/json

{"id": 123, "name": "John"}

# 201 Created
HTTP/1.1 201 Created
Location: /api/users/125
Content-Type: application/json

{"id": 125, "name": "Alice"}

# 204 No Content
HTTP/1.1 204 No Content

3xx Redirection Codes

CodeNameUsage
301Moved PermanentlyResource permanently moved to new URI
302FoundTemporary redirect
304Not ModifiedCached version still valid (conditional GET)

4xx Client Error Codes

4xx status codes indicate that the client made an error in the request.

CodeNameUsage
400Bad RequestMalformed request syntax or invalid parameters
401UnauthorizedAuthentication required or failed
403ForbiddenAuthenticated but not authorized
404Not FoundResource does not exist
405Method Not AllowedHTTP method not supported for resource
406Not AcceptableRequested media type not supported
409ConflictRequest conflicts with current state
410GoneResource permanently deleted
422Unprocessable EntityValidation errors
429Too Many RequestsRate limit exceeded

Error Response Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Email address is not properly formatted"
      },
      {
        "field": "age",
        "message": "Age must be a positive integer"
      }
    ],
    "timestamp": "2025-11-17T10:45:00Z",
    "path": "/api/users"
  }
}

5xx Server Error Codes

5xx status codes indicate that something went wrong on the server side.

CodeNameUsage
500Internal Server ErrorGeneric server error
501Not ImplementedServer doesn’t support requested functionality
502Bad GatewayInvalid response from upstream server
503Service UnavailableServer temporarily unavailable
504Gateway TimeoutUpstream server timeout

🎯 REST API Design Best Practices

1. Use Nouns for Resources, Not Verbs

In RESTful API design, it’s recommended to use nouns instead of verbs in endpoints, with HTTP methods indicating the action.

Good:

1
2
3
4
5
GET    /users
POST   /users
GET    /users/123
PUT    /users/123
DELETE /users/123

Bad:

1
2
3
4
5
GET    /getUsers
POST   /createUser
GET    /getUserById/123
POST   /updateUser/123
POST   /deleteUser/123

2. Use Plural Nouns for Collections

Use plural nouns to represent collections of resources.

Good:

1
2
3
/users
/products
/orders

Bad:

1
2
3
/user
/product
/order

3. Use Hierarchical Structure for Relationships

Design endpoints to reflect resource relationships through URI structure.

Examples:

1
2
3
4
5
6
7
8
9
10
11
# User's orders
GET /users/123/orders

# Specific order of a user
GET /users/123/orders/456

# Comments on a blog post
GET /posts/789/comments

# Specific comment
GET /posts/789/comments/101

4. Use Query Parameters for Filtering, Sorting, and Pagination

Filtering:

1
2
3
GET /users?role=admin
GET /products?category=electronics&price_min=100&price_max=500
GET /orders?status=pending&created_after=2025-11-01

Sorting:

1
2
3
GET /users?sort=name
GET /users?sort=-created_at  # Descending
GET /products?sort=price,name  # Multiple fields

Pagination:

1
2
3
4
5
6
7
8
# Offset-based
GET /users?limit=20&offset=40

# Page-based
GET /users?page=3&per_page=20

# Cursor-based (recommended for large datasets)
GET /users?cursor=eyJpZCI6MTIzfQ==&limit=20

Response with Pagination:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "data": [
    {"id": 123, "name": "John"},
    {"id": 124, "name": "Jane"}
  ],
  "pagination": {
    "total": 1000,
    "page": 3,
    "per_page": 20,
    "total_pages": 50,
    "next": "/users?page=4&per_page=20",
    "prev": "/users?page=2&per_page=20"
  }
}

5. Use Consistent Naming Conventions

URL Conventions:

  • Use lowercase letters
  • Use hyphens (-) for readability, not underscores
  • Keep URLs short and intuitive

Good:

1
2
3
/api/v1/user-profiles
/api/v1/shopping-carts
/api/v1/payment-methods

Bad:

1
2
3
/api/v1/user_profiles
/api/v1/UserProfiles
/api/v1/shoppingCarts

6. Provide Filtering for Sub-Resources

Avoid deep nesting (more than 2-3 levels):

Instead of:

1
/authors/123/books/456/chapters/789/paragraphs/101

Use:

1
/paragraphs/101?book_id=456&chapter_id=789

7. Use Standard HTTP Methods Correctly

Method Safety and Idempotency:

MethodSafeIdempotentCacheable
GETβœ“βœ“βœ“
POSTβœ—βœ—βœ—
PUTβœ—βœ“βœ—
PATCHβœ—βœ—*βœ—
DELETEβœ—βœ“βœ—

*PATCH can be idempotent depending on implementation

8. Accept and Respond with JSON

JSON is the standard for transferring data, as almost every networked technology can use it.

Request:

1
2
3
4
5
6
7
8
POST /api/users HTTP/1.1
Content-Type: application/json
Accept: application/json

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

Response:

1
2
3
4
5
6
7
8
9
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "created_at": "2025-11-17T10:00:00Z"
}

9. Handle Errors Gracefully

When the API does not successfully complete, we should fail gracefully by sending an error with information to help users make corrective action.

Error Response Structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "error": {
    "status": 400,
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Email address is not properly formatted"
      }
    ],
    "timestamp": "2025-11-17T10:50:00Z",
    "path": "/api/users",
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

10. Use Proper MIME Types

1
2
3
4
5
Content-Type: application/json
Content-Type: application/xml
Content-Type: application/pdf
Content-Type: text/html
Content-Type: image/png

πŸ” REST API Security

Security is paramount in API design. REST APIs must implement multiple layers of protection.

Authentication vs Authorization

Authentication: Verifies β€œWho are you?”
Authorization: Determines β€œWhat can you do?”

1
2
3
4
5
6
7
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Authentication β”‚ β†’ Who is the user?
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Authorization  β”‚ β†’ What can they access?
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Authentication Methods

1. Basic Authentication

Basic Authentication relies on a username and password combination sent through HTTP headers, encoded in Base64.

How it Works:

1
2
3
GET /api/users HTTP/1.1
Host: api.example.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Encoding:

1
Base64("username:password") = "dXNlcm5hbWU6cGFzc3dvcmQ="

Pros:

  • Simple to implement
  • Supported by all HTTP clients
  • Suitable for testing/development

Cons:

  • Credentials sent with every request
  • Not secure without HTTPS
  • No built-in expiration mechanism

Warning: Basic Authentication should ONLY be used over HTTPS and is not recommended for production APIs.

2. API Keys

How it Works:

1
2
3
GET /api/users HTTP/1.1
Host: api.example.com
X-API-Key: 7f8d9e2b-4c5a-6e1d-9a8f-3b4c5a6e7d8f

or as query parameter:

1
GET /api/users?api_key=7f8d9e2b-4c5a-6e1d-9a8f-3b4c5a6e7d8f

Best Practices:

  • Use header-based keys (more secure than query parameters)
  • Rotate keys regularly
  • Use different keys for different environments
  • Implement rate limiting per API key
  • Store keys securely (never in source code)

Pros:

  • Simple to implement
  • Easy to revoke
  • Good for server-to-server communication

Cons:

  • Static credentials (no expiration)
  • If compromised, affects all requests
  • Limited granular permissions

3. JWT (JSON Web Tokens)

JWTs are an open, standard way to represent user identity securely during two-party interactions.

JWT Structure:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Components:

  1. Header (Algorithm & Token Type)
  2. Payload (Claims/Data)
  3. Signature (Verification)

Header:

1
2
3
4
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload:

1
2
3
4
5
6
7
{
  "sub": "1234567890",
  "name": "John Doe",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

Usage:

1
2
3
GET /api/users HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Implementation Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Generate JWT
const jwt = require('jsonwebtoken');

const token = jwt.sign(
  {
    sub: user.id,
    email: user.email,
    role: user.role
  },
  process.env.JWT_SECRET,
  { expiresIn: '1h' }
);

// Verify JWT
const decoded = jwt.verify(token, process.env.JWT_SECRET);

Pros:

  • Stateless authentication
  • Contains user information
  • Can include expiration
  • Self-contained
  • Works across different domains

Cons:

  • Larger than simple tokens
  • Cannot be revoked before expiration
  • Payload is readable (Base64 encoded, not encrypted)

4. OAuth 2.0

OAuth 2.0 is a widely adopted authorization framework that allows users to grant limited access to resources without exposing their credentials.

OAuth 2.0 Flow (Authorization Code):

sequenceDiagram
    participant User
    participant Client
    participant AuthServer
    participant ResourceServer
    
    User->>Client: 1. Request resource
    Client->>AuthServer: 2. Redirect to authorization
    User->>AuthServer: 3. Login & grant permission
    AuthServer->>Client: 4. Authorization code
    Client->>AuthServer: 5. Exchange code for token
    AuthServer->>Client: 6. Access token
    Client->>ResourceServer: 7. Request with token
    ResourceServer->>Client: 8. Protected resource

Grant Types:

  1. Authorization Code: For web/mobile apps with backend
  2. Client Credentials: For server-to-server communication
  3. Refresh Token: To obtain new access tokens
  4. Implicit (deprecated): For browser-based apps

Access Token Request:

1
2
3
4
5
6
7
8
9
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=https://client.example.com/callback&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET

Access Token Response:

1
2
3
4
5
6
7
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
  "scope": "read write"
}

Using Access Token:

1
2
3
GET /api/users/me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Pros:

  • Industry standard
  • Delegated authorization
  • Granular scopes/permissions
  • Secure token management
  • Works with third-party services

Cons:

  • Complex implementation
  • Requires understanding of multiple flows
  • Overhead for simple use cases

Authorization Strategies

Role-Based Access Control (RBAC)

Users are assigned roles, and roles have permissions.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "user": {
    "id": 123,
    "email": "john@example.com",
    "role": "admin"
  },
  "roles": {
    "admin": ["read", "write", "delete", "manage_users"],
    "editor": ["read", "write"],
    "viewer": ["read"]
  }
}

Implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function checkPermission(user, resource, action) {
  const rolePermissions = {
    'admin': ['read', 'write', 'delete', 'manage_users'],
    'editor': ['read', 'write'],
    'viewer': ['read']
  };
  
  const permissions = rolePermissions[user.role] || [];
  return permissions.includes(action);
}

// Usage
if (!checkPermission(user, 'users', 'delete')) {
  return res.status(403).json({
    error: 'Forbidden: Insufficient permissions'
  });
}

Attribute-Based Access Control (ABAC)

Access decisions based on attributes of user, resource, and environment.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "user": {
    "id": 123,
    "department": "engineering",
    "clearance_level": 3
  },
  "resource": {
    "type": "document",
    "classification": "confidential",
    "department": "engineering"
  },
  "environment": {
    "time": "09:00",
    "location": "office",
    "ip_trusted": true
  }
}

Security Best Practices

1. Always Use HTTPS

Transport Layer Security (TLS) encrypts data in transit.

1
2
HTTP  β†’ Port 80  β†’ Unencrypted ❌
HTTPS β†’ Port 443 β†’ Encrypted βœ“

Enforce HTTPS:

1
2
3
4
5
6
7
// Express.js middleware
app.use((req, res, next) => {
  if (!req.secure && req.get('x-forwarded-proto') !== 'https') {
    return res.redirect('https://' + req.get('host') + req.url);
  }
  next();
});

2. Validate Input

Never trust client input. Always validate and sanitize.

Example Validation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { body, validationResult } = require('express-validator');

app.post('/users',
  body('email').isEmail().normalizeEmail(),
  body('age').isInt({ min: 1, max: 120 }),
  body('name').trim().isLength({ min: 1, max: 100 }),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // Process valid data
  }
);

3. Implement Rate Limiting

Protect against brute force and DDoS attacks.

Example with Express:

1
2
3
4
5
6
7
8
9
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP, please try again later.'
});

app.use('/api/', limiter);

Response Header:

1
2
3
4
5
6
7
8
9
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700227200
Retry-After: 900

{
  "error": "Rate limit exceeded. Try again in 15 minutes."
}

4. Use CORS Properly

Cross-Origin Resource Sharing controls which domains can access your API.

Example Configuration:

1
2
3
4
5
6
7
8
9
10
11
const cors = require('cors');

const corsOptions = {
  origin: ['https://example.com', 'https://app.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400 // 24 hours
};

app.use(cors(corsOptions));

5. Implement Security Headers

Recommended Headers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Prevent clickjacking
X-Frame-Options: DENY

# Prevent MIME type sniffing
X-Content-Type-Options: nosniff

# Enable XSS protection
X-XSS-Protection: 1; mode=block

# Content Security Policy
Content-Security-Policy: default-src 'self'

# Strict Transport Security
Strict-Transport-Security: max-age=31536000; includeSubDomains

# Referrer Policy
Referrer-Policy: no-referrer-when-downgrade

Express Implementation:

1
2
const helmet = require('helmet');
app.use(helmet());

6. Don’t Expose Sensitive Information

Bad:

1
2
3
4
{
  "error": "Database connection failed: Connection refused at 192.168.1.10:5432",
  "stack": "Error: connect ECONNREFUSED 192.168.1.10:5432\n    at TCPConnectWrap.afterConnect..."
}

Good:

1
2
3
4
5
6
7
{
  "error": {
    "code": "SERVICE_UNAVAILABLE",
    "message": "Service temporarily unavailable. Please try again later.",
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

7. SQL Injection Prevention

Bad (Vulnerable):

1
2
const query = `SELECT * FROM users WHERE id = ${userId}`;
db.query(query);

Good (Parameterized):

1
2
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [userId]);

8. Implement Logging and Monitoring

What to Log:

  • Authentication attempts (success/failure)
  • Authorization failures
  • Input validation failures
  • Rate limit violations
  • Error conditions
  • Performance metrics

Example Log Entry:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "timestamp": "2025-11-17T10:30:00Z",
  "level": "INFO",
  "event": "API_REQUEST",
  "method": "POST",
  "path": "/api/users",
  "status": 201,
  "duration_ms": 45,
  "user_id": 123,
  "ip": "203.0.113.42",
  "user_agent": "Mozilla/5.0...",
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}

πŸ“– API Versioning

API versioning ensures backward compatibility while allowing evolution of your API.

Why Version Your API?

  • Backward Compatibility: Old clients continue working
  • Gradual Migration: Users can migrate at their own pace
  • Breaking Changes: Introduce new features without disrupting existing integrations
  • Clear Communication: Version indicates stability and compatibility

Versioning Strategies

1. URI Versioning (Most Common)

Version is part of the URL path.

Example:

1
2
3
https://api.example.com/v1/users
https://api.example.com/v2/users
https://api.example.com/v3/users

Pros:

  • Clear and explicit
  • Easy to route
  • Browser-friendly
  • Easy to test different versions

Cons:

  • Clutters URI space
  • Requires separate documentation per version

Implementation (Express):

1
2
3
4
5
// v1 routes
app.use('/api/v1/users', require('./routes/v1/users'));

// v2 routes
app.use('/api/v2/users', require('./routes/v2/users'));

2. Header Versioning

Version specified in request header.

Example:

1
2
3
GET /api/users HTTP/1.1
Host: api.example.com
Accept: application/vnd.example.v2+json

or custom header:

1
2
3
GET /api/users HTTP/1.1
Host: api.example.com
API-Version: 2

Pros:

  • Clean URIs
  • Flexible versioning
  • Follows REST principles

Cons:

  • Less visible
  • Harder to test in browser
  • Requires custom headers

Implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
app.use((req, res, next) => {
  const version = req.headers['api-version'] || '1';
  req.apiVersion = version;
  next();
});

app.get('/api/users', (req, res) => {
  if (req.apiVersion === '2') {
    // v2 logic
  } else {
    // v1 logic
  }
});

3. Query Parameter Versioning

Example:

1
2
https://api.example.com/users?version=2
https://api.example.com/users?v=2

Pros:

  • Easy to implement
  • Simple to test

Cons:

  • Pollutes query parameters
  • Can conflict with other parameters
  • Not RESTful

4. Content Negotiation Versioning

Uses Accept header with custom media types.

Example:

1
2
3
GET /api/users HTTP/1.1
Host: api.example.com
Accept: application/vnd.example.v2+json

Pros:

  • Follows REST principles
  • Clean URIs
  • Standards-based

Cons:

  • Complex to implement
  • Harder for clients to use
  • Less discoverable

Versioning Best Practices

  1. Start with v1: Begin versioning from the start
  2. Use Major Versions Only: v1, v2, v3 (not v1.2.3)
  3. Support Multiple Versions: Maintain at least 2 versions
  4. Deprecation Policy: Announce deprecation well in advance
  5. Documentation: Maintain docs for all supported versions
  6. Default Version: Have a sensible default when no version specified
  7. Version Response: Include version in response headers

Deprecation Header:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
Link: <https://api.example.com/v2/users>; rel="successor-version"

{
  "warning": "This API version will be deprecated on 2025-12-31. Please migrate to v2."
}

πŸ“ API Documentation

Comprehensive documentation is crucial for API adoption and usability.

What to Document

  1. Authentication: How to authenticate
  2. Base URL: API endpoint
  3. Endpoints: Available resources
  4. Request Format: Headers, parameters, body
  5. Response Format: Structure and examples
  6. Error Codes: All possible errors
  7. Rate Limits: Throttling policies
  8. Versioning: How versions work
  9. Examples: Real-world use cases
  10. Changelog: Version history

OpenAPI Specification (formerly Swagger)

OpenAPI is the industry standard for describing REST APIs.

Example OpenAPI Specification:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
openapi: 3.0.0
info:
  title: User Management API
  description: API for managing users
  version: 1.0.0
  contact:
    name: API Support
    email: support@example.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: https://api.example.com/v1
    description: Production server
  - url: https://staging-api.example.com/v1
    description: Staging server

paths:
  /users:
    get:
      summary: Get all users
      description: Retrieves a list of all users with pagination
      tags:
        - Users
      parameters:
        - name: page
          in: query
          description: Page number
          required: false
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          description: Items per page
          required: false
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
      security:
        - bearerAuth: []
    
    post:
      summary: Create a new user
      description: Creates a new user account
      tags:
        - Users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
      responses:
        '201':
          description: User created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequestError'
        '422':
          $ref: '#/components/responses/ValidationError'
      security:
        - bearerAuth: []

  /users/{userId}:
    get:
      summary: Get user by ID
      description: Retrieves a specific user by their ID
      tags:
        - Users
      parameters:
        - name: userId
          in: path
          description: User ID
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          $ref: '#/components/responses/NotFoundError'
      security:
        - bearerAuth: []

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
          example: 123
        name:
          type: string
          example: John Doe
        email:
          type: string
          format: email
          example: john@example.com
        role:
          type: string
          enum: [admin, user, viewer]
          example: user
        created_at:
          type: string
          format: date-time
          example: 2025-11-17T10:00:00Z
    
    UserCreate:
      type: object
      required:
        - name
        - email
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        email:
          type: string
          format: email
        role:
          type: string
          enum: [admin, user, viewer]
          default: user
    
    Pagination:
      type: object
      properties:
        total:
          type: integer
          example: 1000
        page:
          type: integer
          example: 1
        per_page:
          type: integer
          example: 20
        total_pages:
          type: integer
          example: 50
    
    Error:
      type: object
      properties:
        code:
          type: string
          example: VALIDATION_ERROR
        message:
          type: string
          example: Request validation failed
        details:
          type: array
          items:
            type: object
  
  responses:
    UnauthorizedError:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    
    NotFoundError:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    
    BadRequestError:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    
    ValidationError:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

Documentation Tools

Swagger UI

Automatically generates interactive documentation from OpenAPI specs.

Features:

  • Interactive API explorer
  • Try-it-out functionality
  • Code generation
  • Schema visualization

Implementation:

1
2
3
4
5
const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');
const swaggerDocument = YAML.load('./openapi.yaml');

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

Postman

Popular API development tool with documentation features.

Features:

  • Request collections
  • Environment variables
  • Automated testing
  • Public documentation
  • Code snippets

ReDoc

Alternative to Swagger UI with a cleaner interface.

Features:

  • Responsive three-panel design
  • Search functionality
  • Deep linking
  • No dependencies

Implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
  <head>
    <title>API Documentation</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
  </head>
  <body>
    <redoc spec-url='openapi.yaml'></redoc>
    <script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"></script>
  </body>
</html>

🎨 API Design Patterns

1. Pagination Patterns

Offset-Based Pagination

Request:

1
GET /users?limit=20&offset=40

Response:

1
2
3
4
5
6
7
8
{
  "data": [...],
  "pagination": {
    "limit": 20,
    "offset": 40,
    "total": 1000
  }
}

Pros:

  • Simple to implement
  • Jump to any page
  • SQL-friendly (LIMIT/OFFSET)

Cons:

  • Performance degrades with large offsets
  • Inconsistent results if data changes
  • Not suitable for real-time data

Cursor-Based Pagination

Request:

1
GET /users?cursor=eyJpZCI6MTIzfQ==&limit=20

Response:

1
2
3
4
5
6
7
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTQzfQ==",
    "has_more": true
  }
}

Pros:

  • Consistent results
  • Better performance
  • Handles real-time data
  • No duplication or skipping

Cons:

  • Cannot jump to specific page
  • More complex to implement
  • Cursor must be opaque

Page-Based Pagination

Request:

1
GET /users?page=3&per_page=20

Response:

1
2
3
4
5
6
7
8
9
{
  "data": [...],
  "pagination": {
    "page": 3,
    "per_page": 20,
    "total_pages": 50,
    "total_items": 1000
  }
}

2. Filtering and Searching

Simple Filtering

1
2
GET /products?category=electronics&price_min=100&price_max=500
GET /users?status=active&role=admin

Advanced Filtering (Query Language)

Using RSQL:

1
2
GET /products?filter=category==electronics;price>100;price<500
GET /users?filter=status==active,role==admin

Using JSON Filter:

1
GET /products?filter={"category":"electronics","price":{"$gt":100,"$lt":500}}
1
2
GET /products?q=laptop&fields=name,description
GET /articles?search=rest+api&fuzzy=true

3. Sorting

Single Field:

1
2
GET /users?sort=name
GET /users?sort=-created_at  # Descending

Multiple Fields:

1
2
GET /users?sort=role,name
GET /users?sort=-created_at,name

4. Field Selection (Sparse Fieldsets)

Allow clients to request only needed fields.

Request:

1
GET /users?fields=id,name,email

Response:

1
2
3
4
5
6
7
8
9
{
  "data": [
    {
      "id": 123,
      "name": "John Doe",
      "email": "john@example.com"
    }
  ]
}

5. Resource Expansion

Allow clients to include related resources.

Request:

1
GET /users/123?expand=orders,profile

Response:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "id": 123,
  "name": "John Doe",
  "orders": [
    {"id": 456, "total": 99.99},
    {"id": 457, "total": 149.99}
  ],
  "profile": {
    "bio": "Software developer",
    "avatar": "https://example.com/avatar.jpg"
  }
}

6. Bulk Operations

Bulk Create

Request:

1
2
3
4
5
6
7
8
9
POST /users/bulk HTTP/1.1
Content-Type: application/json

{
  "users": [
    {"name": "User 1", "email": "user1@example.com"},
    {"name": "User 2", "email": "user2@example.com"}
  ]
}

Response:

1
2
3
4
5
6
7
{
  "created": 2,
  "results": [
    {"id": 123, "status": "created"},
    {"id": 124, "status": "created"}
  ]
}

Bulk Update

Request:

1
2
3
4
5
6
7
8
9
PATCH /users/bulk HTTP/1.1
Content-Type: application/json

{
  "updates": [
    {"id": 123, "status": "active"},
    {"id": 124, "status": "inactive"}
  ]
}

7. Async Operations

For long-running operations, use async pattern.

Initial Request:

1
2
3
4
5
6
7
POST /reports/generate HTTP/1.1
Content-Type: application/json

{
  "type": "sales",
  "date_range": "2025-01-01/2025-12-31"
}

Response:

1
2
3
4
5
6
7
8
HTTP/1.1 202 Accepted
Location: /reports/jobs/abc123

{
  "job_id": "abc123",
  "status": "pending",
  "created_at": "2025-11-17T10:00:00Z"
}

Status Check:

1
GET /reports/jobs/abc123 HTTP/1.1

Response (In Progress):

1
2
3
4
5
6
{
  "job_id": "abc123",
  "status": "processing",
  "progress": 45,
  "estimated_completion": "2025-11-17T10:05:00Z"
}

Response (Completed):

1
2
3
4
5
6
{
  "job_id": "abc123",
  "status": "completed",
  "result_url": "/reports/files/abc123.pdf",
  "completed_at": "2025-11-17T10:04:30Z"
}

πŸ“Š API Lifecycle Terminology Tables

Table 1: Lifecycle Phases - Different Terminologies

This table shows how different organizations and standards refer to similar phases in the API lifecycle:

Generic TermSoftware IndustryRESTful ContextDevOps/AgileEnterpriseAlternate Terms
PlanningRequirements PhaseAPI DesignSprint PlanningInitiationIdeation, Conception, Discovery
DesignArchitecture PhaseResource ModelingDesign SprintAnalysisSpecification, Blueprint, Schema Design
DevelopmentImplementationEndpoint CreationDevelopment SprintConstructionCoding, Building, Engineering
TestingQA PhaseAPI TestingTesting SprintVerificationQuality Assurance, Validation
DocumentationDocumentation PhaseAPI DocsDocumentation SprintKnowledge TransferSpecification Writing, User Guides
DeploymentRelease PhaseAPI PublicationRelease SprintRolloutLaunch, Go-Live, Production Release
MonitoringOperations PhaseAPI AnalyticsOperationsMaintenanceObservability, Tracking, Surveillance
VersioningMaintenance PhaseAPI EvolutionIterationEnhancementUpgrades, Updates, Revisions
DeprecationEnd-of-LifeAPI SunsetRetirement SprintDecommissionPhase-out, Discontinuation
SecuritySecurity PhaseAuthentication/AuthorizationSecurity SprintComplianceProtection, Access Control

Table 2: Hierarchical Differentiation of Lifecycle Stages

This table shows the hierarchical relationship between different levels of API lifecycle terminology:

LevelCategorySubcategorySpecific ActivityTechnical ImplementationExample
1StrategyBusiness AlignmentUse Case DefinitionRequirements GatheringDefine β€œUser Management API” need
1StrategyValue PropositionROI AnalysisCost-Benefit AnalysisCalculate development vs. benefits
Β Β Β Β Β Β 
2PlanningAPI DesignResource IdentificationEntity ModelingDefine User, Order, Product entities
2PlanningAPI DesignEndpoint PlanningURI Structure Design/users, /orders, /products
2PlanningSecurity DesignAuth StrategyOAuth 2.0 vs JWT DecisionChoose JWT for stateless auth
2PlanningDocumentation PlanningDoc Tool SelectionOpenAPI SpecificationSelect Swagger/ReDoc
Β Β Β Β Β Β 
3DevelopmentBackend DevelopmentRoute ImplementationExpress.js Routingapp.get('/users', handler)
3DevelopmentBackend DevelopmentBusiness LogicController LogicUser validation, DB operations
3DevelopmentDatabase DesignSchema CreationMigration ScriptsCreate users table with columns
3DevelopmentMiddleware DevelopmentAuth MiddlewareJWT VerificationToken validation middleware
Β Β Β Β Β Β 
4TestingUnit TestingFunction TestingJest/Mocha TestsTest individual functions
4TestingIntegration TestingAPI TestingPostman/NewmanTest complete API flows
4TestingSecurity TestingPenetration TestingOWASP ZAPTest for vulnerabilities
4TestingLoad TestingPerformance TestingApache JMeterTest under load
Β Β Β Β Β Β 
5DocumentationAPI ReferenceEndpoint DocumentationOpenAPI SpecDocument all endpoints
5DocumentationUser GuidesIntegration GuideMarkdown DocsHow to integrate tutorial
5DocumentationCode ExamplesSDK/Client ExamplesCode SnippetsPython, JS, cURL examples
Β Β Β Β Β Β 
6DeploymentEnvironment SetupServer ConfigurationAWS/Azure/GCP SetupConfigure production servers
6DeploymentCI/CD PipelineAutomationGitHub Actions/JenkinsAutomated deployment pipeline
6DeploymentContainer DeploymentOrchestrationDocker/KubernetesDeploy containers
6DeploymentDNS ConfigurationRoutingRoute 53/CloudflareSetup api.example.com
Β Β Β Β Β Β 
7MonitoringPerformance MonitoringMetrics CollectionPrometheus/GrafanaTrack response times, errors
7MonitoringLog AggregationCentralized LoggingELK Stack/SplunkCollect and analyze logs
7MonitoringError TrackingException MonitoringSentry/RollbarTrack and alert on errors
7MonitoringAnalyticsUsage AnalyticsGoogle Analytics APITrack API usage patterns
Β Β Β Β Β Β 
8VersioningVersion PlanningBreaking ChangesSemantic VersioningPlan v1 β†’ v2 migration
8VersioningMigration StrategyBackward CompatibilityDual-Version SupportRun v1 and v2 simultaneously
8VersioningDeprecation NoticeCommunicationEmail/Docs UpdateAnnounce v1 deprecation
Β Β Β Β Β Β 
9MaintenanceBug FixesIssue ResolutionHotfix DeploymentFix critical bugs
9MaintenancePerformance OptimizationTuningDatabase IndexingOptimize slow queries
9MaintenanceSecurity PatchesUpdatesDependency UpdatesUpdate vulnerable packages
Β Β Β Β Β Β 
10DeprecationSunset PlanningTimeline DefinitionDeprecation Policy6-month sunset period
10DeprecationUser CommunicationMigration SupportDocumentation UpdatesProvide migration guide
10DeprecationFinal ShutdownDecommissioningServer ShutdownTurn off deprecated endpoints

Table 3: Cross-Reference - Industry Standard Terminology

REST API ContextSDLC TermAPI Management TermMicroservices TermCloud-Native Term
Resource DesignData ModelingAPI Product DesignService DesignResource Provisioning
Endpoint CreationFeature DevelopmentAPI ImplementationService DevelopmentFunction Development
AuthenticationSecurity ImplementationAPI SecurityService AuthIdentity Management
Rate LimitingPerformance ControlAPI ThrottlingService Rate LimitingRequest Quotas
API GatewayEntry PointAPI ManagerService Mesh GatewayIngress Controller
DocumentationTechnical WritingAPI PortalService DocumentationDeveloper Portal
VersioningRelease ManagementAPI LifecycleService VersioningRelease Strategy
MonitoringOperationsAPI AnalyticsService ObservabilityCloud Monitoring
TestingQuality AssuranceAPI TestingService TestingIntegration Testing
DeploymentReleaseAPI PublicationService DeploymentContainer Deployment

πŸ”„ Complete REST API Implementation Example

Let’s build a complete REST API for a blog platform to demonstrate all concepts in practice.

Project Structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
blog-api/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   β”œβ”€β”€ database.js
β”‚   β”‚   └── env.js
β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   β”œβ”€β”€ auth.js
β”‚   β”‚   β”œβ”€β”€ errorHandler.js
β”‚   β”‚   β”œβ”€β”€ rateLimiter.js
β”‚   β”‚   └── validator.js
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ User.js
β”‚   β”‚   └── Post.js
β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”œβ”€β”€ auth.js
β”‚   β”‚   β”œβ”€β”€ users.js
β”‚   β”‚   └── posts.js
β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   β”œβ”€β”€ authController.js
β”‚   β”‚   β”œβ”€β”€ userController.js
β”‚   β”‚   └── postController.js
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ authService.js
β”‚   β”‚   └── postService.js
β”‚   └── app.js
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ unit/
β”‚   └── integration/
β”œβ”€β”€ docs/
β”‚   └── openapi.yaml
β”œβ”€β”€ .env
β”œβ”€β”€ .gitignore
β”œβ”€β”€ package.json
└── README.md

Database Models

User Model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// src/models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true,
    minlength: 2,
    maxlength: 100
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
    trim: true,
    match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  },
  password: {
    type: String,
    required: true,
    minlength: 8
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  },
  isActive: {
    type: Boolean,
    default: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  },
  updatedAt: {
    type: Date,
    default: Date.now
  }
});

// Hash password before saving
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

// Compare password method
userSchema.methods.comparePassword = async function(candidatePassword) {
  return await bcrypt.compare(candidatePassword, this.password);
};

// Remove password from JSON output
userSchema.methods.toJSON = function() {
  const obj = this.toObject();
  delete obj.password;
  return obj;
};

module.exports = mongoose.model('User', userSchema);

Post Model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// src/models/Post.js
const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    trim: true,
    minlength: 5,
    maxlength: 200
  },
  content: {
    type: String,
    required: true,
    minlength: 10
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  tags: [{
    type: String,
    trim: true
  }],
  status: {
    type: String,
    enum: ['draft', 'published', 'archived'],
    default: 'draft'
  },
  publishedAt: {
    type: Date
  },
  createdAt: {
    type: Date,
    default: Date.now
  },
  updatedAt: {
    type: Date,
    default: Date.now
  }
});

// Update publishedAt when status changes to published
postSchema.pre('save', function(next) {
  if (this.isModified('status') && this.status === 'published' && !this.publishedAt) {
    this.publishedAt = new Date();
  }
  this.updatedAt = new Date();
  next();
});

module.exports = mongoose.model('Post', postSchema);

Middleware Implementation

Authentication Middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');

exports.authenticate = async (req, res, next) => {
  try {
    // Get token from header
    const authHeader = req.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res.status(401).json({
        error: {
          code: 'UNAUTHORIZED',
          message: 'No token provided'
        }
      });
    }
    
    const token = authHeader.substring(7);
    
    // Verify token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
    // Get user from database
    const user = await User.findById(decoded.userId);
    
    if (!user || !user.isActive) {
      return res.status(401).json({
        error: {
          code: 'UNAUTHORIZED',
          message: 'Invalid token'
        }
      });
    }
    
    // Attach user to request
    req.user = user;
    next();
  } catch (error) {
    if (error.name === 'JsonWebTokenError') {
      return res.status(401).json({
        error: {
          code: 'UNAUTHORIZED',
          message: 'Invalid token'
        }
      });
    }
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({
        error: {
          code: 'TOKEN_EXPIRED',
          message: 'Token has expired'
        }
      });
    }
    next(error);
  }
};

exports.authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        error: {
          code: 'FORBIDDEN',
          message: 'Insufficient permissions'
        }
      });
    }
    next();
  };
};

Error Handler Middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// src/middleware/errorHandler.js
exports.errorHandler = (err, req, res, next) => {
  console.error(err);
  
  // Mongoose validation error
  if (err.name === 'ValidationError') {
    const errors = Object.values(err.errors).map(e => ({
      field: e.path,
      message: e.message
    }));
    
    return res.status(422).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: 'Validation failed',
        details: errors
      }
    });
  }
  
  // Mongoose duplicate key error
  if (err.code === 11000) {
    const field = Object.keys(err.keyPattern)[0];
    return res.status(409).json({
      error: {
        code: 'DUPLICATE_ERROR',
        message: `${field} already exists`
      }
    });
  }
  
  // Mongoose cast error (invalid ID)
  if (err.name === 'CastError') {
    return res.status(400).json({
      error: {
        code: 'INVALID_ID',
        message: 'Invalid resource ID'
      }
    });
  }
  
  // Default error
  res.status(err.statusCode || 500).json({
    error: {
      code: err.code || 'INTERNAL_SERVER_ERROR',
      message: process.env.NODE_ENV === 'production' 
        ? 'An unexpected error occurred' 
        : err.message
    }
  });
};

exports.notFound = (req, res) => {
  res.status(404).json({
    error: {
      code: 'NOT_FOUND',
      message: 'Resource not found'
    }
  });
};

Rate Limiter Middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');

exports.generalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: {
    error: {
      code: 'RATE_LIMIT_EXCEEDED',
      message: 'Too many requests, please try again later'
    }
  },
  standardHeaders: true,
  legacyHeaders: false,
});

exports.authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5, // 5 login attempts per 15 minutes
  skipSuccessfulRequests: true,
  message: {
    error: {
      code: 'TOO_MANY_LOGIN_ATTEMPTS',
      message: 'Too many login attempts, please try again later'
    }
  }
});

Validation Middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// src/middleware/validator.js
const { body, query, param, validationResult } = require('express-validator');

exports.validate = (validations) => {
  return async (req, res, next) => {
    await Promise.all(validations.map(validation => validation.run(req)));
    
    const errors = validationResult(req);
    if (errors.isEmpty()) {
      return next();
    }
    
    res.status(422).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: 'Validation failed',
        details: errors.array().map(err => ({
          field: err.path,
          message: err.msg
        }))
      }
    });
  };
};

// Common validations
exports.userValidations = {
  register: [
    body('name').trim().notEmpty().isLength({ min: 2, max: 100 }),
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({ min: 8 }).matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]/),
  ],
  login: [
    body('email').isEmail().normalizeEmail(),
    body('password').notEmpty()
  ]
};

exports.postValidations = {
  create: [
    body('title').trim().notEmpty().isLength({ min: 5, max: 200 }),
    body('content').trim().notEmpty().isLength({ min: 10 }),
    body('tags').optional().isArray(),
    body('status').optional().isIn(['draft', 'published', 'archived'])
  ],
  update: [
    body('title').optional().trim().isLength({ min: 5, max: 200 }),
    body('content').optional().trim().isLength({ min: 10 }),
    body('tags').optional().isArray(),
    body('status').optional().isIn(['draft', 'published', 'archived'])
  ],
  list: [
    query('page').optional().isInt({ min: 1 }),
    query('limit').optional().isInt({ min: 1, max: 100 }),
    query('status').optional().isIn(['draft', 'published', 'archived']),
    query('sort').optional().isIn(['createdAt', '-createdAt', 'title', '-title'])
  ]
};

Controllers

Auth Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// src/controllers/authController.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');

exports.register = async (req, res, next) => {
  try {
    const { name, email, password } = req.body;
    
    // Create user
    const user = await User.create({ name, email, password });
    
    // Generate token
    const token = jwt.sign(
      { userId: user._id },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );
    
    res.status(201).json({
      user: user.toJSON(),
      token,
      expiresIn: '7d'
    });
  } catch (error) {
    next(error);
  }
};

exports.login = async (req, res, next) => {
  try {
    const { email, password } = req.body;
    
    // Find user
    const user = await User.findOne({ email });
    
    if (!user || !user.isActive) {
      return res.status(401).json({
        error: {
          code: 'INVALID_CREDENTIALS',
          message: 'Invalid email or password'
        }
      });
    }
    
    // Check password
    const isMatch = await user.comparePassword(password);
    
    if (!isMatch) {
      return res.status(401).json({
        error: {
          code: 'INVALID_CREDENTIALS',
          message: 'Invalid email or password'
        }
      });
    }
    
    // Generate token
    const token = jwt.sign(
      { userId: user._id },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );
    
    res.json({
      user: user.toJSON(),
      token,
      expiresIn: '7d'
    });
  } catch (error) {
    next(error);
  }
};

exports.getProfile = async (req, res) => {
  res.json({
    user: req.user.toJSON()
  });
};

Post Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// src/controllers/postController.js
const Post = require('../models/Post');

exports.createPost = async (req, res, next) => {
  try {
    const { title, content, tags, status } = req.body;
    
    const post = await Post.create({
      title,
      content,
      tags,
      status,
      author: req.user._id
    });
    
    await post.populate('author', 'name email');
    
    res.status(201).json({
      post
    });
  } catch (error) {
    next(error);
  }
};

exports.getPosts = async (req, res, next) => {
  try {
    const {
      page = 1,
      limit = 20,
      status,
      sort = '-createdAt',
      search
    } = req.query;
    
    // Build query
    const query = {};
    
    // Filter by status if provided
    if (status) {
      query.status = status;
    }
    
    // Only show published posts for non-authors
    if (!req.user || req.user.role !== 'admin') {
      if (!status) {
        query.status = 'published';
      }
    }
    
    // Search functionality
    if (search) {
      query.$or = [
        { title: { $regex: search, $options: 'i' } },
        { content: { $regex: search, $options: 'i' } }
      ];
    }
    
    // Calculate pagination
    const skip = (page - 1) * limit;
    
    // Execute query
    const [posts, total] = await Promise.all([
      Post.find(query)
        .populate('author', 'name email')
        .sort(sort)
        .skip(skip)
        .limit(parseInt(limit)),
      Post.countDocuments(query)
    ]);
    
    res.json({
      data: posts,
      pagination: {
        page: parseInt(page),
        limit: parseInt(limit),
        total,
        pages: Math.ceil(total / limit)
      }
    });
  } catch (error) {
    next(error);
  }
};

exports.getPost = async (req, res, next) => {
  try {
    const post = await Post.findById(req.params.id)
      .populate('author', 'name email');
    
    if (!post) {
      return res.status(404).json({
        error: {
          code: 'NOT_FOUND',
          message: 'Post not found'
        }
      });
    }
    
    // Check if user can view this post
    if (post.status !== 'published' && 
        (!req.user || (req.user._id.toString() !== post.author._id.toString() && req.user.role !== 'admin'))) {
      return res.status(403).json({
        error: {
          code: 'FORBIDDEN',
          message: 'Access denied'
        }
      });
    }
    
    res.json({
      post
    });
  } catch (error) {
    next(error);
  }
};

exports.updatePost = async (req, res, next) => {
  try {
    const post = await Post.findById(req.params.id);
    
    if (!post) {
      return res.status(404).json({
        error: {
          code: 'NOT_FOUND',
          message: 'Post not found'
        }
      });
    }
    
    // Check if user is author or admin
    if (req.user._id.toString() !== post.author.toString() && req.user.role !== 'admin') {
      return res.status(403).json({
        error: {
          code: 'FORBIDDEN',
          message: 'Access denied'
        }
      });
    }
    
    // Update fields
    const { title, content, tags, status } = req.body;
    if (title) post.title = title;
    if (content) post.content = content;
    if (tags) post.tags = tags;
    if (status) post.status = status;
    
    await post.save();
    await post.populate('author', 'name email');
    
    res.json({
      post
    });
  } catch (error) {
    next(error);
  }
};

exports.deletePost = async (req, res, next) => {
  try {
    const post = await Post.findById(req.params.id);
    
    if (!post) {
      return res.status(404).json({
        error: {
          code: 'NOT_FOUND',
          message: 'Post not found'
        }
      });
    }
    
    // Check if user is author or admin
    if (req.user._id.toString() !== post.author.toString() && req.user.role !== 'admin') {
      return res.status(403).json({
        error: {
          code: 'FORBIDDEN',
          message: 'Access denied'
        }
      });
    }
    
    await post.deleteOne();
    
    res.status(204).send();
  } catch (error) {
    next(error);
  }
};

Routes

Auth Routes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/routes/auth.js
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const { authenticate } = require('../middleware/auth');
const { validate, userValidations } = require('../middleware/validator');
const { authLimiter } = require('../middleware/rateLimiter');

router.post('/register', 
  validate(userValidations.register),
  authController.register
);

router.post('/login',
  authLimiter,
  validate(userValidations.login),
  authController.login
);

router.get('/profile',
  authenticate,
  authController.getProfile
);

module.exports = router;

Post Routes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/routes/posts.js
const express = require('express');
const router = express.Router();
const postController = require('../controllers/postController');
const { authenticate } = require('../middleware/auth');
const { validate, postValidations } = require('../middleware/validator');

router.get('/',
  validate(postValidations.list),
  postController.getPosts
);

router.get('/:id',
  postController.getPost
);

router.post('/',
  authenticate,
  validate(postValidations.create),
  postController.createPost
);

router.put('/:id',
  authenticate,
  validate(postValidations.update),
  postController.updatePost
);

router.delete('/:id',
  authenticate,
  postController.deletePost
);

module.exports = router;

Main Application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// src/app.js
const express = require('express');
const mongoose = require('mongoose');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
require('dotenv').config();

const authRoutes = require('./routes/auth');
const postRoutes = require('./routes/posts');
const { errorHandler, notFound } = require('./middleware/errorHandler');
const { generalLimiter } = require('./middleware/rateLimiter');

const app = express();

// Database connection
mongoose.connect(process.env.MONGODB_URI)
  .then(() => console.log('Connected to MongoDB'))
  .catch(err => console.error('MongoDB connection error:', err));

// Middleware
app.use(helmet()); // Security headers
app.use(cors()); // CORS
app.use(morgan('combined')); // Logging
app.use(express.json()); // Parse JSON
app.use(express.urlencoded({ extended: true }));

// Rate limiting
app.use('/api/', generalLimiter);

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// API routes
app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/posts', postRoutes);

// Error handling
app.use(notFound);
app.use(errorHandler);

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

module.exports = app;

Environment Configuration

1
2
3
4
5
# .env
NODE_ENV=development
PORT=3000
MONGODB_URI=mongodb://localhost:27017/blog-api
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production

πŸ§ͺ Testing REST APIs

Unit Testing

Test Auth Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// tests/unit/authController.test.js
const { expect } = require('chai');
const sinon = require('sinon');
const jwt = require('jsonwebtoken');
const authController = require('../../src/controllers/authController');
const User = require('../../src/models/User');

describe('Auth Controller', () => {
  describe('register', () => {
    it('should create a new user and return token', async () => {
      const req = {
        body: {
          name: 'Test User',
          email: 'test@example.com',
          password: 'Password123'
        }
      };
      const res = {
        status: sinon.stub().returnsThis(),
        json: sinon.spy()
      };
      
      const userStub = sinon.stub(User, 'create').resolves({
        _id: '123',
        name: 'Test User',
        email: 'test@example.com',
        toJSON: () => ({ id: '123', name: 'Test User', email: 'test@example.com' })
      });
      
      const jwtStub = sinon.stub(jwt, 'sign').returns('fake-token');
      
      await authController.register(req, res, () => {});
      
      expect(res.status.calledWith(201)).to.be.true;
      expect(res.json.called).to.be.true;
      
      userStub.restore();
      jwtStub.restore();
    });
  });
});

Integration Testing

Test Post Endpoints:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// tests/integration/posts.test.js
const request = require('supertest');
const { expect } = require('chai');
const app = require('../../src/app');
const User = require('../../src/models/User');
const Post = require('../../src/models/Post');

describe('POST /api/v1/posts', () => {
  let token;
  let userId;
  
  before(async () => {
    // Create test user
    const user = await User.create({
      name: 'Test User',
      email: 'test@example.com',
      password: 'Password123'
    });
    userId = user._id;
    
    // Get auth token
    const response = await request(app)
      .post('/api/v1/auth/login')
      .send({
        email: 'test@example.com',
        password: 'Password123'
      });
    
    token = response.body.token;
  });
  
  after(async () => {
    await User.deleteMany({});
    await Post.deleteMany({});
  });
  
  it('should create a new post', async () => {
    const response = await request(app)
      .post('/api/v1/posts')
      .set('Authorization', `Bearer ${token}`)
      .send({
        title: 'Test Post',
        content: 'This is test content',
        tags: ['test', 'example'],
        status: 'published'
      });
    
    expect(response.status).to.equal(201);
    expect(response.body).to.have.property('post');
    expect(response.body.post).to.have.property('title', 'Test Post');
  });
  
  it('should return 401 without token', async () => {
    const response = await request(app)
      .post('/api/v1/posts')
      .send({
        title: 'Test Post',
        content: 'This is test content'
      });
    
    expect(response.status).to.equal(401);
  });
  
  it('should return 422 with invalid data', async () => {
    const response = await request(app)
      .post('/api/v1/posts')
      .set('Authorization', `Bearer ${token}`)
      .send({
        title: 'Bad', // Too short
        content: 'Short' // Too short
      });
    
    expect(response.status).to.equal(422);
    expect(response.body.error).to.have.property('code', 'VALIDATION_ERROR');
  });
});

πŸ“ˆ Performance Optimization

1. Database Query Optimization

Use Indexing:

1
2
3
4
// Create indexes
postSchema.index({ author: 1, status: 1 });
postSchema.index({ createdAt: -1 });
postSchema.index({ title: 'text', content: 'text' }); // Text search

Use Projection:

1
2
3
4
5
// Bad - loads all fields
const posts = await Post.find();

// Good - only load needed fields
const posts = await Post.find().select('title author createdAt');

Use Lean Queries:

1
2
3
4
5
// Bad - returns Mongoose documents (slower)
const posts = await Post.find();

// Good - returns plain JavaScript objects (faster)
const posts = await Post.find().lean();

2. Caching

Redis Caching:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const redis = require('redis');
const client = redis.createClient();

exports.getCachedPosts = async (req, res, next) => {
  const cacheKey = `posts:${req.query.page || 1}`;
  
  try {
    // Check cache
    const cached = await client.get(cacheKey);
    
    if (cached) {
      return res.json(JSON.parse(cached));
    }
    
    // If not cached, proceed to controller
    next();
  } catch (error) {
    // On cache error, proceed without cache
    next();
  }
};

// In controller
exports.getPosts = async (req, res, next) => {
  try {
    const posts = await Post.find()...;
    
    const response = {
      data: posts,
      pagination: {...}
    };
    
    // Cache for 5 minutes
    const cacheKey = `posts:${req.query.page || 1}`;
    await client.setEx(cacheKey, 300, JSON.stringify(response));
    
    res.json(response);
  } catch (error) {
    next(error);
  }
};

3. Response Compression

1
2
3
4
5
6
7
8
9
10
11
12
const compression = require('compression');

app.use(compression({
  level: 6, // Compression level (0-9)
  threshold: 1024, // Only compress responses larger than 1kb
  filter: (req, res) => {
    if (req.headers['x-no-compression']) {
      return false;
    }
    return compression.filter(req, res);
  }
}));

4. Pagination Best Practices

Cursor-Based for Large Datasets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
exports.getPostsCursor = async (req, res, next) => {
  try {
    const { cursor, limit = 20 } = req.query;
    
    const query = {};
    
    if (cursor) {
      // Decode cursor (contains last seen ID)
      const decodedCursor = Buffer.from(cursor, 'base64').toString('ascii');
      query._id = { $gt: decodedCursor };
    }
    
    const posts = await Post.find(query)
      .limit(parseInt(limit) + 1)
      .sort({ _id: 1 });
    
    const hasMore = posts.length > limit;
    const results = hasMore ? posts.slice(0, limit) : posts;
    
    const nextCursor = hasMore
      ? Buffer.from(results[results.length - 1]._id.toString()).toString('base64')
      : null;
    
    res.json({
      data: results,
      pagination: {
        nextCursor,
        hasMore
      }
    });
  } catch (error) {
    next(error);
  }
};

πŸ” API Monitoring and Analytics

Key Metrics to Track

  1. Request Metrics:
    • Request count (per endpoint)
    • Response times (average, p95, p99)
    • Error rates (4xx, 5xx)
    • Request sizes
  2. Performance Metrics:
    • API latency
    • Database query times
    • External API call times
    • Cache hit/miss rates
  3. Business Metrics:
    • API usage per user/key
    • Most popular endpoints
    • Geographic distribution
    • Time-based patterns

Logging Best Practices

Structured Logging:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  defaultMeta: { service: 'blog-api' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// Log middleware
app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    
    logger.info('API Request', {
      method: req.method,
      path: req.path,
      statusCode: res.statusCode,
      duration,
      userAgent: req.get('user-agent'),
      ip: req.ip,
      userId: req.user ? req.user._id : null
    });
  });
  
  next();
});

🎯 REST API Best Practices Summary

Design Principles

  1. Consistency is Key
    • Use consistent naming conventions
    • Maintain uniform response structures
    • Apply the same patterns across all endpoints
  2. Think in Resources, Not Actions
    • Design around nouns (resources), not verbs (actions)
    • Use HTTP methods to indicate actions
    • Keep URLs hierarchical and logical
  3. Version from Day One
    • Start with v1 even for the first version
    • Plan for future changes
    • Maintain backward compatibility
  4. Security First
    • Always use HTTPS in production
    • Implement proper authentication/authorization
    • Validate all inputs
    • Use rate limiting
    • Never expose sensitive information in errors
  5. Documentation is Essential
    • Maintain up-to-date API documentation
    • Provide clear examples
    • Document all error codes
    • Include authentication guides
  6. Design for Failure
    • Implement proper error handling
    • Use meaningful HTTP status codes
    • Provide descriptive error messages
    • Include request IDs for debugging
  7. Performance Matters
    • Implement caching where appropriate
    • Use pagination for large datasets
    • Optimize database queries
    • Compress responses
    • Monitor and measure performance
  8. Make it Testable
    • Write unit and integration tests
    • Use test environments
    • Implement health check endpoints
    • Monitor in production

Common Pitfalls to Avoid

❌ Don’t:

  • Use verbs in endpoint names (/getUsers, /createPost)
  • Return different structures for the same resource
  • Ignore HTTP status codes (always return 200)
  • Expose internal implementation details
  • Skip authentication on public endpoints
  • Nest resources too deeply (more than 2-3 levels)
  • Change API behavior without versioning
  • Return HTML errors for JSON APIs
  • Store state on the server (violates REST principles)
  • Hardcode URLs in responses

βœ… Do:

  • Use nouns for resources (/users, /posts)
  • Maintain consistent response structures
  • Use appropriate HTTP status codes
  • Abstract implementation details
  • Secure all endpoints appropriately
  • Keep resource nesting shallow
  • Version your API properly
  • Return JSON errors for JSON APIs
  • Keep APIs stateless
  • Use HATEOAS links where beneficial

πŸ“š Additional Resources and Tools

Development Tools

API Testing:

  • Postman: Popular API testing tool with collections and automation
  • Insomnia: Lightweight REST client
  • Thunder Client: VS Code extension for API testing
  • HTTPie: Command-line HTTP client
  • cURL: Universal command-line tool

API Documentation:

  • Swagger/OpenAPI: Industry standard specification
  • ReDoc: Beautiful API documentation from OpenAPI specs
  • Postman Docs: Auto-generated documentation
  • API Blueprint: Markdown-based documentation
  • Stoplight: API design and documentation platform

API Monitoring:

  • Datadog: Comprehensive monitoring platform
  • New Relic: Application performance monitoring
  • Grafana: Metrics visualization
  • Prometheus: Time-series database and monitoring
  • ELK Stack: Elasticsearch, Logstash, Kibana for logging

API Management:

  • Kong: Open-source API gateway
  • AWS API Gateway: Managed API gateway service
  • Azure API Management: Microsoft’s API management service
  • Google Apigee: Google’s API management platform
  • Tyk: Open-source API gateway

Load Testing:

  • Apache JMeter: Open-source load testing tool
  • Artillery: Modern load testing toolkit
  • k6: Developer-centric load testing tool
  • Gatling: High-performance load testing framework
  • Locust: Python-based load testing tool

Security Tools

  • OWASP ZAP: Security testing tool
  • Burp Suite: Web security testing
  • Snyk: Vulnerability scanning
  • npm audit: Node.js security auditing
  • SSL Labs: SSL/TLS configuration testing

Code Examples in Different Languages

Python (Flask)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'your-secret-key'
jwt = JWTManager(app)

# In-memory storage (use database in production)
users = []
posts = []

@app.route('/api/v1/auth/register', methods=['POST'])
def register():
    data = request.get_json()
    
    # Validate input
    if not data.get('email') or not data.get('password'):
        return jsonify({
            'error': {
                'code': 'VALIDATION_ERROR',
                'message': 'Email and password required'
            }
        }), 422
    
    # Check if user exists
    if any(u['email'] == data['email'] for u in users):
        return jsonify({
            'error': {
                'code': 'DUPLICATE_ERROR',
                'message': 'Email already exists'
            }
        }), 409
    
    # Create user
    user = {
        'id': len(users) + 1,
        'name': data.get('name'),
        'email': data['email'],
        'password': generate_password_hash(data['password'])
    }
    users.append(user)
    
    # Generate token
    access_token = create_access_token(identity=user['id'])
    
    return jsonify({
        'user': {
            'id': user['id'],
            'name': user['name'],
            'email': user['email']
        },
        'token': access_token
    }), 201

@app.route('/api/v1/posts', methods=['GET'])
def get_posts():
    page = request.args.get('page', 1, type=int)
    limit = request.args.get('limit', 20, type=int)
    
    start = (page - 1) * limit
    end = start + limit
    
    paginated_posts = posts[start:end]
    
    return jsonify({
        'data': paginated_posts,
        'pagination': {
            'page': page,
            'limit': limit,
            'total': len(posts)
        }
    })

@app.route('/api/v1/posts', methods=['POST'])
@jwt_required()
def create_post():
    data = request.get_json()
    
    post = {
        'id': len(posts) + 1,
        'title': data['title'],
        'content': data['content'],
        'author_id': get_jwt_identity()
    }
    posts.append(post)
    
    return jsonify({'post': post}), 201

if __name__ == '__main__':
    app.run(debug=True)

Java (Spring Boot)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// User Entity
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    @Column(nullable = false)
    private String password;
    
    // Getters and setters
}

// Post Entity
@Entity
@Table(name = "posts")
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String title;
    
    @Column(nullable = false, columnDefinition = "TEXT")
    private String content;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private User author;
    
    private LocalDateTime createdAt = LocalDateTime.now();
    
    // Getters and setters
}

// Post Controller
@RestController
@RequestMapping("/api/v1/posts")
public class PostController {
    
    @Autowired
    private PostService postService;
    
    @GetMapping
    public ResponseEntity<Map<String, Object>> getPosts(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "20") int limit) {
        
        Page<Post> postsPage = postService.getPosts(page - 1, limit);
        
        Map<String, Object> response = new HashMap<>();
        response.put("data", postsPage.getContent());
        
        Map<String, Object> pagination = new HashMap<>();
        pagination.put("page", page);
        pagination.put("limit", limit);
        pagination.put("total", postsPage.getTotalElements());
        pagination.put("pages", postsPage.getTotalPages());
        response.put("pagination", pagination);
        
        return ResponseEntity.ok(response);
    }
    
    @PostMapping
    public ResponseEntity<Map<String, Object>> createPost(
            @Valid @RequestBody PostRequest request,
            @AuthenticationPrincipal User user) {
        
        Post post = postService.createPost(request, user);
        
        Map<String, Object> response = new HashMap<>();
        response.put("post", post);
        
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<?> getPost(@PathVariable Long id) {
        return postService.getPost(id)
            .map(post -> {
                Map<String, Object> response = new HashMap<>();
                response.put("post", post);
                return ResponseEntity.ok(response);
            })
            .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(Map.of(
                    "error", Map.of(
                        "code", "NOT_FOUND",
                        "message", "Post not found"
                    )
                )));
    }
}

// Exception Handler
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, Object>> handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        
        List<Map<String, String>> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> Map.of(
                "field", error.getField(),
                "message", error.getDefaultMessage()
            ))
            .collect(Collectors.toList());
        
        Map<String, Object> response = new HashMap<>();
        response.put("error", Map.of(
            "code", "VALIDATION_ERROR",
            "message", "Validation failed",
            "details", errors
        ));
        
        return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(response);
    }
}

Go (Gin Framework)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
)

type User struct {
    ID       uint   `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email"`
    Password string `json:"-"` // Never return password
}

type Post struct {
    ID       uint   `json:"id"`
    Title    string `json:"title"`
    Content  string `json:"content"`
    AuthorID uint   `json:"author_id"`
}

var users []User
var posts []Post

func main() {
    router := gin.Default()
    
    v1 := router.Group("/api/v1")
    {
        auth := v1.Group("/auth")
        {
            auth.POST("/register", register)
            auth.POST("/login", login)
        }
        
        postsGroup := v1.Group("/posts")
        {
            postsGroup.GET("", getPosts)
            postsGroup.POST("", authMiddleware(), createPost)
            postsGroup.GET("/:id", getPost)
        }
    }
    
    router.Run(":8080")
}

func register(c *gin.Context) {
    var input struct {
        Name     string `json:"name" binding:"required"`
        Email    string `json:"email" binding:"required,email"`
        Password string `json:"password" binding:"required,min=8"`
    }
    
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusUnprocessableEntity, gin.H{
            "error": gin.H{
                "code":    "VALIDATION_ERROR",
                "message": err.Error(),
            },
        })
        return
    }
    
    // Create user (hash password in production)
    user := User{
        ID:       uint(len(users) + 1),
        Name:     input.Name,
        Email:    input.Email,
        Password: input.Password,
    }
    users = append(users, user)
    
    // Generate token
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "user_id": user.ID,
    })
    tokenString, _ := token.SignedString([]byte("secret"))
    
    c.JSON(http.StatusCreated, gin.H{
        "user": gin.H{
            "id":    user.ID,
            "name":  user.Name,
            "email": user.Email,
        },
        "token": tokenString,
    })
}

func getPosts(c *gin.Context) {
    page := 1
    limit := 20
    
    if p, ok := c.GetQuery("page"); ok {
        // Parse page (simplified)
        _ = p
    }
    
    c.JSON(http.StatusOK, gin.H{
        "data": posts,
        "pagination": gin.H{
            "page":  page,
            "limit": limit,
            "total": len(posts),
        },
    })
}

func createPost(c *gin.Context) {
    var input struct {
        Title   string `json:"title" binding:"required,min=5,max=200"`
        Content string `json:"content" binding:"required,min=10"`
    }
    
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusUnprocessableEntity, gin.H{
            "error": gin.H{
                "code":    "VALIDATION_ERROR",
                "message": err.Error(),
            },
        })
        return
    }
    
    // Get user from context (set by authMiddleware)
    userID := c.GetUint("user_id")
    
    post := Post{
        ID:       uint(len(posts) + 1),
        Title:    input.Title,
        Content:  input.Content,
        AuthorID: userID,
    }
    posts = append(posts, post)
    
    c.JSON(http.StatusCreated, gin.H{
        "post": post,
    })
}

func getPost(c *gin.Context) {
    id := c.Param("id")
    
    // Find post (simplified)
    _ = id
    
    c.JSON(http.StatusOK, gin.H{
        "post": posts[0],
    })
}

func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        
        if tokenString == "" {
            c.JSON(http.StatusUnauthorized, gin.H{
                "error": gin.H{
                    "code":    "UNAUTHORIZED",
                    "message": "No token provided",
                },
            })
            c.Abort()
            return
        }
        
        // Verify token (simplified)
        c.Set("user_id", uint(1))
        c.Next()
    }
}

πŸŽ“ Learning Path and Next Steps

Beginner Level

  1. Understand HTTP Fundamentals
    • HTTP methods and status codes
    • Headers and request/response structure
    • Basic client-server communication
  2. Learn REST Principles
    • Six architectural constraints
    • Resource-oriented design
    • Stateless communication
  3. Build Simple APIs
    • Create basic CRUD endpoints
    • Implement proper status codes
    • Structure JSON responses

Intermediate Level

  1. Master Authentication & Authorization
    • Implement JWT authentication
    • Understand OAuth 2.0 flows
    • Role-based access control
  2. API Design Patterns
    • Pagination strategies
    • Filtering and sorting
    • Versioning approaches
  3. Error Handling & Validation
    • Comprehensive error responses
    • Input validation
    • Rate limiting

Advanced Level

  1. Performance Optimization
    • Caching strategies
    • Database query optimization
    • Response compression
  2. API Documentation
    • OpenAPI/Swagger specifications
    • Interactive documentation
    • Code generation
  3. Monitoring & Analytics
    • Logging and metrics
    • Performance monitoring
    • User analytics
  4. Microservices & Scalability
    • API gateways
    • Service mesh
    • Distributed systems

🎯 Conclusion

Building effective REST APIs requires understanding both the theoretical principles and practical implementation details. Key takeaways:

  1. Follow REST Constraints: Adhere to the six architectural constraints for truly RESTful APIs
  2. Design Around Resources: Think in terms of nouns (resources) rather than verbs (actions)
  3. Use HTTP Properly: Leverage HTTP methods, status codes, and headers correctly
  4. Prioritize Security: Always implement authentication, authorization, and input validation
  5. Document Thoroughly: Comprehensive documentation is crucial for API adoption
  6. Version Intelligently: Plan for changes from the beginning
  7. Monitor and Optimize: Continuously measure and improve performance
  8. Think About Scalability: Design APIs that can grow with your application

REST APIs are the backbone of modern web applications. By following best practices and staying updated with evolving standards, you can build APIs that are scalable, maintainable, and developer-friendly.


πŸ“– References


This post is licensed under CC BY 4.0 by the author.