A Tiny CLI, and Where to Take It
The logic is finished. shorten() and resolve() do everything a URL shortener does. But right now the only way to use it is to edit the source code, which is no way to hand it to a friend. This phase wraps it in a small command interface - type a command, get an answer - and then lays out the paths from this toy to something real.
A command loop
A command-line tool is a loop: read what the user typed, figure out which command it is, do the thing, print the result, repeat. We'll support two commands:
shorten <url>- mint a code for a URL and print it.get <code>- resolve a code back to its URL.
On your own machine you'd read commands with Python's input() in a while True: loop. Here in the browser there's no keyboard prompt, so we'll feed the loop a fixed list of commands and process them the same way input() would. The command-parsing logic is identical - only the source of the lines changes.
Run this. It's the whole thing, end to end:
=
=
return
=
, =
return
=
=
= 0
global
return
=
=
=
+= 1
return
return
= # ["shorten", "https://..."]
return
=
== and == 2:
=
# on your machine this would be: while True: handle(input("> "))
# here we feed it a script of commands instead
=
Run it and read the transcript. Each > line is a typed command; the indented line under it is the response. The repeated URL gets the same code, an unknown code reports cleanly, and even nonsense input gets a tidy "unknown command" instead of a crash. line.split(maxsplit=1) is what splits shorten https://... into the command and everything after it, keeping the URL whole even though URLs can contain spaces in odd cases.
That's a complete, usable URL shortener in well under 50 lines. You built it from a dictionary, a counter, and two functions.
Taking it to your own machine
To run this for real, copy the code above into a file called shortener.py, and swap the scripted session loop for a live one:
# replace the `session` block with this:
=
break
Then run it from a terminal:
Now it prompts you with > and waits for commands until you type quit. Same logic, real keyboard.
Where to take it
Here are the next steps in roughly increasing order of effort. Each one is a real, satisfying upgrade.
| Upgrade | What it adds | Where to start |
|---|---|---|
| File persistence | Codes survive restarts instead of vanishing | Save the two dicts to a JSON file on each change, load them on startup |
| Custom aliases | shorten <url> <alias> so users pick /launch |
Add an optional third part to the command; reject it if the alias is taken |
| A real web server | Actual http://localhost/aZ4 links that redirect |
The stdlib http.server, or step up to Flask/FastAPI |
| Unguessable codes | Codes no one can walk through sequentially | Mix randomness into the counter, or hash + base62 the count |
| Click counts | Track how many times each link is followed | A third dict, code -> count, bumped in resolve() |
File persistence is the most rewarding first step - right now everything evaporates when the program stops. The standard-library json module turns your two dictionaries into a file and back:
global , ,
=
=
=
=
Call load() at startup (wrapped in a try/except FileNotFoundError for the first run) and save() after each shorten(). Now your links persist across restarts - the leap from a toy to something you'd actually keep.
A real web server is the upgrade that makes it feel like the real product. With http.server from the standard library you can answer GET /aZ4 by looking up the code and returning an HTTP redirect to the long URL - at which point clicking your short link in a browser genuinely sends you somewhere. That's the moment it stops being a script and starts being a service.
What you built
You started with a single sentence - a map from short code to long URL - and ended with a working program: a dictionary store, a base62 generator fed by a counter, shorten() and resolve() that handle the awkward cases, and a command loop to drive it. The same architecture, scaled up with a database and a web server, is what runs behind every short link you've ever clicked.
The mechanism was never the hard part. Now you've seen it bare, and you know exactly which dials to turn to take it further.