0Pricing
PHP Academy · Lesson

API Gateways and Service Discovery

Route, aggregate and locate services dynamically.

API Gateways and Service Discovery is a free PHP Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the PHP Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Gateways & Discovery

Once you have a dozen services, two problems appear. Clients shouldn't need to know each service's address or call five of them to render one screen — that's the API gateway's job. And services that scale up/down and move IPs need a way to find each other — that's service discovery.

This lesson covers both, with PHP at the gateway edge.

What an API Gateway Does

An API gateway is a single entry point in front of your services. Typical responsibilities:

  • Routing requests to the right backend.
  • Cross-cutting concerns: authentication, rate limiting, CORS, TLS termination.
  • Aggregation: compose several backend calls into one client response.
  • Protocol translation: external REST in, internal gRPC out.

It keeps clients simple and centralizes policy you'd otherwise duplicate in every service.

Routing at the Edge

At its core a gateway maps an inbound path to an upstream service. Production gateways (Kong, Traefik, Nginx, AWS API Gateway) do this declaratively, but the logic is simple enough to illustrate in PHP.

<?php
$routes = [
    '#^/api/orders#'    => 'http://orders-svc',
    '#^/api/customers#' => 'http://customers-svc',
    '#^/api/catalog#'   => 'http://catalog-svc',
];

function resolveUpstream(string $path, array $routes): ?string {
    foreach ($routes as $pattern => $upstream) {
        if (preg_match($pattern, $path)) {
            return $upstream . $path;
        }
    }
    return null; // 404 at the gateway
}

echo resolveUpstream('/api/orders/42', $routes), "\n";

Centralizing Auth

Validate the caller once at the gateway, then forward a trusted identity downstream so each service doesn't re-verify the raw token. The gateway checks the JWT signature/expiry and injects headers like X-User-Id into the internal request (over a trusted network).

<?php
function authenticate(string $authHeader): ?array {
    if (!str_starts_with($authHeader, 'Bearer ')) return null;
    $jwt = substr($authHeader, 7);
    $claims = verifyJwt($jwt);            // signature + exp check
    if ($claims === null) return null;
    // Forward minimal trusted identity to internal services
    return ['X-User-Id' => $claims['sub'], 'X-Scopes' => implode(',', $claims['scopes'])];
}
function verifyJwt(string $j): ?array { return ['sub' => 'u-7', 'scopes' => ['orders:read']]; }
print_r(authenticate('Bearer abc.def.ghi'));

Response Aggregation

A mobile screen may need order, customer, and catalog data. Rather than make the client fire three calls, the gateway fans out, waits, and merges. To keep this fast, issue the upstream calls concurrently (Guzzle promises / curl_multi) rather than sequentially.

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;

$http = new Client(['timeout' => 2.0]);
$promises = [
    'order'    => $http->getAsync('http://orders-svc/orders/42'),
    'customer' => $http->getAsync('http://customers-svc/customers/7'),
    'catalog'  => $http->getAsync('http://catalog-svc/items?order=42'),
];
$results = Utils::settle($promises)->wait(); // run in parallel
// merge the fulfilled bodies into one response for the client

Backends for Frontends

One generic gateway often can't serve a web app, a mobile app, and partners equally well — each wants different aggregations and payload shapes. The Backend-for-Frontend (BFF) pattern gives each client type its own thin gateway, tailored to its needs, while shared services stay generic.

This avoids a bloated god-gateway and lets each client team move independently.

The Discovery Problem

In a dynamic environment instances come and go and their IPs change. Hardcoding http://10.0.3.14:8080 is fragile. Service discovery keeps a live registry of "which healthy instances of service X exist right now" so callers resolve a logical name to a real address at call time.

Client-Side vs Server-Side Discovery

Two models:

  • Client-side — the caller queries a registry (Consul, etcd) and picks an instance itself, doing its own load balancing.
  • Server-side — the caller hits a stable virtual address (a load balancer / Kubernetes Service) that resolves and balances for it.

In Kubernetes you usually get server-side discovery for free: call http://customers-svc and the cluster DNS + Service handle the rest. Outside k8s, Consul-style registries are common.

Querying a Registry

With client-side discovery, the PHP caller asks the registry for healthy instances and chooses one. The registry only returns instances that pass health checks, so dead nodes are excluded automatically.

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;

function discover(Client $http, string $service): string {
    // Consul: only passing-health instances
    $res = $http->get("http://consul:8500/v1/health/service/$service?passing=true");
    $nodes = json_decode((string) $res->getBody(), true);
    if (!$nodes) throw new \RuntimeException("No healthy $service");
    $pick = $nodes[array_rand($nodes)]['Service']; // simple LB
    return "http://{$pick['Address']}:{$pick['Port']}";
}

Health Checks & Registration

Discovery is only as good as its health data. Each service exposes a /health endpoint that checks its real dependencies (DB, cache), and registers itself (or is registered by the platform) on startup. The registry probes that endpoint and drops failing instances.

Make the health check meaningful: returning 200 while the database is down is worse than useless — it routes traffic into a broken node.

<?php
// GET /health
function health(PDO $db, Redis $cache): array {
    $checks = [
        'db'    => safe(fn() => $db->query('SELECT 1') !== false),
        'cache' => safe(fn() => $cache->ping() === '+PONG'),
    ];
    $ok = !in_array(false, $checks, true);
    http_response_code($ok ? 200 : 503);
    return ['status' => $ok ? 'pass' : 'fail', 'checks' => $checks];
}
function safe(callable $c): bool { try { return (bool) $c(); } catch (\Throwable) { return false; } }

Liveness vs Readiness

One health endpoint isn't enough — distinguish two questions:

  • Liveness: "is the process alive?" If it fails, the orchestrator restarts the container. Keep it cheap and dependency-free, or a flaky DB triggers pointless restarts.
  • Readiness: "can it serve traffic right now?" If it fails, traffic is withheld but the process keeps running (e.g. warming a cache, DB temporarily unreachable).

Conflating them causes restart loops or routing into not-yet-ready nodes.

<?php
// GET /livez  - is the process itself healthy? (no external deps)
function livez(): void { http_response_code(200); echo 'alive'; }

// GET /readyz - should we receive traffic? (checks dependencies)
function readyz(PDO $db): void {
    try { $db->query('SELECT 1'); http_response_code(200); echo 'ready'; }
    catch (\Throwable) { http_response_code(503); echo 'not ready'; }
}

Quick Check

Reducing client round-trips.

Recap

Routing and locating services:

  • An API gateway centralizes routing, auth, rate limiting, TLS, and aggregation.
  • Aggregate concurrently; use BFFs when client needs diverge.
  • Service discovery resolves logical names to live, healthy instances.
  • Client-side (query a registry) vs server-side (stable LB / k8s Service) discovery.
  • Meaningful health checks keep traffic off broken nodes.

Next: keeping all these calls resilient when parts inevitably fail.

Frequently asked questions

Is the “API Gateways and Service Discovery” lesson free?

Yes — the full text of “API Gateways and Service Discovery” is free to read here on the web, and the PHP Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the PHP Academy course, upgrade to CoddyKit PRO.

What will I learn in “API Gateways and Service Discovery”?

Route, aggregate and locate services dynamically. You practise PHP Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start PHP Academy?

No prior experience is required. PHP Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “API Gateways and Service Discovery” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this PHP Academy lesson?

Yes. Every PHP Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. From Monolith to Microservices
  2. Service Communication: REST and gRPC
  3. API Gateways and Service Discovery
  4. Resilience: Circuit Breakers and Retries
← Back to PHP Academy