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:
-
Simplicity – Flask provides just the right amount of features without being overly complex. You can have a basic app up and running in minutes.
-
Flexibility – Flask doesn‘t make too many decisions for you. It‘s unopinionated and easy to customize to fit your needs.
-
Extensibility – Need to add user accounts or an admin dashboard down the line? Flask has a rich ecosystem of extensions to enhance your app.
-
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:
- The Python
secrets
module for generating secure random strings - 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:
- A route to accept a POST request with a URL to shorten and return the short URL
- 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:
- Checks if it received a POST request (i.e. the user submitted the form)
- Validates that a URL was provided
- Checks if a custom short ID was provided and is available
- Generates a random short ID if none was provided
- Saves the new short URL to the database
- 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:
- Setting up a free Heroku account and installing the Heroku CLI tool
- Creating a Procfile and requirements.txt to configure your app for Heroku
- Pushing your code to Heroku and configuring your app
- 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
- GitHub repo with complete source code
- Official Flask documentation
- Flask Mega-Tutorial by Miguel Grinberg
- Full Stack Python – Flask
- TestDriven.io – Flask Tutorials
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!