When someone asks me what side project they should build to learn how the web works, I usually say "a URL shortener."
It sounds trivial. But building one properly touches every interesting layer of web development — HTTP semantics, databases, hashing, URLs, redirects, analytics, distributed counters. You can build the naive version in twenty minutes. You can spend a career polishing it.
Let me walk you through the satisfying middle ground: a working URL shortener in Node.js in about twenty lines, plus a calm discussion of where it gets interesting after that.
The whole idea, in one paragraph
You have long URLs. You want short URLs. You store a mapping from short to long. When someone visits the short URL, you look up the long one and redirect them. That's it.
The basics (actually writing it)
Here's a minimal shortener using Express and an in-memory map:
const express = require('express');
const app = express();
app.use(express.json());
const urls = new Map(); // short -> long
let counter = 1;
// base62 encoding: 0-9, a-z, A-Z
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const encode = (n) => {
let s = '';
while (n > 0) { s = chars[n % 62] + s; n = Math.floor(n / 62); }
return s || '0';
};
app.post('/shorten', (req, res) => {
const code = encode(counter++);
urls.set(code, req.body.url);
res.json({ short: `http://localhost:3000/${code}` });
});
app.get('/:code', (req, res) => {
const long = urls.get(req.params.code);
if (!long) return res.status(404).send('Not found');
res.redirect(301, long);
});
app.listen(3000);
That's it. Run it, POST to /shorten, visit the short URL, get redirected. You have built a working bit.ly.
The interesting bit: base62 encoding
The function that turns counter 12345 into "3D7" is called base62 encoding, and it's quietly beautiful.
Why 62? Because URLs are case-sensitive (26 + 26 letters), plus ten digits, gives 62 safe characters. You could use base64, but the + and / characters cause problems in URLs.
With base62, short codes grow logarithmically:
- 62 codes fit in 1 character
- 62² = 3,844 codes in 2 characters
- 62⁶ = about 57 billion codes in 6 characters
Six characters covers the entire world's URL shortening needs. Beautiful.
Where this naive version falls down
If you're building something for real use, you quickly hit:
Persistence. An in-memory Map disappears when your server restarts. You want a real database. Postgres, Redis, or a key-value store all work; Redis is lovely here because of how it's used.
Concurrency. If two servers issue short codes at the same time, they can collide. Use a database-generated sequence, or a distributed counter (Redis INCR, for example).
Collisions for random codes. If you generate short codes randomly instead of sequentially (which is harder to guess but more private), you need to check for collisions before saving.
Analytics. Most real shorteners want to know how many times a URL was clicked, from where, by which browser. Now you're logging events, aggregating them, worrying about privacy...
Caching. If you get popular, looking up the long URL in the database for every click hits your DB hard. Put a cache in front. Now think about cache invalidation. Welcome to distributed systems.
Abuse. Anyone can submit any URL. People will submit spam, phishing, malware. You now need a URL safety service, rate limiting, and probably user accounts.
Custom slugs. "Hey, can I have yourshortener.com/my-name?" Sure. Now handle that.
Why I love this project
Every one of those "where it falls down" points is a perfect excuse to learn something. Put the data in Postgres → learn SQL. Add Redis → learn caching. Deploy to production → learn servers. Handle abuse → learn operations and trust and safety. Analytics → learn data pipelines.
You can spend a month on each of these, and each month you'll come out a meaningfully better engineer. And at the end, you'll have a URL shortener nobody uses. Which is fine. The point was never the shortener.
The human version
My favourite URL shorteners have personality. bit.ly is corporate. tinyurl.com is homely and web-1.0. Custom shorteners at publications (nyti.ms, bbc.in) tell you where the link is going.
If you build your own, give it a name, buy it a domain, host it somewhere cheap, and use it in your tweets. It's a small joy to send out a link like short.yourname.com/hello and have it work because you built the thing.
If you want to go further
Things to add, roughly in order:
- Persistence (Postgres or Redis).
- User accounts (who owns which shortcodes).
- Analytics (when does the link get clicked, from where).
- Custom slugs.
- Expiration dates.
- Rate limiting and basic abuse protection.
- A nice front-end (a plain HTML form is fine; Next.js is overkill).
- HTTPS and a custom domain.
Any one of those could be an evening's work. All of them, together, teach you a frightening amount about building web services.
Have fun. And feel free to write me if you build one.