Production-style distributed system with stateless FastAPI services, Redis-backed caching and rate limiting, PostgreSQL persistence, and Nginx load balancing.
This project demonstrates how production-grade backend systems handle scalability, caching, rate limiting, and fault tolerance beyond basic CRUD applications.
Key goals:
- Design a scalable API with clear request/response contracts
- Introduce caching and rate limiting as core system-level concerns
- Support multiple runtime environments (local, Docker, cloud)
- Ensure reliability through automated testing and CI validation
- Provide end-to-end interaction through a lightweight frontend
- Reduced database load for hot URLs using Redis caching, cutting local redirect latency from ~
10–32 msto8–11 msand improving read-heavy scalability - Built horizontally scalable stateless FastAPI services behind an Nginx load balancer
- Implemented distributed rate limiting across replicas using Redis
- Designed and deployed a distributed system with stateless services and shared infrastructure (PostgreSQL + Redis)
- Deployed the React frontend and FastAPI backend to Render
- Frontend: https://url-shortener-frontend-av1x.onrender.com
- Backend API: https://url-shortener-gfp0.onrender.com/docs
- Cache hit latency: ~8–11 ms
- Cache miss latency: ~10–32 ms
- Supports horizontal scaling through stateless application replicas
- Redis reduces repeated database queries for hot URLs
- CI: automated backend tests on every push and pull request (GitHub Actions)
A lightweight React frontend enables:
- Creating short URLs
- Viewing generated links
- Retrieving click statistics
The frontend communicates with the deployed FastAPI backend via REST APIs, enabling end-to-end interaction with the distributed system.
When the backend returns structured error details, such as rate-limit responses, the frontend surfaces those messages in the UI.
- Frontend and backend hosted on Render
- Neon PostgreSQL provides the persistent source of truth in the hosted deployment
- Upstash Redis provides shared caching and distributed rate limiting in the hosted deployment, with graceful fallback when unavailable
- Reverse proxies should preserve
X-Forwarded-Forso Redis-backed rate limiting can identify individual clients correctly - Environment-based configuration enables seamless switching between local, Docker, and cloud deployments
- The hosted backend is configured through
DATABASE_URLandREDIS_URL
- Stateless FastAPI services behind an Nginx load balancer
- Stateless design eliminates the need for sticky sessions, enabling seamless horizontal scaling
- PostgreSQL serves as the single source of truth for durability and consistency
- Enforced strong consistency for URL creation using PostgreSQL uniqueness constraints under concurrent requests
- Redis handles shared caching and distributed rate limiting
- Designed for fault tolerance with Redis treated as optional; system falls back to PostgreSQL on cache failures
- Rate limiting uses the first
X-Forwarded-Foraddress when requests pass through a reverse proxy or load balancer
- Stateless services enable horizontal scaling without sticky sessions
- Designed to handle high read throughput workloads by offloading repeated requests to Redis cache
- Shared Redis keeps caching and rate limiting behavior consistent across replicas
- PostgreSQL ensures strong consistency and durability for URL creation under concurrent requests
- Tradeoff: prioritized read performance via caching while accepting eventual consistency for cached redirect data
- Create short URLs via API (
POST /shorten) or through the frontend UI - Redirect short URLs with
GET /{short_code} - Track click counts with
GET /stats/{short_code} - Cache redirect lookups using Redis
- Enforce request rate limits using Redis-backed distributed rate limiting
- Run the full stack locally using Docker Compose
- Validate backend behavior with pytest, frontend behavior with Vitest, and backend CI checks with GitHub Actions
- Support horizontal scaling via stateless application instances behind a load balancer
- Benchmark cache performance (miss vs. hit latency)
To validate the effectiveness of Redis caching, redirect latency was measured for cache misses (first request) and cache hits (subsequent requests).
Run locally:
python3 scripts/benchmark_cache.pyOptional environment overrides:
BENCHMARK_BASE_URL=http://127.0.0.1:8000 \
BENCHMARK_ORIGINAL_URL=https://www.google.com \
BENCHMARK_HIT_RUNS=20 \
python3 scripts/benchmark_cache.pyExample results:
- Cache miss latency: ~10–32 ms
- Average cache hit latency: ~8–11 ms
- Reduced repeated database queries for hot URLs
While latency improvement is modest in local testing, Redis caching reduces repeated database queries and improves scalability for read-heavy workloads.
Frontend: https://url-shortener-frontend-av1x.onrender.com
Backend API:
curl https://url-shortener-gfp0.onrender.com/health
curl -X POST "https://url-shortener-gfp0.onrender.com/shorten" \
-H "Content-Type: application/json" \
-d '{"original_url":"https://www.google.com"}'- Designed RESTful APIs using FastAPI for URL creation, redirection, and analytics
- Implemented PostgreSQL-backed persistence as the durable source of truth for URL mappings and analytics
- Integrated Redis for shared caching and distributed rate limiting with graceful fallback when unavailable
- Containerized and orchestrated multiple application instances using Docker Compose to simulate a distributed environment
- Built automated test coverage with pytest for the backend and Vitest for the frontend
- Configured GitHub Actions CI to run backend tests on every push and pull request
- Introduced Nginx as a load balancer to distribute traffic across multiple FastAPI instances
- Demonstrated a split hosted architecture with Render for app hosting, Neon for PostgreSQL, and Upstash for Redis
- Validated Redis caching effectiveness using benchmark measurements (cache miss vs. hit latency)
- Python
- FastAPI
- PostgreSQL
- Redis
- SQLAlchemy
- Alembic
- React
- Docker / Docker Compose
- Nginx
- pytest
- Vitest
- React Testing Library
- GitHub Actions
app/ # FastAPI backend application
__init__.py
main.py # application entrypoint
core/ # application configuration and environment setup
__init__.py
config.py
services/ # external services and infrastructure logic
__init__.py
cache.py
rate_limiter.py
crud.py # database operations
database.py # database connection and setup
models.py # SQLAlchemy models
schemas.py # Pydantic schemas
utils.py # helper utilities
alembic/ # Alembic migration environment
versions/ # migration revision files
alembic.ini # Alembic configuration
frontend/ # React frontend (Vite, API integration)
src/
App.jsx # main frontend application
App.test.jsx # frontend UI tests
test/ # frontend test setup
nginx/ # Nginx configuration for load balancing
tests/ # automated tests
scripts/
seed.py # sample data loader
benchmark_cache.py # measures cache miss vs. hit latency
docker-compose.yml # service orchestration
Dockerfile # app container definition
requirements.txt # backend dependencies
ENV— runtime environment (development / production)DATABASE_URL— PostgreSQL connection string Neon-hosted deployments should keepsslmode=require; the deployed app typically uses the pooled Neon URL.REDIS_URL— Redis connection string (optional) Upstash-hosted deployments typically use arediss://...URL.BASE_URL— base URL for generated short linksCORS_ALLOW_ORIGINS— comma-separated frontend origins allowed to call the APIAUTO_CREATE_SCHEMA— enables automatic table creation on startup; defaults to enabled in development/test and disabled in production-style environmentsPORT— application port
The service exposes a health check endpoint:
GET /healthcurl http://127.0.0.1:8000/healthdocker compose up --buildOpen:
- Backend API docs: http://127.0.0.1:8000/docs
- Backend health: http://127.0.0.1:8000/health
The Docker Compose backend stack runs python -m alembic upgrade head before starting the FastAPI replicas, so a fresh database is migrated automatically.
- Start required services:
docker compose up -d db redis- Activate virtual environment:
source venv/bin/activate- Install dependencies:
python3 -m pip install -r requirements.txt- Configure local environment:
export BASE_URL=http://127.0.0.1:8000For local runs, make sure DATABASE_URL, REDIS_URL, BASE_URL, and AUTO_CREATE_SCHEMA are set through your .env file or exported in the shell.
Example local backend environment:
export DATABASE_URL=postgresql://postgres:postgres@localhost:5432/urlshortener
export REDIS_URL=redis://localhost:6379/0
export BASE_URL=http://127.0.0.1:8000
export AUTO_CREATE_SCHEMA=falseExample hosted provider environment:
export DATABASE_URL=postgresql://<user>:<password>@<neon-host>/<database>?sslmode=require
export REDIS_URL=rediss://default:<password>@<upstash-host>:6379
export BASE_URL=https://your-backend-domain.onrender.com
export AUTO_CREATE_SCHEMA=false- Apply migrations:
python3 -m alembic upgrade head- Run backend:
python3 -m uvicorn app.main:app --reloadBASE_URL controls the short links returned by the API, so set it to the backend address you want clients to use.
cd frontend
npm install
export VITE_API_BASE_URL=http://127.0.0.1:8000
npm run devOpen: Frontend: http://localhost:5173
VITE_API_BASE_URL tells the frontend which backend API to call in local development or deployment environments.
- Backend tests:
python3 -m pytest -v- Frontend tests:
cd frontend
npm run test:runThe frontend uses Vitest and React Testing Library to cover core UI flows such as shorten success, stats fetch success, and backend error display.
GitHub Actions currently runs backend tests on every push and pull request. Frontend tests are available locally with npm run test:run.
This project uses Alembic for schema migrations.
For the hosted Neon setup:
-
Use the pooled Neon URL for the deployed application runtime
-
Use the direct Neon URL for Alembic,
psql, and other admin tasks when needed -
Apply the latest migrations:
python3 -m alembic upgrade head- Create a new migration after changing models:
python3 -m alembic revision --autogenerate -m "describe change"- Check the current revision:
python3 -m alembic current- For normal development and deployment, prefer migrations with:
AUTO_CREATE_SCHEMA=falseAUTO_CREATE_SCHEMA=true is still available for quick demo or prototyping workflows, but AUTO_CREATE_SCHEMA=false should be the default once the schema is managed by Alembic.
python3 -m alembic upgrade head
python3 -m scripts.seedRun the migration step first when seeding a fresh database.
- Start the backend stack:
docker compose up --build- In another terminal, start the frontend:
cd frontend
export VITE_API_BASE_URL=http://127.0.0.1:8000
npm run dev- Open the frontend UI: http://localhost:5173
- Enter a URL (e.g., https://www.google.com) and generate a short link
- Open the returned short URL in your browser to verify redirection
- Check click statistics using the UI or via:
GET /stats/{short_code}
- Open API docs: http://127.0.0.1:8000/docs
- Create a short URL using
POST /shorten
{
"original_url": "https://www.google.com"
}This project demonstrates a horizontally scalable, distributed system with a React frontend and a FastAPI backend running locally using Docker Compose.
The architecture is designed for scalability, fault tolerance, and consistent behavior across multiple application instances.
The local distributed simulation uses Docker Compose, while the hosted deployment keeps the same application design with Render for app hosting, Neon for PostgreSQL, and Upstash for Redis.
- Nginx load balancer
- 3 FastAPI application replicas
- PostgreSQL as the durable, shared source of truth
- Redis for shared caching and distributed rate limiting
- User interacts with the React frontend or sends a request directly to Nginx
- Nginx routes the request to one FastAPI replica
- The selected instance processes the request and interacts with shared PostgreSQL and Redis
- Redirect requests use Redis as a cache-first lookup layer
- Rate limiting is enforced globally across replicas through Redis
React Frontend / Browser
↓
Nginx Load Balancer
↓
+-----------------------------+
| FastAPI Application Layer |
| - app1 |
| - app2 |
| - app3 |
+-----------------------------+
↓
+--------------------------------+
| Shared Infrastructure |
| - Redis |
| (cache + rate limit) |
| - PostgreSQL |
| (source of truth) |
+--------------------------------+
- Introduced Nginx as a load balancer to distribute traffic across multiple FastAPI instances
- Ensured a stateless application design so any instance can handle any request
- Used Redis as a shared cache and coordination layer for rate limiting across replicas
- Used PostgreSQL as the single source of truth for URL mappings and analytics
- Enforced uniqueness of short codes at the database level to ensure correctness under concurrent distributed writes
- Confirmed load balancing by observing requests handled across multiple instances
- Verified shared Redis cache behavior across replicas
- Validated global rate limiting enforcement across instances
- Ensured consistent state via shared PostgreSQL storage
- Request enters the FastAPI service
- Redis-backed rate limiter validates request frequency
- A short code is generated and checked for uniqueness
- Mapping is stored in PostgreSQL
- FastAPI checks Redis cache for the short code
- On cache hit → return redirect immediately (low latency)
- On cache miss:
- Query PostgreSQL
- Store result in Redis for future requests
- Increment click count
- Return redirect response
- Retrieve URL metadata and click count from PostgreSQL
- Click counts are updated even on cache hits to maintain consistency between cache and persistent storage
- Short codes are generated randomly and validated with a database uniqueness constraint to avoid collisions
- Redis is treated as an optional dependency, with graceful fallback to database queries to maintain system availability
- Introduce background workers for asynchronous click tracking
- Enhance rate limiting with sliding window or token bucket algorithms
- Implement custom aliases and expiration policies
- Build analytics aggregation pipeline for high-volume traffic
- Deploy multi-instance setup to the cloud using container orchestration (e.g., Kubernetes)