Back to all projects
Status: in-progress2026-01-01

API Gateway & Management Suite

A high-performance security and observability layer designed to sit in front of distributed microservices.

FastAPIRedisPostgreSQLNginxDockerPython

Securing and monitoring microservices without touching your source code.

Microservices are often deployed without unified security or monitoring because implementing these features in every individual service is slow, repetitive, and error-prone. Without a centralized gateway, implementing features like authentication, rate limiting, and health monitoring individually leads to technical debt and inconsistent security policies. This project solves that by providing a dynamic routing layer that enforces zero-trust security and traffic shaping across any number of registered backends.

To ensure the gateway doesn't become a performance bottleneck, I engineered the system with sub-1ms auth checks using Redis-backed caching and implemented a SHA-256 pre-hashing layer to maintain high-entropy key security despite native library character limits. The result is a containerized management suite that offers real-time traffic inspection and automated health monitoring, allowing developers to secure their services with zero code changes.

Technical Approach and Execution Flow

The gateway is designed as a Modular Reverse Proxy with a clear separation between the Data Plane (handling traffic) and the Control Plane (managing settings and analytics).

Request Flow: Traffic enters through an Nginx reverse proxy, which handles SSL termination and forwards requests to the FastAPI engine. Every request passes through a global middleware layer that starts a high-resolution timer and captures the request body for logging.

Authentication Handshake: Before reaching a service, a FastAPI dependency performs a two-tier check. It first looks in Redis for a cached session. If not found, it performs a secure database lookup in PostgreSQL, verifying the API key hash before caching the result back in Redis to ensure future requests stay under 1ms.

Dynamic Routing: The system uses a "Catch-all" path logic. It extracts the service prefix from the URL, finds the matching target URL in the database, and uses an asynchronous HTTPX client to recreate the request (headers, method, and payload) to the backend.

Resilience & Background Tasks: I used the FastAPI Lifespan event to run a persistent background worker. This worker pings each registered service every 30 seconds. If a service stops responding, the worker updates the database, and the proxy logic immediately begins returning 503 errors to protect the system.

The "Bcrypt Length" Trade-off

During the implementation of API Key security, I hit a limitation where the Bcrypt library refuses to process any string longer than 72 bytes. This presented a problem because high-entropy API keys can easily exceed this limit when encoded. I needed the slow, secure hashing of Bcrypt to prevent brute-force attacks, but I couldn't risk the system crashing or truncating keys if a user provided a long token. Then I thought of implementing a SHA-256 Pre-hashing layer. Instead of sending the raw API key to Bcrypt, I first hash the key with SHA-256 to produce a fixed-length 64-character string. This string is then passed into Bcrypt for salting and hashing.

Challenges & Failure Modes

The development of this gateway was a series of successful local environment executions that failed immediately in a production environment.

The Nginx 502 "Loop": After deploying to AWS, I spent an entire day fighting a 502 Bad Gateway error. The dashboard showed a successful 200 OK, but the client received a failure. I eventually discovered that Nginx was rejecting responses because my gateway was sending both Content-Length and Transfer-Encoding headers simultaneously—a protocol violation. I had to learn how to manually "strip" conflicting headers from the backend response before passing them back to the client.

The Postgres Initialization Trap: When I first migrated from SQLite to PostgreSQL in Docker, I didn't realize that Postgres only sets the password during the first time a volume is created. I spent hours changing environment variables and wondering why the Gateway kept getting "Authentication Failed" until I realized I had to wipe the Docker volumes to force a fresh initialization.

Middleware Body Consumption: A major failure occurred when I tried to log request bodies. I didn't know that FastAPI (and Starlette) only allows the request body to be read once. By reading the body in the middleware to log it, I accidentally "emptied" the stream, causing the actual proxy router to receive an empty request. I had to implement a custom receive function to "reset" the body stream so it could be read a second time.

University Network Interference: During testing, the gateway appeared to be down, but it turned out the university firewall was intercepting unencrypted HTTP traffic and redirecting it to a login portal. This forced me to accelerate the implementation of SSL and domain name configuration much earlier than planned.

Key Takeaways

Observability is Mandatory: I learned that you cannot debug a distributed system by just looking at the code. Learning to use docker logs --follow and Nginx's error.log was the only way I was able to solve protocol-level issues. Without clear logs, I was just guessing.

Infrastructure is Harder than Logic: Writing the Python code to proxy a request was straightforward. The real work was "the plumbing"—configuring Docker networks, managing persistent volumes, and handling SSL termination. I realized that a backend developer’s job is as much about the environment as it is about the code.

The "Black Box" of Libraries: I initially relied on high-level libraries for authentication, but when they failed (like the Bcrypt character limit in Python 3.13), I had to drop down to lower-level implementations. This taught me to understand the underlying constraints of the tools I use rather than treating them as magic.

Handling State: Moving from a local SQLite file to a containerized Postgres instance taught me the importance of statelessness in application design. Ensuring that the Gateway can be destroyed and recreated without losing user data or service configurations was a major mindset shift.