Hello there, future web developers! Have you ever seen those super short links on social media or in messages, like bit.ly/xxxx? These are created by URL shorteners. A URL shortener is a web service that takes a long, complicated web address (URL) and turns it into a much shorter, neater one. When someone clicks on the short URL, they are automatically redirected to the original long URL.
Why are they useful?
* They make links easier to share, especially where space is limited (like tweets).
* They can make links look cleaner and more professional.
* Some even track clicks, giving you insights into who is using your links.
In this blog post, we’re going to build our very own simple URL shortener using a fantastic Python web framework called Flask. Don’t worry if you’re new to web development; we’ll break down every step into easy-to-understand pieces.
What is Flask?
Flask is a micro-framework for Python. Think of a web framework as a toolbox filled with everything you need to build a website or a web application. Some frameworks are “full-stack” and come with many tools pre-selected, while “micro-frameworks” like Flask give you the basic necessities and let you choose additional tools as needed. This makes Flask very flexible and great for learning because it doesn’t hide too much away.
We’ll use Flask to:
1. Create a web page where you can input a long URL.
2. Save that long URL and generate a unique short code for it in a database.
3. Create a special short URL that, when visited, will redirect to your original long URL.
Let’s get started!
1. Setting Up Your Development Environment
Before we write any code, we need to prepare our workspace.
1.1 Create a Project Folder
First, create a new folder for your project. You can name it something like flask_url_shortener.
mkdir flask_url_shortener
cd flask_url_shortener
1.2 Create a Virtual Environment
It’s a good practice to use a virtual environment for every Python project. A virtual environment is like a separate, isolated container for your project’s Python packages. This prevents conflicts between different projects that might need different versions of the same package.
python3 -m venv venv
(If python3 doesn’t work, try python or py depending on your system setup.)
This command creates a folder named venv inside your project folder, which contains a new Python installation just for this project.
1.3 Activate the Virtual Environment
Now, you need to “activate” this environment. Once activated, any Python packages you install will go into this venv folder, not your global Python installation.
- On macOS/Linux:
bash
source venv/bin/activate - On Windows (Command Prompt):
bash
venv\Scripts\activate.bat - On Windows (PowerShell):
bash
venv\Scripts\Activate.ps1
You’ll know it’s active when you see (venv) at the beginning of your command prompt.
1.4 Install Flask and Flask-SQLAlchemy
Now that your virtual environment is active, let’s install the necessary packages. We need Flask itself, and Flask-SQLAlchemy to easily work with databases.
SQLAlchemy is a powerful tool (an ORM, or Object-Relational Mapper) that lets you interact with databases using Python objects instead of writing raw SQL queries. Flask-SQLAlchemy is an extension that makes using SQLAlchemy with Flask even easier. For our simple project, it will use a local SQLite database, which is a file-based database that doesn’t require a separate server.
pip install Flask Flask-SQLAlchemy shortuuid
pipis the Python package installer. It helps us download and install libraries.shortuuidis a small library that will help us generate unique, short, human-readable IDs for our URLs.
2. Project Structure
Let’s set up the basic folders and files for our Flask application.
flask_url_shortener/
├── venv/ # Our virtual environment (created automatically)
├── app.py # Main Flask application file
└── templates/
└── index.html # HTML template for our web page
Create the templates folder, and inside it, create index.html.
3. Building the Flask Application (app.py)
This file will contain all the logic for our URL shortener.
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
import shortuuid
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_super_secret_key_here'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class URL(db.Model):
# Primary key for unique identification, automatically incrementing.
id = db.Column(db.Integer, primary_key=True)
# The original long URL, cannot be empty.
original_url = db.Column(db.String(500), nullable=False)
# The short code (e.g., 'abc123'), must be unique and cannot be empty.
short_code = db.Column(db.String(10), unique=True, nullable=False)
# A helpful representation for debugging
def __repr__(self):
return f"URL('{self.original_url}', '{self.short_code}')"
def generate_short_code():
# Loop until a unique short code is generated
while True:
# Generate a unique 8-character string using shortuuid
# This makes codes human-readable and less prone to collisions.
code = shortuuid.uuid()[:8] # Get the first 8 characters
# Check if this code already exists in our database
if not URL.query.filter_by(short_code=code).first():
return code
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
original_url = request.form['original_url']
# Simple validation: Check if the URL is provided
if not original_url:
flash('Please enter a URL to shorten!', 'danger') # 'danger' is a category for styling
return redirect(url_for('index'))
# Check if this URL has already been shortened
existing_url = URL.query.filter_by(original_url=original_url).first()
if existing_url:
flash(f'URL already shortened! Short URL: {request.url_root}{existing_url.short_code}', 'info')
return redirect(url_for('index'))
# Generate a unique short code
short_code = generate_short_code()
# Create a new URL object
new_url = URL(original_url=original_url, short_code=short_code)
# Add to the database session and commit
db.session.add(new_url)
db.session.commit()
flash(f'URL shortened successfully! Short URL: {request.url_root}{new_url.short_code}', 'success')
return redirect(url_for('index'))
# For GET request, display all shortened URLs
all_urls = URL.query.all()
# render_template looks for files in the 'templates' folder
return render_template('index.html', all_urls=all_urls)
@app.route('/<short_code>')
def redirect_to_original(short_code):
# Find the URL entry in the database by its short code
url_entry = URL.query.filter_by(short_code=short_code).first_or_404()
# If found, redirect the user to the original URL (HTTP 302 Found)
return redirect(url_entry.original_url)
with app.app_context():
db.create_all()
if __name__ == '__main__':
# 'debug=True' reloads the server on code changes and shows helpful error messages.
# Set to 'False' in a production environment.
app.run(debug=True)
Technical Explanations for app.py:
* Flask(__name__): Initializes the Flask application. __name__ tells Flask where to look for resources like templates.
* app.config[...]: Used to configure Flask.
* SECRET_KEY: Important for security features like flash messages (which temporarily store data in cookies).
* SQLALCHEMY_DATABASE_URI: Specifies which database to use (sqlite:///site.db means a SQLite database file named site.db in the project root).
* SQLALCHEMY_TRACK_MODIFICATIONS: A setting for Flask-SQLAlchemy; often set to False to save memory unless you specifically need its event tracking.
* db.Model: Our URL class inherits from db.Model, telling SQLAlchemy that this class represents a table in our database.
* db.Column(...): Defines columns in our database table.
* primary_key=True: Marks this column as the unique identifier for each row.
* nullable=False: Means this column cannot be empty.
* unique=True: Means every value in this column must be different from others.
* @app.route('/'): These are called decorators. They map specific URLs (or “routes”) to Python functions.
* '/': The root URL of our application (e.g., http://127.0.0.1:5000/).
* methods=['GET', 'POST']: Specifies that this route can handle both GET (when you just visit the page) and POST (when you submit a form) requests.
* request.form['original_url']: When a form is submitted (a POST request), this accesses the data sent from the form field named original_url.
* flash(...): A Flask function to display one-time messages to the user (e.g., “URL shortened successfully!”). These messages are stored in the session and displayed once.
* redirect(url_for('index')): After processing a form, it’s good practice to redirect the user back to a GET request to prevent issues if they refresh the page. url_for('index') generates the URL for the index function.
* db.session.add(new_url): Adds our new URL object to the database session. Think of the session as a staging area.
* db.session.commit(): Saves (commits) all changes in the session permanently to the database.
* render_template('index.html', all_urls=all_urls): This function renders (processes) an HTML file from the templates folder and passes data (like all_urls) to it.
* first_or_404(): A SQLAlchemy query method that either returns the first matching result or automatically sends an HTTP 404 (Not Found) error if no match is found.
* app.run(debug=True): Starts the Flask web server. debug=True is useful during development as it automatically reloads the server when you make code changes and provides helpful error messages.
4. Creating the HTML Template (templates/index.html)
This file will provide the user interface for our shortener.
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple URL Shortener</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #0056b3;
text-align: center;
}
form {
display: flex;
gap: 10px;
margin-bottom: 30px;
flex-wrap: wrap; /* Allows items to wrap on smaller screens */
}
form input[type="text"] {
flex-grow: 1; /* Takes up available space */
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
min-width: 200px; /* Minimum width before wrapping */
}
form button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
form button:hover {
background-color: #0056b3;
}
.flash {
padding: 10px;
margin-bottom: 20px;
border-radius: 4px;
color: white;
font-weight: bold;
}
.flash.success { background-color: #28a745; }
.flash.danger { background-color: #dc3545; }
.flash.info { background-color: #17a2b8; }
.url-list {
margin-top: 20px;
}
.url-list h2 {
color: #0056b3;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.url-item {
background-color: #f9f9f9;
border: 1px solid #eee;
padding: 15px;
margin-bottom: 10px;
border-radius: 4px;
display: flex;
flex-direction: column;
gap: 5px;
}
.url-item p {
margin: 0;
word-break: break-all; /* Ensures long URLs break correctly */
}
.url-item .label {
font-weight: bold;
color: #555;
margin-right: 5px;
}
.url-item a {
color: #007bff;
text-decoration: none;
}
.url-item a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<h1>TinyLink: Simple URL Shortener</h1>
<!-- Flash messages display here -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-messages">
{% for category, message in messages %}
<div class="flash {{ category }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<form method="POST" action="/">
<input type="text" name="original_url" placeholder="Enter your long URL here..." required>
<button type="submit">Shorten</button>
</form>
<div class="url-list">
<h2>Your Shortened URLs</h2>
{% if all_urls %}
{% for url in all_urls %}
<div class="url-item">
<p><span class="label">Original:</span> <a href="{{ url.original_url }}" target="_blank" rel="noopener noreferrer">{{ url.original_url }}</a></p>
<p><span class="label">Shortened:</span> <a href="{{ url_for('redirect_to_original', short_code=url.short_code, _external=True) }}" target="_blank" rel="noopener noreferrer">
{{ url_for('redirect_to_original', short_code=url.short_code, _external=True) }}
</a></p>
</div>
{% endfor %}
{% else %}
<p>No URLs shortened yet. Go ahead and shorten one!</p>
{% endif %}
</div>
</div>
</body>
</html>
Technical Explanations for index.html:
* Jinja2 Templating: Flask uses Jinja2 as its templating engine. This allows us to embed Python-like logic directly into our HTML.
* {% ... %}: For statements (like if conditions or for loops).
* {{ ... }}: For expressions (to display data).
* {% with messages = get_flashed_messages(with_categories=true) %}: This block checks if there are any flash messages from our Flask application and iterates through them to display them. with_categories=true allows us to get the category (like ‘success’, ‘danger’, ‘info’) to style the messages.
* <form method="POST" action="/">: This HTML form sends data to our Flask application using the POST method, and the action="/" means it sends data to the root URL, which is handled by our index() function in app.py.
* <input type="text" name="original_url" ...>: The name="original_url" attribute is crucial because Flask uses this name to retrieve the input value (request.form['original_url']).
* {% for url in all_urls %}: This loop iterates through the all_urls list (which we passed from app.py) and displays information for each shortened URL.
* {{ url_for('redirect_to_original', short_code=url.short_code, _external=True) }}: This Jinja2 function generates a URL for our redirect_to_original Flask function, passing the short_code as an argument. _external=True makes sure it generates a full URL (e.g., http://127.0.0.1:5000/abc123) instead of just /abc123.
5. Running Your Application
- Ensure your virtual environment is active. (You should see
(venv)in your terminal prompt.) - Navigate to your project directory (where
app.pyis located) in your terminal. -
Run the application:
bash
flask run
Sometimes,python app.pyalso works ifFLASK_APPis not set, butflask runis the recommended way to run Flask applications.You should see output similar to this:
* Serving Flask app 'app.py'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit -
Open your web browser and go to
http://127.0.0.1:5000.
You should see your URL shortener! Try pasting a long URL (like https://www.google.com/search?q=flask+url+shortener+tutorial) into the input field and click “Shorten.” You’ll see the short URL generated, and you can click on it to test the redirection.
Conclusion
Congratulations! You’ve successfully built a basic URL shortener using Flask, Flask-SQLAlchemy, and shortuuid. You’ve learned how to:
* Set up a Flask project with a virtual environment.
* Define a database model using Flask-SQLAlchemy.
* Create Flask routes to handle different web requests (GET and POST).
* Render dynamic HTML templates using Jinja2.
* Generate unique short codes.
* Redirect users from a short URL to an original long URL.
This is just the beginning! Here are some ideas for how you could expand this project:
* Add user authentication so only registered users can shorten URLs.
* Implement custom short codes (e.g., tiny.link/my-awesome-link).
* Add click tracking for each short URL.
* Make the UI more responsive and stylish.
* Deploy your application to a live server so others can use it.
Keep experimenting, keep learning, and happy coding!
Leave a Reply
You must be logged in to post a comment.