Node.js · Vercel Functions · JavaScript
URL Shortener
Turns a long URL into a short code and 302-redirects visitors — generation, redirect, and click stats all live in one backend.
Paste a long URL and the backend generates a random short code, stores the mapping, and serves a 302 redirect when it's visited. All three operations (shorten, redirect, stats) are handled by a single serverless function so they share the same in-memory store — a good example of a real constraint you hit once code moves from 'runs on my machine' to serverless.
Live demo
embed// Every /api/* request is routed through this one serverless function so the
// in-memory link store is shared across shorten/redirect/stats — splitting
// these into separate files would put each on its own Lambda with its own
// memory, and a shortened link would 404 the moment you tried to visit it.
const store = globalThis.__urlStore || (globalThis.__urlStore = new Map());
function randomCode(len = 6) {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let out = '';
for (let i = 0; i < len; i++) out += chars[Math.floor(Math.random() * chars.length)];
return out;
}
function shorten(req, res) {
const url = (req.body && req.body.url || '').toString().trim();
let parsed;
try {
parsed = new URL(url);
if (!['http:', 'https:'].includes(parsed.protocol)) throw new Error('bad protocol');
} catch {
res.status(400).json({ error: 'a valid http(s) URL is required' });
return;
}
let code = randomCode();
while (store.has(code)) code = randomCode();
store.set(code, { url: parsed.toString(), clicks: 0, createdAt: new Date().toISOString() });
const proto = req.headers['x-forwarded-proto'] || 'https';
const host = req.headers['x-forwarded-host'] || req.headers.host;
res.status(201).json({ code, shortUrl: `${proto}://${host}/api/go?code=${code}` });
}
function goTo(req, res, code) {
const entry = store.get(code);
if (!entry) {
res.status(404).send('Short link not found (it may have expired — demo storage is in-memory).');
return;
}
entry.clicks += 1;
res.writeHead(302, { Location: entry.url });
res.end();
}
function stats(req, res) {
const proto = req.headers['x-forwarded-proto'] || 'https';
const host = req.headers['x-forwarded-host'] || req.headers.host;
const links = Array.from(store.entries())
.map(([code, v]) => ({ code, shortUrl: `${proto}://${host}/api/go?code=${code}`, ...v }))
.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
res.status(200).json(links);
}
module.exports = (req, res) => {
const action = req.query['...action'] || [];
const [segment] = Array.isArray(action) ? action : [action];
if (segment === 'shorten' && req.method === 'POST') return shorten(req, res);
if (segment === 'go' && req.query.code) return goTo(req, res, req.query.code.toString());
if (segment === 'stats') return stats(req, res);
res.status(404).json({ error: 'not found' });
};
const form = document.getElementById('form');
const urlInput = document.getElementById('url');
const result = document.getElementById('result');
const shortLink = document.getElementById('short-link');
const errorBox = document.getElementById('error');
const list = document.getElementById('list');
async function loadStats() {
const res = await fetch('/api/stats');
const links = await res.json();
list.innerHTML = '';
if (!links.length) {
list.innerHTML = '<p class="empty">No links yet. Shorten one above.</p>';
return;
}
for (const link of links) {
const row = document.createElement('div');
row.className = 'item';
row.innerHTML = `
<span class="orig"></span>
<a class="short" target="_blank" rel="noopener"></a>
<span class="clicks"></span>
`;
row.querySelector('.orig').textContent = link.url;
const a = row.querySelector('.short');
a.href = link.shortUrl;
a.textContent = `/${link.code}`;
row.querySelector('.clicks').textContent = `${link.clicks} click${link.clicks === 1 ? '' : 's'}`;
list.appendChild(row);
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
errorBox.textContent = '';
result.classList.remove('show');
const res = await fetch('/api/shorten', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: urlInput.value.trim() }),
});
const data = await res.json();
if (!res.ok) {
errorBox.textContent = data.error || 'Something went wrong.';
return;
}
shortLink.href = data.shortUrl;
shortLink.textContent = data.shortUrl;
result.classList.add('show');
urlInput.value = '';
loadStats();
});
loadStats();