A FastAPI-based URL shortener with a web UI and DuckDB persistence.
.env configuration, rate limiting (optional Redis backend), pytest unit tests..
├── app # Main application code
│ ├── core # Core components (config, rate limiter)
│ ├── db # Database session management
│ ├── models # Data models (if any, currently uses Pydantic schemas)
│ ├── schemas # Pydantic schemas for data validation
│ ├── services # Business logic
│ ├── static # CSS files
│ ├── templates # HTML templates
│ └── routers # API endpoint definitions
├── tests # Unit tests
├── .env.example # Example environment variables
├── architecture.md # System architecture diagram
├── main.py # Application entrypoint
├── README.md # This file
├── requirements.txt # Dependencies
└── shorten.py # Optional CLI tool
pipgit clone https://github.com/utkuyucel/url-shortener.git && cd url-shortenerpython -m venv venv && source venv/bin/activate (or venv\Scripts\activate on Windows)pip install -r requirements.txtcp .env.example .env and edit .env.
Key settings:
DATABASE_URL=duckdb:///./data.db
HOST=127.0.0.1
PORT=8000
RATE_LIMIT_ENABLED=true
# RATE_LIMIT_STORAGE_URI=redis://localhost:6379/0 # For Redispython main.pyuvicorn main:app --reloadpytest
python shorten.py (requires the FastAPI service to be running).
GET /: Web UI.GET /{short_key}: Redirects and increments visit count.POST /api/v1/url/shorten: Creates a short URL.{ "original_url": "string" }GET /api/v1/urls: Lists last 50 shortened URLs.DELETE /api/v1/urls: Deletes all URLs.GET /api/v1/urls/{short_path}/info: Fetches metadata for a short URL.app/services/.app/db/session.py.shorten.py]:::client
Web[Web Client / Browser]:::client
end
subgraph B[Application/Service Layer]
direction TB
Main[FastAPI Appmain.py]:::api
Routers[API Routersrouters/url.py]:::api
Service[Service Layerservices/url_service.py]:::service
Config[Config Loadercore/config.py]:::config
RateLimiter[Rate Limitercore/rate_limiter.py]:::service
end
subgraph C[Data Layer]
direction TB
DAL[Data Access Layerdb/session.py]:::db
DB[(DuckDBdata.db)]:::db
URLs["Table: urlsid, original_url, short_path, created_at, visit_count"]:::table
end
subgraph D[Optional External Services]
direction TB
Redis[(RedisGET /): FastAPI serves index.html.POST /api/v1/url/shorten): JS sends URL, service layer stores in DuckDB, API returns short URL.GET /{short_key}): Service layer gets original URL, increments visit_count, redirects.GET /api/v1/urls): Service layer fetches from DuckDB, API returns list.DELETE /api/v1/urls): Service layer deletes all from DuckDB.urls table)| Column | Type | Constraints | Description |
|---|---|---|---|
id |
INTEGER |
PRIMARY KEY AUTOINCREMENT |
Unique ID |
original_url |
TEXT |
NOT NULL |
Original URL |
short_path |
TEXT |
NOT NULL UNIQUE |
Short identifier |
created_at |
TIMESTAMP |
DEFAULT CURRENT_TIMESTAMP |
Creation timestamp |
visit_count |
INTEGER |
NOT NULL DEFAULT 0 |
Visit count |
IP-based, configurable limits, optional Redis backend. See Rate Limiting Documentation.
GPL-3.0 license.