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.
The two endpoints
POST /shorten- take{"url": "..."}, store it, return a shortcode(and the full short URL).201on success.GET /s/<code>- look up the code and302/301redirect to the original URL;404if unknown.
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
- Validate the input URL - reject empty/malformed bodies with
400. INSERT OR IGNORE(or upsert) so re-shortening the same URL doesn't error.- Right status codes -
201on create,302/301on redirect,404for unknown,400for bad input. - Index the code (it's the primary key here) so lookups stay O(1) at scale.
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
- Building POST /shorten + the redirect endpoint
- Storing code -> URL and generating codes
- Returning correct status codes (201/302/404/400)
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
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 →