Python Tutorial – How to Create a URL Shortener with Flask

Have you ever wished you could take a long, unwieldy URL and shrink it down into something short and shareable? That‘s exactly what a URL shortener does. URL shorteners provide a quick and easy way to create compact URLs that redirect to longer ones.

Building your own URL shortener is a great project idea to level up your web development skills. Not only will you learn how to craft clean, RESTful APIs, but you‘ll also gain experience working with databases, performing input validation, and more.

In this Python tutorial, we‘ll walk through building a full-featured URL shortener web app step-by-step using the Flask framework. By the end, you‘ll have a solid understanding of the core concepts and be able to extend the project further on your own.

Why Flask for a URL Shortener

Flask is a lightweight, beginner-friendly Python web framework perfect for a URL shortener project. Here are a few reasons why:

  1. Simplicity – Flask provides just the right amount of features without being overly complex. You can have a basic app up and running in minutes.

  2. Flexibility – Flask doesn‘t make too many decisions for you. It‘s unopinionated and easy to customize to fit your needs.

  3. Extensibility – Need to add user accounts or an admin dashboard down the line? Flask has a rich ecosystem of extensions to enhance your app.

  4. Python – Building your app in Python means you‘ll have access to powerful libraries and tools for things like data analysis or machine learning to take your project even further.

Best of all, the concepts you‘ll learn in Flask translate seamlessly to other popular frameworks like Django or FastAPI. Flask is a great jumping off point.

Project Setup

Before we dive in, let‘s get our workspace set up. I recommend using a virtual environment to keep your project dependencies isolated. You can create and activate one with:

python -m venv venv
source venv/bin/activate  # Windows: \venv\Scripts\activate

Next, we need to install Flask and a few other dependencies:

pip install flask python-dotenv

We‘ll use python-dotenv to load environment variables for configuration.

Now let‘s create a new file app.py which will be the entry point for our application:

from flask import Flask

app = Flask(__name__)

@app.route(‘/‘)
def index():
    return ‘Welcome to Shorten.ly!‘

if __name__ == ‘__main__‘:
    app.run()

This sets up a basic Flask app and defines a route for the homepage. Let‘s test it out:

python app.py

Visit http://localhost:5000 in your browser and you should see our welcome message. We‘re ready to start building the actual URL shortener functionality!

Defining the Database Models

The core of a URL shortener is a database that stores mappings between the generated short codes and the original long URLs. We‘ll use the SQLAlchemy library to interact with the database.

First install the dependencies:

pip install flask-sqlalchemy 

Then update app.py to configure the database:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config[‘SQLALCHEMY_DATABASE_URI‘] = ‘sqlite:///db.sqlite3‘
app.config[‘SQLALCHEMY_TRACK_MODIFICATIONS‘] = False

db = SQLAlchemy(app)

