Skip to content

Layered System in REST — Proxies, Gateways, Firewalls, and CDN Architecture

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Layered System in REST. We cover key concepts, practical examples, and best practices to help you master this topic.

The layered system constraint in REST allows intermediate components like proxies, gateways, firewalls, and CDNs to be inserted between client and server without requiring changes to either, enabling scalability, security, and performance optimizations.

What You'll Learn

  • What the layered system constraint enables
  • How to configure your API for layered deployments
  • Common proxy headers and their purposes

Why It Matters

A well-architected layered system lets you add caching, load balancing, authentication, and rate limiting without touching application code. Your API server doesn't know if it's talking directly to a client or through five intermediate layers, because each layer only knows about the next.

Real-World Use

DodaTech's API runs behind six layers: Cloudflare CDN (edge caching), NGINX reverse proxy (rate limiting and TLS termination), Kong API Gateway (authentication and routing), a caching layer (Redis), the application server (Flask), and the database. Each layer is independently deployable and scalable.

flowchart LR
    C["Client"] --> CDN["Cloudflare CDN\nEdge Cache"]
    CDN --> NGINX["NGINX\nTLS + Rate Limit"]
    NGINX --> GW["Kong Gateway\nAuth + Routing"]
    GW --> Redis["Redis Cache\nLayer"]
    Redis --> App["Application\nServer"]
    App --> DB["Database"]
    style C fill:#dbeafe,stroke:#2563eb
    style App fill:#fef3c7,stroke:#d97706
    style DB fill:#bbf7d0,stroke:#16a34a

Proxy Headers

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/status')
def status():
    # The application sees the last proxy in the chain
    return jsonify({
        "client_ip": request.remote_addr,
        "forwarded_for": request.headers.get('X-Forwarded-For'),
        "forwarded_proto": request.headers.get('X-Forwarded-Proto'),
        "forwarded_host": request.headers.get('X-Forwarded-Host'),
        "real_ip": request.headers.get('X-Real-IP'),
        "cf_connecting_ip": request.headers.get('CF-Connecting-IP'),
        "cf_country": request.headers.get('CF-IPCountry'),
        "cf_ray": request.headers.get('CF-Ray')
    })

Trusted Proxy Configuration

from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)

# Configure trusted proxies
# num_proxies: number of proxy layers before the app
app.wsgi_app = ProxyFix(
    app.wsgi_app,
    x_for=1,      # Number of X-Forwarded-For proxies
    x_proto=1,    # Number of X-Forwarded-Proto proxies
    x_host=1,     # Number of X-Forwarded-Host proxies
    x_prefix=1    # Number of X-Forwarded-Prefix proxies
)

@app.route('/api/orders')
def get_orders():
    # Now request.remote_addr returns the real client IP
    client_ip = request.remote_addr
    return jsonify({"client_ip": client_ip, "orders": get_orders_for_ip(client_ip)})

CDN Configuration

# When using a CDN like Cloudflare, configure cache purging
import requests

class CDNManager:
    def __init__(self, api_token, zone_id):
        self.api_token = api_token
        self.zone_id = zone_id
        self.base_url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}"

    def purge_url(self, url):
        response = requests.post(
            f"{self.base_url}/purge_cache",
            headers={
                "Authorization": f"Bearer {self.api_token}",
                "Content-Type": "application/json"
            },
            json={"files": [url]}
        )
        return response.status_code == 200

    def purge_all(self):
        response = requests.post(
            f"{self.base_url}/purge_cache",
            headers={
                "Authorization": f"Bearer {self.api_token}",
                "Content-Type": "application/json"
            },
            json={"purge_everything": True}
        )
        return response.status_code == 200

cdn = CDNManager("your-api-token", "your-zone-id")

# Purge CDN cache when data changes
@app.route('/api/products/<int:product_id>', methods=['PUT'])
def update_product(product_id):
    updated = update_product_in_db(product_id, request.json)
    cdn.purge_url(f"https://api.dodatech.com/api/products/{product_id}")
    return jsonify(updated)

Common Mistakes

1. Not Configuring ProxyFix

Behind a proxy, request.remote_addr returns the proxy IP, not the client IP. Without ProxyFix, all clients appear to come from the same IP.

2. Hardcoding Service Locations

In a layered system, service addresses change. Use service discovery (DNS, Consul, Kubernetes services) instead of hardcoded IPs.

3. Not Forwarding the Original Protocol

When NGINX terminates TLS, the app sees HTTP. Forward X-Forwarded-Proto so the app generates correct HTTPS URLs.

4. Allowing Direct Access to Internal Servers

Internal servers (Redis, database) must not be directly accessible from the internet. Layer security at the edge.

5. Ignoring Layer Latency

Each layer adds latency. Measure and optimize each layer. A six-layer system should still respond in under 200ms.

Practice Questions

  1. What is the purpose of the layered system constraint?
  2. What header carries the original client IP through proxies?
  3. How does ProxyFix middleware work?
  4. Why should you purge CDN cache after updates?
  5. How do you measure latency in a layered system?

Answers

  1. To allow intermediate components without affecting client or server code. 2. X-Forwarded-For header. 3. It reads proxy headers and updates request.remote_addr and other properties to reflect the original client. 4. To prevent users from receiving stale cached data after updates. 5. Use tracing headers (X-Request-Id, X-Trace-Id) and measure at each layer.

Challenge

Build a multi-layer API deployment with: a CDN caching layer (simulated), an NGINX reverse proxy, a Kong API Gateway for routing, and an application server. Configure proxy headers at each layer so the application correctly identifies the original client IP and protocol.

FAQ

What is the layered system constraint?

A REST constraint that allows intermediaries (proxies, gateways) between client and server.

What is X-Forwarded-For?

An HTTP header that carries the original client IP through proxy chains.

How do I get the real client IP behind a proxy?

Use ProxyFix middleware (Werkzeug/Flask) or read X-Forwarded-For and X-Real-IP headers.

Can layers add security?

Yes. Firewalls, rate limiters, and authentication gateways add security at different layers.

What is service discovery in a layered system?

A mechanism for services to find each other dynamically without hardcoded addresses.

Mini Project

Create a docker-compose setup with four layers: a Cloudflare-like caching layer (using Varnish), an NGINX reverse proxy, a Flask API server, and PostgreSQL. Configure all proxy headers to pass through correctly, and demonstrate that the application sees the real client IP and protocol.

What's Next

  • Learn about code-on-demand as an optional REST constraint
  • Explore resource naming conventions for consistent URI design
  • Continue to resource relationships and sub-resources

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro