APIG

API Gateway

APIGW architecture

API Gateway

Definition

An API gateway is an infrastructure component that serves as the single entry point for all client requests to a system's backend services. It handles cross-cutting concerns including request routing, authentication, authorization, rate limiting, response aggregation, protocol translation, request/response transformation, and observability. By centralizing these responsibilities in a single layer, an API gateway simplifies client interactions and enforces consistent security and operational policies across all endpoints.

The API gateway pattern emerged from the microservices architectural style, where decomposing a monolithic application into many services creates a challenge for clients: instead of communicating with one server, clients would need to know about and manage connections to dozens of services. The gateway abstracts this complexity, presenting a unified API surface while routing requests to the appropriate backend service. This decoupling allows backend services to evolve independently -- services can be split, merged, or replaced without affecting client integrations.

Beyond simple routing, modern API gateways provide sophisticated capabilities including request validation (ensuring payloads conform to schema definitions before they reach backend services), response transformation (adapting internal data formats to client expectations), circuit breaking (preventing cascading failures when downstream services degrade), and API versioning (supporting multiple API versions simultaneously). These capabilities make the API gateway a critical piece of production infrastructure that directly impacts security posture, reliability, and developer experience.

Historical Context and Evolution

The concept of an API gateway traces its lineage through several decades of network architecture evolution. In the early days of web services, organizations deployed simple reverse proxies (Apache httpd, Nginx) to distribute traffic across backend servers. These proxies handled basic load balancing and SSL termination but offered no awareness of the API contracts they mediated.

The emergence of Service-Oriented Architecture (SOA) in the early 2000s introduced the Enterprise Service Bus (ESB), a heavyweight middleware layer that provided message routing, protocol transformation, and orchestration. ESBs like MuleSoft, IBM WebSphere, and Oracle Service Bus centralized integration logic but became notorious for complexity, vendor lock-in, and operational burden. The ESB was the spiritual predecessor of the modern API gateway, but its monolithic nature contradicted the lightweight principles that would later define microservices.

When the microservices movement gained momentum around 2012-2014, driven by organizations like Netflix, Amazon, and Spotify decomposing their monoliths, the need for a lightweight API mediation layer became acute. Netflix's Zuul gateway (open-sourced in 2013) demonstrated that a thin, programmable gateway could handle routing, filtering, and monitoring without the heavyweight orchestration of ESBs. Kong (2015), Tyk (2015), and AWS API Gateway (2015) followed, establishing the API gateway as a distinct infrastructure category.

The current generation of API gateways (Envoy, Istio, Kong 3.x, AWS API Gateway v2) has evolved beyond simple request routing to encompass service mesh integration, WebSocket support, gRPC bridging, GraphQL federation, and declarative configuration through infrastructure-as-code. The Prismatic Platform takes a distinctive approach within this landscape, implementing auto-introspecting discovery that eliminates the manual endpoint registration required by conventional gateways.

Core Responsibilities

An API gateway centralizes several cross-cutting concerns that would otherwise be duplicated across every backend service:

ResponsibilityDescriptionBenefit
Request RoutingMaps incoming paths to backend servicesClients interact with a single endpoint
AuthenticationValidates JWT tokens and API keysConsistent auth across all services
AuthorizationEnforces RBAC policies per endpointCentralized permission management
Rate LimitingThrottles requests per client/endpointPrevents abuse and resource exhaustion
Request ValidationValidates payloads against OpenAPI schemasMalformed requests rejected early
Response TransformationAdapts internal formats to client expectationsBackend independence from API contract
Protocol TranslationBridges HTTP to WebSocket, gRPC, etc.Multi-protocol support from single entry
ObservabilityLogs, metrics, and traces for all trafficUnified monitoring and debugging
CORS ManagementCross-origin resource sharing headersConsistent browser security policies
CachingResponse caching for frequently accessed dataReduced backend load and latency
VersioningSimultaneous support for multiple API versionsBackward compatibility without backend coupling
Circuit BreakingPrevents cascading failures to degraded backendsSystem resilience under partial failure

Gateway Architecture Patterns

API gateways can be implemented following several architectural patterns, each suited to different deployment contexts:

PatternDescriptionUse CaseTrade-offs
Edge GatewaySingle gateway at the network edgeSmall-medium APIsSimple but can become bottleneck
Backend for Frontend (BFF)Per-client-type gatewayMobile + Web + API clientsOptimal per-client but higher ops cost
Federated GatewayGateway-per-domain with federationMicroservices at scaleTeam autonomy but coordination overhead
Sidecar ProxyPer-service proxy (Envoy, Istio)Service mesh architecturesFine-grained control but complex
Auto-IntrospectingSelf-discovering endpoint registryConvention-over-configurationZero-config but requires conventions

The Prismatic Platform implements the auto-introspecting pattern, where the gateway discovers available endpoints at boot time through Elixir module introspection rather than requiring manual route configuration. This approach leverages the BEAM virtual machine's runtime code inspection capabilities to eliminate configuration drift between the actual codebase and the gateway's routing table.

Auto-Introspecting Gateway Design

The Prismatic API gateway (prismatic_api, port 4004) implements a unique auto-introspecting architecture that eliminates manual endpoint registration:

Boot Time:
  Scanner ─────> Module.concat(Prismatic*) ─────> Code.fetch_docs/1
     |                                                    |
     |           Code.Typespec.fetch_specs/1 <────────────┘
     |                     |
     v                     v
  Registry (ETS) <── TypeMapper ──> OpenApiSpex Schema
     |                                      |
     v                                      v
  DispatchController                   ApiSpec ──> SwaggerUI

Runtime:
  Request ──> Router ──> APIAuth ──> RateLimiter ──> DispatchController
                                                          |
                                                   Registry.lookup(app, action)
                                                          |
                                                   safe_apply(Module, :function, args)
                                                          |
                                                   JSON Response

The scanner examines all compiled modules matching the Prismatic* namespace, extracting public functions with their documentation (Code.fetch_docs/1) and type specifications (Code.Typespec.fetch_specs/1). These are registered in an ETS-backed endpoint registry and automatically mapped to OpenAPI schema definitions through the TypeMapper.

defmodule PrismaticApi.Scanner do
  @moduledoc """
  Boot-time scanner that discovers all Prismatic facade modules
  and registers their public functions as API endpoints.

  The scanner uses Elixir's code introspection capabilities to
  build the endpoint registry without manual configuration,
  ensuring the API surface always reflects the actual codebase.
  """

  @type endpoint :: %{
    module: module(),
    function: atom(),
    arity: non_neg_integer(),
    doc: String.t() | nil,
    spec: term() | nil
  }

  @spec discover_endpoints() :: {:ok, [endpoint()]} | {:error, term()}
  def discover_endpoints do
    endpoints =
      :code.all_loaded()
      |> Enum.filter(fn {module, _} -> facade_module?(module) end)
      |> Enum.flat_map(&extract_endpoints/1)

    Enum.each(endpoints, &register_endpoint/1)
    {:ok, endpoints}
  end

  @spec facade_module?(module()) :: boolean()
  defp facade_module?(module) do
    module_name = Atom.to_string(module)

    String.starts_with?(module_name, "Elixir.Prismatic") and
      not String.contains?(module_name, ".Impl.") and
      has_public_functions?(module)
  end

  defp extract_endpoints({module, _}) do
    case Code.fetch_docs(module) do
      {:docs_v1, _, _, _, _, _, docs} ->
        docs
        |> Enum.filter(&public_function?/1)
        |> Enum.map(&build_endpoint(module, &1))

      _ ->
        []
    end
  end

  defp has_public_functions?(module) do
    module.__info__(:functions)
    |> Enum.any?(fn {_name, arity} -> arity >= 0 end)
  end
end

Request Pipeline

The Prismatic API gateway processes every request through a Plug pipeline that enforces security and operational policies:

StagePlugResponsibilityFailure Response
1Plug.SSLForce HTTPS, TLS enforcement301 redirect to HTTPS
2Plug.ParsersParse JSON/form request bodies400 Bad Request
3PrismaticWeb.Plugs.RequestIdAssign unique request ID for tracing(never fails)
4PrismaticWeb.Plugs.APIAuthValidate JWT, extract user context401 Unauthorized
5PrismaticWeb.Plugs.RequireRoleCheck RBAC role requirements403 Forbidden
6PrismaticWeb.Plugs.RateLimiterEnforce per-client rate limits429 Too Many Requests
7PrismaticApi.DispatchControllerRoute to backend function404 Not Found / 500 Error
defmodule PrismaticApi.Router do
  @moduledoc """
  Gateway router with plug pipeline for authentication,
  authorization, rate limiting, and dispatch.

  Public endpoints (health, endpoint listing) bypass auth.
  All operational endpoints require JWT + RBAC validation.
  """

  use PrismaticApi, :router

  pipeline :api_gateway do
    plug :accepts, ["json"]
    plug PrismaticWeb.Plugs.RequestId
    plug PrismaticWeb.Plugs.APIAuth
    plug PrismaticWeb.Plugs.RequireRole, minimum: :viewer
    plug PrismaticWeb.Plugs.RateLimiter,
      by: :token_sub,
      limits: [
        {60, :second, 100},
        {3600, :second, 5000}
      ]
  end

  scope "/api/v1", PrismaticApi do
    pipe_through :api
    get "/health", HealthController, :check
    get "/endpoints", EndpointController, :index
  end

  scope "/api/v1", PrismaticApi do
    pipe_through [:api, :api_gateway]
    get "/:app/:action", DispatchController, :dispatch
    post "/:app/:action", DispatchController, :dispatch
  end

  scope "/api" do
    pipe_through :api
    get "/openapi", OpenApiSpex.Plug.RenderSpec, []
  end
end

Generic Dispatch Controller

The dispatch controller is the heart of the auto-introspecting gateway. It resolves incoming {app, action} path parameters to {module, function} pairs through the ETS registry and invokes the target function using safe_apply/3, which wraps the call in error handling and telemetry instrumentation:

defmodule PrismaticApi.DispatchController do
  @moduledoc """
  Generic dispatch controller that resolves API requests
  to backend function calls through the endpoint registry.
  Provides safe invocation with error handling and telemetry.
  """

  use PrismaticApi, :controller

  @spec dispatch(Plug.Conn.t(), map()) :: Plug.Conn.t()
  def dispatch(conn, %{"app" => app, "action" => action} = params) do
    case PrismaticApi.Registry.lookup(app, action) do
      {:ok, %{module: module, function: function}} ->
        args = extract_args(params)

        case safe_apply(module, function, args) do
          {:ok, result} ->
            conn
            |> put_status(:ok)
            |> json(%{data: result, status: "success"})

          {:error, reason} ->
            conn
            |> put_status(:unprocessable_entity)
            |> json(%{error: inspect(reason), status: "error"})
        end

      {:error, :not_found} ->
        conn
        |> put_status(:not_found)
        |> json(%{error: "Endpoint #{app}/#{action} not found"})
    end
  end

  @spec safe_apply(module(), atom(), list()) :: {:ok, term()} | {:error, term()}
  defp safe_apply(module, function, args) do
    start_time = System.monotonic_time()

    result = apply(module, function, args)

    :telemetry.execute(
      [:prismatic, :api, :dispatch],
      %{duration: System.monotonic_time() - start_time},
      %{module: module, function: function}
    )

    {:ok, result}
  rescue
    error ->
      {:error, {:dispatch_error, Exception.message(error)}}
  end
end

OpenAPI Integration

The gateway automatically generates a complete OpenAPI 3.0 specification from discovered endpoints. The TypeMapper converts Elixir @spec AST into JSON Schema definitions:

Elixir TypeOpenAPI SchemaNotes
String.t(){type: "string"}Direct mapping
integer(){type: "integer"}Direct mapping
boolean(){type: "boolean"}Direct mapping
float(){type: "number", format: "float"}Format annotation
[String.t()]{type: "array", items: {type: "string"}}Array wrapper
map(){type: "object"}Generic object
Date.t(){type: "string", format: "date"}Format annotation
DateTime.t(){type: "string", format: "date-time"}ISO 8601
{:ok, term()} | {:error, term()}Response schemas (200/400)Tagged tuple decomposition

The generated specification powers an interactive SwaggerUI at /api/swagger-ui, enabling developers to explore and test all discovered endpoints without writing client code.

Security Architecture

The gateway serves as the security perimeter for all API access, implementing defense-in-depth through multiple layered security mechanisms:

Security LayerImplementationProtection
TransportTLS 1.3, HSTS, force_sslEncryption in transit
AuthenticationJWT validation via GuardianIdentity verification
AuthorizationRBAC plug middlewarePermission enforcement
Rate LimitingToken bucket per client/endpointDoS protection
Input ValidationOpenAPI schema validationInjection prevention
CORSStrict origin whitelistCross-origin protection
LoggingStructured JSON with request IDAudit trail
Token ScopingOAuth2 scope validationLeast-privilege access

All security decisions are made at the gateway level, ensuring that backend services never receive unauthenticated, unauthorized, or malformed requests. This "zero trust at the edge" approach simplifies backend service security because services can trust that requests passing through the gateway have been validated. The gateway also serves as the enforcement point for the NO MERCY, NO DOUBTS doctrine, where any security policy violation results in immediate request rejection without exception.

Performance Optimization

The auto-introspecting gateway is designed to meet the platform's strict performance requirements (all pages under 250ms, health checks under 10ms):

OptimizationTechniqueImpact
ETS RegistryIn-memory endpoint lookupO(1) dispatch, ~1 microsecond
Compiled RoutesPhoenix compiled route matchingSub-microsecond routing
Connection PoolingPersistent connections to backendsEliminated TCP handshake overhead
JSON EncodingJason library (NIF-accelerated)2-5x faster than Poison
Request PipeliningHTTP/2 multiplexingReduced head-of-line blocking
Response CachingETS-based response cache with TTLEliminated redundant backend calls

Monitoring and Observability

The gateway emits comprehensive telemetry for monitoring and debugging:

MetricTypeDescription
api.request.durationHistogramEnd-to-end request latency
api.request.countCounterTotal requests by status code
api.auth.failureCounterAuthentication failures by reason
api.rate_limit.exceededCounterRate limit rejections by client
api.dispatch.durationHistogramBackend function execution time
api.error.countCounterErrors by type and endpoint
api.registry.sizeGaugeNumber of registered endpoints
api.scanner.durationHistogramBoot-time scan duration

Context in Prismatic

The Prismatic API application (prismatic_api, port 4004) functions as the platform's API gateway. It auto-discovers all Prismatic facade modules via Elixir introspection at boot time, registers them in an ETS-backed endpoint registry, and routes incoming requests through a generic dispatch controller. Authentication is handled by PrismaticWeb.Plugs.APIAuth with RBAC enforcement, and the full API surface is documented via OpenApiSpex with SwaggerUI at /api/swagger-ui.

Key gateway routes:

RouteMethodAuth RequiredDescription
/api/v1/healthGETNoHealth check endpoint
/api/v1/endpointsGETNoList all discovered endpoints
/api/v1/:app/:actionGETYesGeneric dispatch (0-2 params)
/api/v1/:app/:actionPOSTYesGeneric dispatch (3+ params)
/api/openapiGETNoOpenAPI 3.0 JSON specification
/api/swagger-uiGETNoInteractive API documentation

Comparison with Industry Alternatives

FeaturePrismatic APIGWKongAWS API GatewayEnvoy/Istio
DiscoveryAuto-introspectingDeclarative configConsole/CloudFormationxDS protocol
LanguageElixir/OTPLua/GoManaged serviceC++
Config DriftImpossible (runtime scan)PossiblePossibleReduced via control plane
LatencyMicroseconds (in-process)MillisecondsMillisecondsMicroseconds (sidecar)
ScalingBEAM schedulerHorizontal podsManaged auto-scalePer-pod sidecar
OpenAPIAuto-generated from @specPluginImport/exportN/A (gRPC native)
CostZero (built-in)License/hostingPer-request pricingOperational complexity

Best Practices

Centralize Cross-Cutting Concerns: Authentication, rate limiting, logging, and CORS should be handled exclusively at the gateway. Duplicating these concerns in backend services creates inconsistency and maintenance burden.

Validate Early, Fail Fast: Use OpenAPI schema validation at the gateway to reject malformed requests before they reach backend services. This protects backend services from invalid input and provides clear error messages to API consumers.

Version Your API Surface: Support multiple API versions through the gateway's routing layer. Backend services should be version-agnostic, with the gateway translating between client-facing API versions and internal interfaces.

Monitor Everything: Emit telemetry for every request, including latency histograms, error rates, and authentication failures. Gateway telemetry is the single most valuable source of API health data because it captures 100% of traffic.

Keep the Gateway Thin: The gateway should route, validate, and secure -- not orchestrate complex business logic. Business logic belongs in backend services. A thick gateway becomes a deployment bottleneck and single point of failure.

  • REST API - HTTP interface style served through the gateway
  • Rate Limiting - Traffic control enforced at the gateway level
  • JWT - Token-based authentication validated by the gateway
  • RBAC - Role-based authorization enforced at the gateway
  • OAuth2 - Token issuance and validation through the gateway
  • OpenAPI - Specification documenting all gateway endpoints
  • Plug - Elixir middleware composing the gateway pipeline
  • TLS - Transport encryption terminated at the gateway
  • Circuit Breaker - Resilience pattern protecting backend services
  • Observability - Monitoring and tracing through gateway telemetry
  • Microservices - Architectural style that necessitates API gateways
  • ETS - In-memory storage backing the endpoint registry

See Also


Connect & Contribute

Created by Tomas Korcak (korczis) | Open Source under GHL