Python URL Shortener Project

Python project 13

Python URL Shortener Project

Build a local URL-shortening web application with Flask and SQLite. Users can submit a complete web address, receive a short code, and follow that code through a database-backed redirect.

Learn Python with mentor-guided projectsView all project ideas

What you will build

The app runs on your computer, creates its database automatically, validates HTTP and HTTPS URLs, generates hard-to-guess short codes, stores parameterized SQL values, and returns a normal 302 redirect.

Skills practised

  • Flask routes and forms
  • SQLite persistence
  • URL parsing and validation
  • Secure random tokens

Requirements

  • Python 3.10 or later
  • Flask: pip install flask
  • A terminal and browser
  • About 45 minutes to build

Full Python code

Save this code as url_shortener.py, then follow the run instructions below.

"""A local Flask and SQLite URL shortener for learning purposes."""

from __future__ import annotations

import secrets
import sqlite3
import string
from contextlib import closing
from pathlib import Path
from urllib.parse import urlsplit

from flask import Flask, abort, redirect, render_template_string, request, url_for


DATABASE = Path(__file__).with_name("short_urls.db")
ALPHABET = string.ascii_letters + string.digits

app = Flask(__name__)

PAGE = """
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
<title>Local URL Shortener</title></head>
<body>
  <main>
    <h1>Local URL Shortener</h1>
    <form method="post">
      <label>Long URL <input name="long_url" type="url" required placeholder="https://example.com/page"></label>
      <button type="submit">Shorten</button>
    </form>
    {% if error %}<p role="alert">{{ error }}</p>{% endif %}
    {% if short_url %}<p>Short URL: <a href="{{ short_url }}">{{ short_url }}</a></p>{% endif %}
  </main>
</body>
</html>
"""


def connect() -> sqlite3.Connection:
    connection = sqlite3.connect(DATABASE)
    connection.row_factory = sqlite3.Row
    return connection


def setup_database() -> None:
    with closing(connect()) as connection:
        connection.execute(
            "CREATE TABLE IF NOT EXISTS links "
            "(code TEXT PRIMARY KEY, long_url TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP)"
        )
        connection.commit()


def valid_public_url(value: str) -> bool:
    """Apply basic syntax checks; this is not a reputation or safety check."""
    try:
        parsed = urlsplit(value)
        return parsed.scheme in {"http", "https"} and bool(parsed.netloc) and not parsed.username
    except ValueError:
        return False


def new_code(length: int = 7) -> str:
    return "".join(secrets.choice(ALPHABET) for _ in range(length))


def store_url(long_url: str) -> str:
    with closing(connect()) as connection:
        for _ in range(10):
            code = new_code()
            try:
                connection.execute("INSERT INTO links (code, long_url) VALUES (?, ?)", (code, long_url))
                connection.commit()
                return code
            except sqlite3.IntegrityError:
                continue
    raise RuntimeError("Could not create a unique short code. Try again.")


@app.route("/", methods=["GET", "POST"])
def home():
    error = None
    short_url = None
    if request.method == "POST":
        long_url = request.form.get("long_url", "").strip()
        if not valid_public_url(long_url):
            error = "Enter a complete http:// or https:// URL without embedded credentials."
        else:
            short_url = url_for("follow", code=store_url(long_url), _external=True)
    return render_template_string(PAGE, error=error, short_url=short_url)


@app.get("/<code>")
def follow(code: str):
    with closing(connect()) as connection:
        row = connection.execute("SELECT long_url FROM links WHERE code = ?", (code,)).fetchone()
    if row is None:
        abort(404)
    return redirect(row["long_url"], code=302)


def main() -> None:
    setup_database()
    app.run(host="127.0.0.1", port=5000, debug=False)


if __name__ == "__main__":
    main()

How the project works

store_url() tries a randomly generated seven-character code and relies on the database primary key to reject the extremely unlikely duplicate. SQL parameters keep submitted values separate from the query text.

The dynamic route looks up the code and returns an HTTP 302 redirect. This learning app intentionally runs only on 127.0.0.1. A public shortener would also need abuse prevention, unsafe-domain controls, rate limiting, moderation, analytics privacy, HTTPS, and production hosting.

Run the project

  1. Install Flask with python -m pip install flask.
  2. Run python url_shortener.py.
  3. Open http://127.0.0.1:5000/.
  4. Submit a complete URL and test the generated short link.

Accuracy and safety notes

  • Local database: links are stored in short_urls.db.
  • Validation: syntax checks do not prove that a destination is safe.
  • Development server: do not expose Flask’s built-in server directly to the internet.

Ways to extend it

Add click counts, expiry dates, custom aliases, duplicate-URL reuse, an admin view, and production-grade abuse controls.

Continue learning Python

Build the next project, return to the Softenant project library, or explore the Python programming course in Vizag for structured lessons, mentor feedback, and portfolio practice.