Skip to content

simply-mihir/SnapIT

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation


SnapIT : URL Shortener

A production-ready, low-latency URL shortener with analytics and caching.

SnapIt screenshot

Live Demo FastAPI Next.js PostgreSQL Redis

SnapIT is a highly scalable, full-stack URL shortener built with FastAPI, PostgreSQL, Redis, Next.js, and Tailwind CSS. It features Redis cache-aside low-latency redirects, race-safe custom aliases, per-IP rate limiting, non-blocking click analytics, and graceful HTTP 410 expiration handling.


Architecture & Redirect Flow

                  ┌─────────┐      Miss       ┌────────────┐
 ┌────────┐  GET  │         ├────────────────►│            │
 │ Client ├──────►│  Redis  │                 │ PostgreSQL │
 └────────┘       │  Cache  │◄────────────────┤     DB     │
                  └────┬────┘   Repopulate    └──────┬─────┘
                       │                             │
                   Hit │ (302 Redirect)              │ Async Update
                       ▼                             ▼
                 ┌───────────┐                 ┌─────────────┐
                 │ Redirect  │                 │ Background  │
                 │ completed │                 │ Analytics   │
                 └───────────┘                 └─────────────┘

Table of Contents

  1. Key Features
  2. API Reference
  3. Database Schema
  4. Quick Start (Local Development)
  5. Running Tests
  6. Project Structure
  7. Environment Configuration
  8. Free-Tier Deployment Guide
  9. Production Notes

1. Key Features

  • Low-Latency Redirects: Utilizes Redis cache-aside architecture. The database is exclusively touched on a cache miss.
  • Race-Safe Aliases: Custom aliases are enforced for uniqueness at the database layer to prevent race conditions.
  • Rate Limiting: Fixed-window per-IP rate limiting backed by Redis, returning 429 Too Many Requests with Retry-After headers.
  • Async Analytics: Click-counts and last_accessed timestamps are recorded via fire-and-forget background tasks so redirects never block.
  • Graceful Expiration: Supports TTL on URLs, returning an HTTP 410 Gone status for expired links.
  • Containerized: Fully packaged with Docker Compose for a one-command local development environment.

2. API Reference

Method Path Description
POST /api/shorten Create a short URL
GET /api/analytics/{id} Retrieve click-count and timestamps
GET /{short_id} 302 Redirect (404 missing, 410 expired)
GET /health/live Liveness probe
GET /health/ready Readiness probe (DB + Redis check)
GET /docs OpenAPI Swagger UI
Click to view POST /api/shorten Payload & Response

Request Payload:

{
  "original_url": "https://example.com/very/long/path",
  "custom_alias": "my-link",
  "expires_in_days": 30
}

Response (201 Created):

{
  "short_id": "my-link",
  "short_url": "http://localhost:8000/my-link",
  "original_url": "https://example.com/very/long/path",
  "custom_alias": "my-link",
  "created_at": "2026-04-23T10:15:00Z",
  "expires_at": "2026-05-23T10:15:00Z"
}

Note: Errors handle 400 invalid URL/alias, 409 alias taken, and 429 rate-limited.


3. Database Schema

Table: urls

Column Type Notes
id bigint PK, autoincrement
original_url varchar(2048) Not null
short_id varchar(64) Unique, indexed (hot-path lookup)
custom_alias varchar(64) Unique, indexed, nullable
created_at timestamptz Default now()
expires_at timestamptz Indexed (for cleanup jobs), nullable
click_count integer Default 0, incremented via atomic UPDATE
last_accessed_at timestamptz Nullable

4. Quick Start (Local Development)

The entire stack is containerized for zero-friction setup.

# Clone the repository
git clone https://github.com/YOUR_USERNAME/url-shortener.git
cd url-shortener

# Boot the stack
docker compose up --build

Access Points:

(Note: The backend auto-creates DB tables on the first boot for local dev convenience).


5. Running Tests

The test suite intelligently swaps Postgres for SQLite (aiosqlite) and Redis for fakeredis, allowing tests to run entirely offline.

cd backend

# Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows use: .venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt
pip install fakeredis pytest

# Run tests
pytest -q

6. Project Structure

url-shortener/
├── backend/
│   ├── app/
│   │   ├── main.py                 # FastAPI entrypoint + lifespan
│   │   ├── core/                   # config, utils, exceptions
│   │   ├── db/                     # async SQLAlchemy engine + session
│   │   ├── models/                 # ORM models
│   │   ├── schemas/                # Pydantic request/response models
│   │   ├── services/               # cache, rate limiter, business logic
│   │   └── routes/                 # shorten, redirect, health, deps
│   ├── tests/                      # pytest suite (SQLite + fakeredis)
│   └── Dockerfile
├── frontend/
│   ├── pages/                      # _app.js, index.js
│   ├── components/                 # ShortenerForm, ThemeToggle
│   ├── styles/globals.css
│   └── Dockerfile
└── docker-compose.yml

7. Environment Configuration

Configuration is managed via environment variables. See backend/.env.example and frontend/.env.example.

  • DATABASE_URL: Async SQLAlchemy URL (postgresql+asyncpg://...)
  • REDIS_URL: Redis connection string (rediss://... for TLS)
  • RATE_LIMIT_MAX_REQUESTS / RATE_LIMIT_WINDOW_SECONDS: Per-IP limits
  • CACHE_DEFAULT_TTL: Redis TTL for short URLs in seconds
  • SHORT_ID_LENGTH: Base62 length (Default 7 → 62⁷ ≈ 3.5T keyspace)
  • CORS_ORIGINS: Comma-separated list of allowed origins

8. Free-Tier Deployment Guide

Deploying this stack for $0 is easy using modern cloud providers:

  1. PostgreSQL (Supabase): Create a project → Settings → Database → copy the "Connection pooling" URL. Change the prefix to postgresql+asyncpg://.
  2. Redis (Upstash): Create a free Redis DB. Copy the TLS URL (rediss://default:<password>@<host>:6379).
  3. Backend (Render): Create a Web Service → Connect repo → Root directory backend/.
    • Env Vars: DATABASE_URL, REDIS_URL, APP_ENV=production, BASE_URL, CORS_ORIGINS.
    • Health check path: /health/ready
  4. Frontend (Netlify / Vercel): Import repo → Root directory frontend/. Add Env Var: NEXT_PUBLIC_API_URL=https://<your-render-backend>.onrender.com.

9. Production Notes

Before scaling to heavy production traffic, consider the following:

  • Migrations: The init_db() call on startup is for local dev. In production, remove this call and manage schema changes strictly via Alembic.
  • Scaling: For High Availability (HA), run Uvicorn with multiple workers behind a reverse proxy. Because state lives in Postgres and Redis, the backend scales horizontally infinitely.
  • Analytics Queuing: Consider moving analytics from in-process background tasks to a robust queue (RQ, Arq, Celery, or AWS SQS) to prevent dropping click records during abrupt pod crashes.
  • Data Cleanup: Implement a nightly Cron job to delete rows where expires_at < now() - interval '7 days' to prevent DB bloat.

10. Observability

Production traffic is instrumented via OpenTelemetry, with traces and metrics exported to Grafana Cloud over OTLP.

SnapIt Grafana dashboard

The service emits:

  • Distributed traces — per-request flame graphs spanning FastAPI → SQLAlchemy → asyncpg → Redis
  • Histogram metrics — request duration P50 / P95 / P99 by method and status code
  • Counter metrics — request volume, error rate, cache operations

11. License:

  • MIT — do what you want.

12. Deployed Link:

About

A production grade URL shortener with custom aliases, expiration controls, and Redis caching. FastAPI + Next.js.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors