1630 words
8 minutes
How to Build a Custom URL Shortener with FastAPI and SQLite

Building a Custom URL Shortener with FastAPI and SQLite

Developing a custom URL shortener provides flexibility and control over links, particularly valuable for branding, internal systems, or specific tracking needs. A URL shortener service takes a long Uniform Resource Locator (URL) and converts it into a significantly shorter, unique identifier, often using a custom domain. When the short URL is accessed, the service redirects the user to the original long URL.

FastAPI, a modern, fast (high-performance) web framework for building APIs with Python 3.7+, and SQLite, a lightweight, serverless, self-contained SQL database engine, are well-suited for building such a service, especially for small to medium-scale applications or as a learning project. FastAPI’s speed is comparable to NodeJS and Go, while SQLite offers simplicity and ease of setup without requiring a separate database server.

Essential Concepts for a Custom URL Shortener

The core function of a URL shortener involves mapping a long URL to a short code and redirecting requests for the short code back to the original long URL.

  • URL Mapping: The fundamental operation is creating a unique association between a lengthy URL and a brief, alphanumeric string (the “short code”). This mapping must be stored persistently.
  • Short Code Generation: A method is needed to generate unique short codes. This can involve various strategies, such as incremental counters, random strings, or hashing techniques. The generated code must be unique for each long URL stored (or unique globally if allowing multiple short codes for the same long URL, though one-to-one is simpler initially).
  • Database Storage: A database is required to store the mapping between the generated short code and the original long URL. SQLite is chosen for its simplicity and file-based nature, making setup straightforward.
  • Web Framework: A web framework like FastAPI is used to handle incoming requests for both creating new short URLs and redirecting existing ones. FastAPI provides automatic documentation (Swagger UI/OpenAPI) and strong validation using Python type hints.
  • Redirection: Upon receiving a request for a short URL, the service looks up the associated long URL in the database and issues an HTTP 301 (Permanent Redirect) or 307/308 (Temporary Redirect) response to the client’s browser, instructing it to navigate to the original URL.

Building the URL Shortener: A Step-by-Step Guide

Constructing the custom URL shortener involves setting up the project, defining the data model, creating the API endpoints, and handling database interactions.

1. Project Setup and Dependencies

First, set up a Python environment and install the necessary libraries.

Terminal window
# Create a project directory
mkdir fastapi_url_shortener
cd fastapi_url_shortener
# Set up a virtual environment (recommended)
python -m venv venv
source venv/bin/activate # On Windows: `venv\Scripts\activate`
# Install dependencies
pip install fastapi uvicorn sqlalchemy shortuuid
  • fastapi: The web framework.
  • uvicorn: An ASGI server to run the FastAPI application.
  • sqlalchemy: An Object-Relational Mapper (ORM) to interact with the SQLite database.
  • shortuuid: A library for generating unique, short, and unambiguous IDs.

2. Database Schema Definition with SQLAlchemy

Define the structure for storing the URL mappings. A simple table with two columns suffices: one for the short code and one for the original long URL.

Create a file database.py:

from sqlalchemy import create_engine, Column, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# Define the database URL. SQLite uses a file.
# 'sqlite:///././shortener.db' refers to a file named shortener.db
# in the current directory where the script is run.
DATABASE_URL = "sqlite:///./shortener.db"
# create_engine creates an engine that connects to the database.
# connect_args={"check_same_thread": False} is needed for SQLite
# because SQLite is not designed for concurrent writes from multiple threads.
# FastAPI runs using async which can involve multiple threads/processes,
# so this flag prevents issues, though proper session handling is also key.
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
# declarative_base() returns a base class for declarative models.
Base = declarative_base()
# Define the URL mapping model.
class URLMapping(Base):
__tablename__ = "url_mappings" # Table name in the database
# Short code column - acts as the primary key
short_code = Column(String, primary_key=True, index=True)
# Original long URL column
long_url = Column(String, index=True)
# Create a SessionLocal class to create database sessions.
# autoflush=False: Prevents SQLAlchemy from flushing changes to the DB
# until session.commit() or session.flush() is called.
# autocommit=False: Prevents SQLAlchemy from committing after every operation.
# bind=engine: Binds the session to our database engine.
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Function to create all tables defined by Base (our URLMapping model)
def create_db_tables():
Base.metadata.create_all(bind=engine)
# Dependency to get a database session (for use in FastAPI endpoints)
def get_db():
db = SessionLocal()
try:
yield db # Provide the session to the calling function
finally:
db.close() # Ensure the session is closed after the request is finished

Run the create_db_tables() function once to initialize the database file and table. You can do this in your main FastAPI file before starting the server, or via a separate script.

3. FastAPI Application and Endpoints

Create the main FastAPI application file, e.g., main.py.

import shortuuid
from fastapi import FastAPI, Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi.responses import RedirectResponse
# Import database setup and model
from database import create_db_tables, get_db, URLMapping
# Initialize the database tables (run this once)
create_db_tables()
app = FastAPI()
# --- Endpoint to create a short URL ---
# Expects a POST request with the long URL in the request body.
@app.post("/shorten")
async def create_short_url(request: Request, db: Session = Depends(get_db)):
try:
# Read the request body directly as text
long_url = await request.body()
long_url = long_url.decode("utf-8").strip() # Decode bytes to string
# Basic validation: Ensure long_url is not empty and starts with http(s)
if not long_url:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="No URL provided")
if not long_url.startswith(("http://", "https://")):
# Simple correction for common cases, a more robust solution might validate further
long_url = "http://" + long_url
# Generate a unique short code
# Retry generating code if it already exists (highly unlikely with shortuuid but good practice)
max_retries = 5
for _ in range(max_retries):
# Generate a short, unique, URL-friendly ID
short_code = shortuuid.uuid()[:8] # Using first 8 chars for brevity
# Check if this code already exists in the database
existing_mapping = db.query(URLMapping).filter(URLMapping.short_code == short_code).first()
if not existing_mapping:
# Code is unique, create the mapping
new_mapping = URLMapping(short_code=short_code, long_url=long_url)
db.add(new_mapping)
try:
db.commit()
db.refresh(new_mapping) # Refresh to get any database-generated defaults/values if needed
# Construct the full short URL (assuming running on localhost:8000 for example)
# In a real deployment, this would use the deployed domain
short_url = f"http://localhost:8000/{short_code}"
return {"short_url": short_url, "long_url": long_url}
except IntegrityError:
# This catches the unlikely case where another process created the same short_code
db.rollback() # Roll back the transaction
continue # Try generating another code
except Exception as e:
db.rollback()
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Database error: {e}")
# If unable to generate a unique code after retries
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Could not generate a unique short code")
except HTTPException as http_exc:
raise http_exc # Re-raise intended HTTP exceptions
except Exception as e:
# Catch any other unexpected errors
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"An unexpected error occurred: {e}")
# --- Endpoint to redirect based on short code ---
# Uses a path parameter to capture the short code.
@app.get("/{short_code}")
def redirect_to_long_url(short_code: str, db: Session = Depends(get_db)):
# Look up the short code in the database
url_mapping = db.query(URLMapping).filter(URLMapping.short_code == short_code).first()
# If mapping exists, redirect
if url_mapping:
return RedirectResponse(url=url_mapping.long_url)
else:
# If mapping not found, return 404 Not Found
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Short URL not found")
# Optional: Root endpoint for basic info or documentation link
@app.get("/")
def read_root():
return {"message": "FastAPI URL Shortener. Visit /docs for API documentation."}

4. Running the Application

With the files created, run the application using Uvicorn.

Terminal window
uvicorn main:app --reload

This starts the server, typically at http://127.0.0.1:8000. The --reload flag is useful during development to automatically restart the server on code changes.

Access http://127.0.0.1:8000/docs in a web browser to see the automatically generated API documentation (Swagger UI), which can be used to test the /shorten endpoint.

Real-World Application Examples

A custom URL shortener built with FastAPI and SQLite, while lightweight, serves various practical purposes:

  • Internal Link Management: Companies can use it to create memorable, short links for internal resources like project dashboards, documentation wikis, or shared drive locations, replacing long, unwieldy internal URLs.
  • Branded Links: Organizations can use a custom domain (e.g., mybrand.link/xyz) instead of generic shortener domains. This enhances brand visibility and trust. While this requires domain configuration and deployment beyond the scope of this basic setup, the FastAPI application is the core service.
  • Simplified Sharing: For specific applications where users need to share links generated within the application, a custom shortener provides a seamless way to create and manage these links directly.

Example Usage:

  1. Shortening a URL:
    • Make a POST request to http://127.0.0.1:8000/shorten.
    • Include the long URL in the request body (as plain text).
    • Example using curl:
      Terminal window
      curl -X POST http://127.0.0.1:8000/shorten -d "https://www.example.com/very/long/page/path/for/article"
    • Expected response (example):
      {
      "short_url": "http://localhost:8000/AbCdEfG1",
      "long_url": "https://www.example.com/very/long/page/path/for/article"
      }
  2. Accessing the Short URL:
    • Open a web browser or use curl to access the generated short URL:
    • http://127.0.0.1:8000/AbCdEfG1
    • The server looks up AbCdEfG1, finds the corresponding long URL, and issues a redirect, causing the browser to navigate to https://www.example.com/very/long/page/path/for/article.

This example demonstrates the core functionality. For production use, enhancements like input validation, error handling, rate limiting, and deployment to a server with a custom domain would be necessary. SQLite performs well for applications with moderate read traffic and low concurrent write traffic, suitable for many internal tools or small public services.

Key Takeaways

  • Building a custom URL shortener provides control over link appearance and management.
  • FastAPI offers a high-performance, developer-friendly framework for building the API.
  • SQLite serves as a simple, file-based database solution ideal for smaller projects or rapid prototyping.
  • The core mechanism involves generating a unique short code, storing the mapping between the short code and the long URL in a database (SQLite via SQLAlchemy), and redirecting requests for the short code.
  • SQLAlchemy provides an Object-Relational Mapper (ORM) layer, simplifying database interactions in Python.
  • The project structure includes defining database models, creating FastAPI endpoints for shortening and redirection, and setting up database sessions.
  • Running the application with Uvicorn makes the API accessible for use and testing.
  • Custom shorteners are useful for internal tools, branding, and controlled link distribution.
How to Build a Custom URL Shortener with FastAPI and SQLite
https://dev-resources.site/posts/how-to-build-a-custom-url-shortener-with-fastapi-and-sqlite/
Author
Dev-Resources
Published at
2025-06-30
License
CC BY-NC-SA 4.0