Pygmy is an open-source, extensible & easy-to-use URL shortener. It's built with a loosely coupled architecture that makes it easy to self-host and customize.
The project has three major parts:
| Component | Framework | Description |
|---|---|---|
Core API (pygmy/) |
Flask | URL shortening engine and REST API |
Web UI (pygmyui/) |
Django | User-facing web interface |
| Database | SQLAlchemy | Supports SQLite, PostgreSQL, and MySQL |
The UI communicates with the API over HTTP — they run as separate processes.
- Shorten URLs with auto-generated or custom short codes (e.g.
pygy.co/pygmy) - Auto-expiring URLs
- Secret key protected URLs
- User accounts with dashboard to manage links
- Link analytics: click counts, referrer tracking, country stats (via GeoIP2)
- Append
+to any short URL to view its stats
docker pull amit19/pygmy
docker run -it -p 8000:8000 amit19/pygmyOpen http://localhost:8000 in your browser.
For a production-like setup with PostgreSQL:
docker-compose upThis starts three services: PostgreSQL, the Flask API, and the Django UI. DB credentials are configured in docker-compose.yml.
git clone https://github.com/amitt001/pygmy.git && cd pygmy
# (Recommended) Create a virtualenv
python3 -m venv env
source env/bin/activate
# Install dependencies
pip install -r requirements.txt
# Start both API and UI servers
python run.py- UI available at http://127.0.0.1:8000
- API available at http://127.0.0.1:9119
- Logs at
pygmy/data/pygmy.log
Note: Python 3 is required. SQLite is the default database — no extra setup needed.
There are two config files:
| File | Purpose |
|---|---|
pygmy/config/pygmy.cfg |
API and core settings (DB, auth, logging) |
pygmyui/pygmyui/settings.py |
Django UI settings |
The pygmy.cfg file is git-ignored. Copy pygmy/config/pygmy_test.cfg as a starting template. Database credentials can also be set via environment variables: DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME.
SQLite (default — no setup required):
[database]
engine: sqlite3
sqlite_data_dir: data
sqlite_db_file_name: pygmy.dbPostgreSQL:
[database]
engine: postgresql
user: root
password: root
host: 127.0.0.1
port: 5432
db_name: pygmyMySQL (requires pip install pymysql):
[database]
engine: mysql
user: root
password: root
host: 127.0.0.1
port: 3306
db_name: pygmyFor MySQL, create the database first: CREATE DATABASE pygmy;. MySQL version > 5.6.5 is recommended.
The REST API runs on port 9119 by default.
Create a user:
curl -XPOST http://127.0.0.1:9119/api/user/1 \
-H 'Content-Type: application/json' \
-d '{"email": "user@example.com", "f_name": "Jane", "l_name": "Doe", "password": "secure_password"}'Shorten a URL:
curl -XPOST http://127.0.0.1:9119/api/shorten \
-H 'Content-Type: application/json' \
-d '{"long_url": "https://example.com"}'Expand a short URL:
curl http://127.0.0.1:9119/api/unshorten?short_url=http://127.0.0.1:9119/AbCdE| Endpoint | Method | Description |
|---|---|---|
/api/shorten |
POST | Create a shortened URL |
/api/unshorten |
GET | Expand a short URL |
/api/user |
POST | Create a new user |
/api/login |
POST | Login (returns JWT tokens) |
/api/user/<id>/links |
GET | List user's links |
/<code> |
GET | Redirect to the long URL |
/<code>+ |
GET | View link statistics |
Pygmy uses JWT for API authentication. On login, two tokens are returned:
- Access token — short-lived, used for API requests
- Refresh token — long-lived, used to obtain new access tokens via
/token/refresh
User passwords are hashed with bcrypt.
pip install pytest
py.testIntegration tests automatically start API (port 9118) and UI (port 8001) test servers.
With coverage:
pip install coverage
coverage run --omit="*/templates*,*/venv*,*/tests*" -m py.test
coverage report- Clone the repo and create a branch for your changes
- Make your changes and add tests if applicable
- Run
py.testto verify everything passes - Submit a pull request
If you find a bug or have a feature request, please open an issue.
- Languages: Python 3, JavaScript, HTML, CSS
- API: Flask, Flask-JWT-Extended, Flask-CORS
- UI: Django, jQuery
- ORM: SQLAlchemy
- Validation: Marshmallow
- Server: Gunicorn
- Analytics: GeoIP2 / MaxMindDB
- Containerization: Docker, Docker Compose
The demo version of this website is made possible due to the generous sponsorship of DigitalOcean
MIT License — Copyright (c) 2022 Amit Tripathi
