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
pip
git clone https://github.com/utkuyucel/url-shortener.git && cd url-shortener
python -m venv venv && source venv/bin/activate
(or venv\Scripts\activate
on Windows)pip install -r requirements.txt
cp .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 Redis
python main.py
uvicorn main:app --reload
pytest
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.