class ShortUrl(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    original_url = db.Column(db.String(500), nullable=False)
    short_id = db.Column(db.String(20), unique=True, nullable=False)
    created_at = db.Column(db.DateTime, nullable=False) 

    def __repr__(self):
        return f‘<ShortUrl {self.short_id}>‘

with app.app_context():
    db.create_all()

# ... rest of code ...

Here we define a ShortUrl model with fields to store:

  • The original long URL
  • The generated short ID
  • A timestamp of when the short URL was created

We‘ve also configured SQLAlchemy to use a SQLite database file db.sqlite3 and created the corresponding table in the database.

Generating Short IDs

Let‘s implement the logic to generate a unique, random short ID for each URL. There are many approaches, but we‘ll keep it simple and use a combination of:

  1. The Python secrets module for generating secure random strings
  2. Checking against existing IDs to avoid collisions

Create a new file shortener.py with:

import secrets
import string

from app import db, ShortUrl

CHARSET = string.ascii_letters + string.digits
MIN_LENGTH = 6

def generate_short_id(charset=CHARSET):
    short_id = ‘‘.join(secrets.choice(charset) for _ in range(MIN_LENGTH))
    if ShortUrl.query.filter_by(short_id=short_id).first():
        # Recursively retry if a collision occurs
        return generate_short_id(charset)
    return short_id

This will give us a function to generate random, unique 6-character IDs using uppercase, lowercase, and digits by default. Feel free to tweak the character set and length to your needs.

Creating and Displaying Short URLs

Now we can implement the core URL shortening functionality. We need two new routes:

  1. A route to accept a POST request with a URL to shorten and return the short URL
  2. A route to accept a GET request with a short ID and redirect to the original URL

Update app.py with:

from flask import Flask, render_template, request, redirect
# ... other imports ...
from shortener import generate_short_id

# ... app setup ...

@app.route(‘/‘, methods=[‘GET‘, ‘POST‘])
def index():
    if request.method == ‘POST‘:
        url = request.form[‘url‘]
        short_id = request.form[‘custom_id‘]

        if short_id and ShortUrl.query.filter_by(short_id=short_id).first():
            # Custom ID already exists
            return render_template(‘index.html‘, error=f‘Short ID "{short_id}" is already in use.‘)

        if not url:
            # No URL provided
            return render_template(‘index.html‘, error=‘Please enter a URL.‘)

        if not short_id:
            # No custom ID, generate one
            short_id = generate_short_id()

        new_short_url = ShortUrl(original_url=url, short_id=short_id)
        db.session.add(new_short_url)
        db.session.commit()

        short_url = request.host_url + short_id
        return render_template(‘index.html‘, short_url=short_url)

    return render_template(‘index.html‘)

@app.route(‘/<short_id>‘)
def redirect_to_original(short_id):
    url = ShortUrl.query.filter_by(short_id=short_id).first()
    if url:
        return redirect(url.original_url) 
    else:
        return render_template(‘404.html‘), 404

The index route does the following:

  1. Checks if it received a POST request (i.e. the user submitted the form)
  2. Validates that a URL was provided
  3. Checks if a custom short ID was provided and is available
  4. Generates a random short ID if none was provided
  5. Saves the new short URL to the database
  6. Returns the shortened URL to display to the user

The redirect_to_original route simply looks up the short ID in the database and redirects to the original URL if found. Otherwise it displays a 404 "Not Found" page.

We‘ve referenced a few HTML templates above, so let‘s create those next.

Creating the HTML Templates

We‘ll use the Jinja templating system that comes with Flask to render our HTML pages. Create a new folder templates and inside add the following files:

base.html:

<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Shorten.ly{% endblock %}</title>
    <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
</head>
<body>
    <header>

        <p>A simple URL shortener built with Flask</p>
    </header>

    <main>
        {% block content %}{% endblock %}
    </main>

    <footer>
        <p>© 2023 Shorten.ly</p>
    </footer>

</body>
</html>

This is a base template that our other pages will extend. It defines the overall structure of the page and includes blocks for the content and title that child templates can override.

index.html:

{% extends "base.html" %}

{% block content %}
<h2>Shorten a URL</h2>

{% if error %}
<p class="error">{{ error }}</p>
{% endif %}

<form method="post">
    <label for="url">Enter a URL:</label>
    <input type="url" name="url" id="url" required>

    <label for="custom_id">Custom Short ID (optional):</label>
    <input type="text" name="custom_id" id="custom_id">

    <button type="submit">Shorten</button>
</form>

{% if short_url %}
<hr>
<p>Your shortened URL is: <a href="{{ short_url }}" target="_blank">{{ short_url }}</a></p>
{% endif %}

{% endblock %}

This template extends the base and defines the content for the index page. It includes the form to enter a URL and an optional custom short ID. It also displays the generated short URL if one was created.

404.html:

{% extends "base.html" %}

{% block content %}
<h2>404 - Page Not Found</h2>
<p>Sorry, the page you requested could not be found.</p>
<p><a href="{{ url_for(‘index‘) }}">Return home</a></p>
{% endblock %}

This is a simple 404 error page template.

We‘re using a classless CSS framework called Simple.css to add some basic styles. Feel free to customize the look and feel as you see fit.

Deploying to a Live Server

To make your URL shortener accessible on the internet, you‘ll need to deploy it to a live server. One of the easiest ways to do this is with a platform like Heroku.

Heroku allows you to host small applications for free and provides an easy interface for managing your app. It also integrates with GitHub for simple deployments.

Rather than recreate the steps here, I recommend following Heroku‘s official Python deployment guide. It walks through:

  1. Setting up a free Heroku account and installing the Heroku CLI tool
  2. Creating a Procfile and requirements.txt to configure your app for Heroku
  3. Pushing your code to Heroku and configuring your app
  4. Viewing your live app!

Taking it Further

Congratulations, you now have a working URL shortener that you built yourself! There are many ways you could expand the project:

  • Add user accounts and allow custom URLs
  • Track click analytics for each short URL
  • Automatically expire old, rarely used short URLs
  • Build a command line interface to generate short URLs
  • Use a distributed key-value store like Redis in place of SQLite

I encourage you to pick a feature that interests you and try implementing it. The best way to learn is by doing.

If you get stuck, don‘t be afraid to consult the Flask documentation, search for answers on Stack Overflow, or reach out to the helpful Flask community.

Additional Resources

I hope this tutorial has been helpful and you feel empowered to continue learning Flask. Feel free to reach out with any questions. Happy coding!

Similar Posts