Skip to content

REST Communication Between Microservices — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about REST Communication Between Microservices. We cover key concepts, practical examples, and best practices to help you master this topic.

REST communication uses HTTP methods and status codes for synchronous service-to-service interactions, relying on stateless requests and standardized resource representations for interoperability.

What You'll Learn

By the end of this lesson you will design RESTful APIs for microservices communication, handle errors consistently, implement API versioning, manage request timeouts, and structure service-to-service HTTP calls.

Why It Matters

REST is the most widely adopted protocol for microservices communication. Its simplicity and ubiquity make it the default choice for synchronous interactions. Poorly designed REST interfaces create brittle integrations, inconsistent error handling, and versioning nightmares.

Real-World Use

DodaZIP's file service exposes a REST API that the web frontend and mobile apps use to upload files, check processing status, and retrieve results. Each endpoint follows consistent patterns for pagination, error responses, and Rate Limiting.

flowchart LR
    A[Service A] -->|GET /api/v1/users/123| B[User Service]
    B -->|200 OK + JSON| A
    A -->|POST /api/v1/files| C[File Service]
    C -->|201 Created + Location| A
    style B fill:#2d3748,color:#fff
    style C fill:#2d3748,color:#fff

REST Principles for Microservices

Core REST constraints applied to service communication.

# rest_principles.py
# REST principles for microservices

def rest_principles():
    principles = {
        "Stateless": "Each request contains all information needed. No server-side session state.",
        "Resource-based": "URLs represent resources (nouns), not actions (verbs).",
        "HTTP Methods": "GET (read), POST (create), PUT (replace), PATCH (update), DELETE (remove).",
        "Status Codes": "Standard HTTP codes indicate success, client error, or server error.",
        "Uniform Interface": "Consistent patterns for collection URLs, pagination, filtering.",
    }
    
    print("REST Principles for Microservices")
    print("=" * 40)
    for principle, desc in principles.items():
        print(f"\n{principle}:")
        print(f"  {desc}")

rest_principles()

Service-to-Service Request Pattern

How one service calls another via REST.

# service_call.py
# Service-to-service REST call

def service_call_pattern():
    print("Service-to-Service REST Call Pattern")
    print("=" * 40)
    print()
    print("Request:")
    print("  GET /api/v1/orders?status=pending&page=1")
    print("  Headers:")
    print("    Authorization: Bearer <service-token>")
    print("    X-Request-ID: <trace-id>")
    print("    Accept: application/json")
    print()
    print("Response (200 OK):")
    print("  {")
    print('    "data": [{ "id": 1, "total": 29.99 }],')
    print('    "pagination": {')
    print('      "page": 1,')
    print('      "per_page": 20,')
    print('      "total": 142')
    print("    }")
    print("  }")
    print()
    print("Error Response (4xx/5xx):")
    print("  {")
    print('    "error": {')
    print('      "code": "ORDER_NOT_FOUND",')
    print('      "message": "Order with id 999 not found",')
    print('      "request_id": "req-abc-123"')
    print("    }")
    print("  }")

service_call_pattern()

Error Handling Strategy

Consistent error responses across services.

# error_handling.py
# REST error handling patterns

def error_handling():
    error_codes = {
        "400": {
            "name": "Bad Request",
            "meaning": "Invalid input format or missing required fields",
            "action": "Fix the request data before retrying"
        },
        "401": {
            "name": "Unauthorized",
            "meaning": "Missing or invalid authentication token",
            "action": "Obtain a valid token and include it in headers"
        },
        "403": {
            "name": "Forbidden",
            "meaning": "Authenticated but not authorized for this resource",
            "action": "Request elevated permissions or use different credentials"
        },
        "404": {
            "name": "Not Found",
            "meaning": "The requested resource does not exist",
            "action": "Verify the resource identifier"
        },
        "409": {
            "name": "Conflict",
            "meaning": "Request conflicts with current state (e.g., duplicate)",
            "action": "Check current state and retry with corrected data"
        },
        "429": {
            "name": "Too Many Requests",
            "meaning": "Rate limit exceeded",
            "action": "Retry after the time specified in Retry-After header"
        },
        "500": {
            "name": "Internal Server Error",
            "meaning": "Unexpected server failure",
            "action": "Retry with exponential backoff; report if persistent"
        },
        "503": {
            "name": "Service Unavailable",
            "meaning": "Service temporarily unavailable (e.g., maintenance)",
            "action": "Retry after a delay; check service health endpoint"
        },
    }
    
    print("HTTP Status Codes for Microservices REST")
    print("=" * 50)
    for code, info in error_codes.items():
        print(f"\n{code} {info['name']}")
        print(f"  Meaning: {info['meaning']}")
        print(f"  Action:  {info['action']}")

error_handling()

API Versioning

Strategies for evolving REST APIs without breaking consumers.

# versioning.py
# API versioning strategies

def versioning_strategies():
    print("API Versioning Strategies")
    print("=" * 35)
    print()
    
    strategies = [
        {
            "method": "URL Path",
            "example": "/api/v1/users, /api/v2/users",
            "pros": "Explicit, easy to route, cache-friendly",
            "cons": "URL pollution, requires redirects or separate deployments"
        },
        {
            "method": "Header",
            "example": "Accept: application/vnd.api+json;version=2",
            "pros": "Clean URLs, standard HTTP content negotiation",
            "cons": "Less visible, harder to test manually"
        },
        {
            "method": "Query Parameter",
            "example": "/api/users?version=2",
            "pros": "Simple to implement, easy to test",
            "cons": "Uncached easily, pollutes query strings"
        },
    ]
    
    for s in strategies:
        print(f"Strategy: {s['method']}")
        print(f"  Example: {s['example']}")
        print(f"  Pros:    {s['pros']}")
        print(f"  Cons:    {s['cons']}")
        print()

versioning_strategies()

Common Mistakes

  1. Using verbs in URLs: URLs should represent resources (nouns), not actions. Bad: /createOrder. Good: POST /orders.

  2. Inconsistent error format: Each service returning different error shapes forces consumers to handle every variant. Use a standardized error envelope across all services.

  3. Ignoring idempotency: POST requests that create resources can be retried. Without idempotency keys, network retries create duplicate resources.

  4. No pagination for collections: Returning all records in a single response works in development but crashes production under load. Always paginate collection endpoints.

  5. Exposing internal IDs externally: Using database primary keys in URLs exposes your data model and creates security risks. Use UUIDs or opaque identifiers instead.

Practice Questions

  1. What is the correct HTTP method for creating a resource? POST. For full replacement, use PUT. For partial update, use PATCH.

  2. What status code indicates a service is rate-limiting requests? 429 Too Many Requests. The client should respect the Retry-After header.

  3. Why should microservices use pagination in REST responses? To prevent memory exhaustion, reduce latency, and avoid transferring unnecessary data.

  4. What is an idempotency key and why is it important? A unique identifier sent with POST requests so the server can detect and reject duplicate submissions.

  5. Challenge: Design a REST API for a notification service that supports email, SMS, and push notifications. Define the resources, methods, status codes, error format, and pagination strategy.

FAQ

Should microservices always use REST?

No. REST is suitable for query-heavy sync interactions. For high-throughput or streaming scenarios, gRPC or async messaging may be better.

How do you handle partial failures in REST?

Use circuit breakers, timeouts, and retry with exponential backoff. Return 503 when a downstream dependency is unavailable.

What is the difference between PUT and PATCH?

PUT replaces the entire resource. PATCH applies partial modifications. Use PATCH for updates that change only specific fields.

How do you version a REST API?

Common approaches include URL path versioning (/v1/), header-based versioning (Accept header), or query parameter versioning (?version=2).

Can REST be used for async communication?

REST is inherently synchronous. For async, use message queues, event buses, or webhooks. Do not force synchronous REST into async scenarios.

Mini Project

Design a RESTful API for a microservice that manages user preferences across multiple services (theme, notifications, privacy settings). Define the resource model, endpoints, status codes, error format, and versioning strategy. Include pagination and idempotency.

def preferences_api():
    print("User Preferences REST API Design")
    print("=" * 40)
    print()
    print("Resource: /api/v1/users/{userId}/preferences")
    print()
    print("Endpoints:")
    print("  GET    /preferences              - List all preferences")
    print("  GET    /preferences/{key}         - Get single preference")
    print("  PUT    /preferences/{key}         - Set preference value")
    print("  PATCH  /preferences               - Bulk update preferences")
    print("  DELETE /preferences/{key}         - Reset to default")
    print()
    print("Response: {")
    print('  "preferences": {')
    print('    "theme": "dark",')
    print('    "notifications": true,')
    print('    "language": "en"')
    print("  },")
    print('  "version": 2')
    print("}")

preferences_api()

What's Next

Next: gRPC Introduction for high-performance RPC communication.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro