How to Build a URL Shortener API

A URL shortener API needs two endpoints: one to create a short code for a URL, and one to redirect a code back to the original. Here's how to build it from scratch with Flask and SQLite.

Backend Engineerpythonflasksqlite

The two endpoints

Storage

One table maps code -> URL:

import sqlite3
def get_db():
    conn = sqlite3.connect("/tmp/urls.db")
    conn.execute("CREATE TABLE IF NOT EXISTS urls (code TEXT PRIMARY KEY, url TEXT NOT NULL)")
    return conn

Generating the code

A short, stable code from the URL via a hash is simple and gives free dedup (same URL -> same code):

import hashlib
def make_code(url: str) -> str:
    return hashlib.sha256(url.encode()).hexdigest()[:7]

(For a guaranteed-unique, non-leaking scheme, base62-encode an auto-increment id instead - see the URL-shortener system-design walkthrough.)

The endpoints

from flask import Flask, request, jsonify, redirect, abort
app = Flask(__name__)

@app.post("/shorten")
def shorten():
    url = (request.get_json() or {}).get("url")
    if not url:
        return jsonify({"error": "url required"}), 400
    code = make_code(url)
    db = get_db()
    db.execute("INSERT OR IGNORE INTO urls (code, url) VALUES (?, ?)", (code, url))
    db.commit()
    return jsonify({"code": code, "short_url": f"http://localhost:8000/s/{code}"}), 201

@app.get("/s/<code>")
def resolve(code):
    row = get_db().execute("SELECT url FROM urls WHERE code = ?", (code,)).fetchone()
    if not row:
        abort(404)
    return redirect(row[0])

The details that make it solid

Collisions, scale, and the redirect

Two design choices decide how this scales. Code generation: a random base62 string is simple but can collide, so check for an existing code and retry on conflict; an incrementing id encoded to base62 never collides and stays short, at the cost of being guessable. Storage: an index on the short code makes the redirect a single fast lookup - which matters because redirects vastly outnumber creates.

Get the redirect right: return 301 only if the mapping never changes, or 302/307 if you want to keep analytics or change targets later - a cached 301 is almost impossible to take back. Validate the incoming URL (require http/https) so the shortener can't become an open redirect. For real traffic, cache hot codes in Redis so popular links skip the database, and add a click counter if you want basic analytics.

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

What endpoints does a URL shortener API need?

Two: POST /shorten to store a URL and return a short code (201), and GET /s/<code> to look it up and redirect to the original (404 if unknown).

How do I generate the short code?

A hash of the URL truncated to ~7 chars is the simplest (and dedupes same URLs). For a guaranteed-unique, non-sequential scheme, base62-encode an auto-increment row id instead.

What status codes should the shortener return?

201 when a short URL is created, 302 or 301 on the redirect, 404 for an unknown code, and 400 for a missing/invalid URL in the request body.

Keep learning

Return the Correct HTTP Status CodeBackend projectRepair Broken API PaginationBackend projectAdd CORS Middleware to an Express APIBackend projectBackend roadmapStep by step to hiredBackend interview questionsSTAR answersAll Backend projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →