Home
Backend from First Principles / Module 33 — API Gateway, Reverse Proxy & BFF

API Gateway, Reverse Proxy & BFF

What sits in front of your services. Nginx, load balancers, the BFF pattern — the edge of your system.


The Edge of Your System

When a request arrives at your application, it almost never goes straight to your code. Something sits in front of your servers — handling TLS, routing, rate limiting, auth checks, and a dozen other concerns that don't belong in your business logic.

That something has different names depending on what it does:

In practice these blur into each other. A modern setup might use Nginx as a reverse proxy, AWS ALB as a load balancer, Kong as an API gateway, and Cloudflare at the edge — all in the same request path. Or one product (like Cloudflare) might do everything.

The key insight: there's a layer between users and your application code where cross-cutting concerns belong. Putting them in your application leaks complexity everywhere.


Reverse Proxy — The Foundation

A reverse proxy is a server that accepts requests from clients and forwards them to backend servers. The client never knows there are multiple backends; from their view, the proxy IS the server.

Text
                                  ┌────────────────┐
                                  │   Backend 1    │
                                  │   (Node.js)    │
                                  └────────────────┘
                                          ▲
                                          │
   Client  ──►  Reverse Proxy  ──►  ──────┼──────────►  Backend 2
                  (Nginx)                 │              (Node.js)
                                          │
                                          ▼
                                  ┌────────────────┐
                                  │   Backend 3    │
                                  │   (Node.js)    │
                                  └────────────────┘

Nginx is the most common reverse proxy. A minimal config:

Nginx
upstream app_servers {
    server 10.0.1.10:3000;
    server 10.0.1.11:3000;
    server 10.0.1.12:3000;
}

server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate     /etc/ssl/certs/server.pem;
    ssl_certificate_key /etc/ssl/private/server.key;

    location / {
        proxy_pass http://app_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

What the reverse proxy does for you:

This is the first piece of infrastructure most apps need beyond a single server.


Load Balancing Strategies

When you have multiple backends, the reverse proxy needs an algorithm to decide which one gets the next request.

Snippet
Round-robin — Rotate through backends in order. Simple, default, fine for stateless services.
  Backend 1 → Backend 2 → Backend 3 → Backend 1 → ...

Least connections — Send to whichever backend has the fewest active connections. Better when request durations vary widely (some are 100ms, some are 30 seconds).

IP hash — Hash the client IP, always send that IP to the same backend. Sticky-ish; useful for stateful protocols like WebSockets.

Weighted — Some backends get more traffic than others (e.g., a beefier instance handles 2x the load).

Health checks — The load balancer pings each backend periodically. Failing backends get pulled from the pool automatically until they recover.

Nginx
upstream app_servers {
    least_conn;                              # algorithm
    server 10.0.1.10:3000 weight=2;          # gets 2x traffic
    server 10.0.1.11:3000;
    server 10.0.1.12:3000 backup;            # only used when others fail
}

The big win: a backend can crash, be deployed, or get patched, and traffic seamlessly shifts to healthy ones. This is how you achieve zero-downtime deploys (combined with graceful shutdown from Module 17).


API Gateway — Beyond a Reverse Proxy

An API gateway is a reverse proxy that understands your API. It can do more than route — it can transform, authenticate, rate-limit, and version-route based on what's in the request.

Common API gateway responsibilities:

Text
       Request enters gateway
              │
              ▼
       ┌─────────────┐
       │ TLS         │ Decrypt HTTPS
       │ termination │
       └──────┬──────┘
              ▼
       ┌─────────────┐
       │ Auth check  │ Validate JWT, API key
       │             │ Reject 401 immediately
       └──────┬──────┘
              ▼
       ┌─────────────┐
       │ Rate limit  │ "100 requests/min per user"
       │             │ 429 if exceeded
       └──────┬──────┘
              ▼
       ┌─────────────┐
       │ Routing     │ /v1/users → users-service
       │             │ /v1/orders → orders-service
       └──────┬──────┘
              ▼
       ┌─────────────┐
       │ Transform   │ Add headers, rewrite paths
       │             │ Aggregate from multiple services
       └──────┬──────┘
              ▼
       ┌─────────────┐
       │ Logging /   │ Capture request, response,
       │ tracing     │ correlation IDs, latencies
       └──────┬──────┘
              ▼
       Forwarded to backend service

Common API gateways:

When you need an API gateway: the moment you have multiple backend services AND any of these cross-cutting concerns become messy to handle in each service individually. Most teams reach for one when they have 3+ microservices.

When you DON'T need one: a single service or a small monolith. Nginx as a plain reverse proxy is enough. API gateways add real complexity — don't add them prematurely.


The BFF Pattern — Backend For Frontend

As products grow, you often have multiple clients with different needs: a web app, an iOS app, an Android app, partner integrations. They want different shapes of data, different fields, different combinations.

Naive approach: build one API and have every client adapt. Result: the API becomes a compromise that serves no client well.

The BFF (Backend For Frontend) pattern: each client gets its own dedicated backend layer, optimized for that client.

Text
                ┌─────────────────────────┐
                │  Domain Services        │
                │  (users, orders, ...)   │
                └─────────────┬───────────┘
                              │
            ┌─────────────────┼─────────────────┐
            ▼                 ▼                 ▼
       ┌─────────┐       ┌─────────┐       ┌─────────┐
       │ Web BFF │       │ iOS BFF │       │ Partner │
       │         │       │         │       │  BFF    │
       └────┬────┘       └────┬────┘       └────┬────┘
            ▼                 ▼                 ▼
       Web app            iOS app           Partners

What each BFF does:

Real-world example: Netflix has separate BFFs for each device family (TV apps, mobile, web, game consoles). Each BFF speaks the protocol that device prefers (REST, GraphQL, gRPC) and exposes endpoints tuned to that device's needs.

When BFF makes sense:
• Multiple distinct clients with different needs
• Mobile clients especially benefit (each network round trip is expensive on cellular)
• Different teams own different clients and want autonomy

When NOT to use BFF:
• One client. You don't need an extra layer between your app and the world.
• A small team that would have to maintain N BFFs alone.

The BFF is one of those patterns that looks like over-engineering until your single API has accumulated client-specific endpoints (/users/web, /users/mobile) and special-casing all over the place. At that point, BFF is the cleanup.


⁂ Back to all modules