Chapter 10 of 10
Build a URL shortener API β a service that takes a long URL, returns a short code, and redirects users who visit the short URL. Clean, practical, and demonstrates the full Flask stack.
import string
import random
from flask import Flask, request, jsonify, redirect, abort
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///shortener.db"
db = SQLAlchemy(app)
class Link(db.Model):
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(10), unique=True, nullable=False)
long_url = db.Column(db.Text, nullable=False)
clicks = db.Column(db.Integer, default=0)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
"code": self.code,
"short_url": f"http://localhost:5000/{self.code}",
"long_url": self.long_url,
"clicks": self.clicks,
"created_at": self.created_at.isoformat(),
}
def generate_code(length=6):
"""Generate a random alphanumeric code."""
chars = string.ascii_letters + string.digits
while True:
code = "".join(random.choices(chars, k=length))
if not Link.query.filter_by(code=code).first():
return code
# POST /shorten β create a short URL
@app.route("/shorten", methods=["POST"])
def shorten():
data = request.get_json()
if not data or "url" not in data:
return jsonify({"error": "url is required"}), 400
long_url = data["url"]
if not long_url.startswith(("http://", "https://")):
return jsonify({"error": "url must start with http:// or https://"}), 400
# Use custom code or generate one
code = data.get("code") or generate_code()
if Link.query.filter_by(code=code).first():
return jsonify({"error": "Code already in use"}), 409
link = Link(code=code, long_url=long_url)
db.session.add(link)
db.session.commit()
return jsonify(link.to_dict()), 201
# GET /<code> β redirect to the long URL
@app.route("/<code>")
def redirect_link(code):
link = Link.query.filter_by(code=code).first_or_404()
link.clicks += 1
db.session.commit()
return redirect(link.long_url)
# GET /stats/<code> β get stats for a link
@app.route("/stats/<code>")
def link_stats(code):
link = Link.query.filter_by(code=code).first_or_404()
return jsonify(link.to_dict())
if __name__ == "__main__":
with app.app_context():
db.create_all()
app.run(debug=True)