A self-hosted, serverless URL shortener built on Node.js and Vercel KV. SHA-256 hashing, idempotent short IDs, multi-tier rate limiting, and a clean REST API — at zero infrastructure cost.
Most URL shorteners are a binary choice: a closed SaaS with opaque data policies and vendor lock-in, or an open-source tool that demands a dedicated database server and full-stack infrastructure. smawl fills the gap between the two.
Zero hosting cost
Vercel's free tier handles serverless functions and KV storage for real-world traffic.
One command deploy
vercel deploy — no servers to
provision, no containers, no databases to manage.
Fully auditable
Collision handling, URL canonicalization, and rate limiting are all tested and open source.
Short IDs are generated by running the normalized URL through SHA-256,
taking the first 5 bytes of the digest, and encoding them in Base62
(a–z A–Z 0–9). The result is left-padded to 6 characters and capped at 8.
The same URL always produces the same ID — enabling idempotency without a lookup table query.
Submitting the same URL twice returns the same short link without creating a duplicate KV entry. When a generated ID collides with an existing different URL, the algorithm appends an incrementing nonce and re-hashes recursively until a free slot is found.
Counters live in Vercel KV — globally consistent across all serverless instances. Falls back gracefully to in-memory when KV is unavailable.
| Client type | Limit | Window |
|---|---|---|
| Default / anonymous | 30 req | 1 min |
| Whitelisted API key / IP | 100 req | 1 min |
| Trusted app referrer | 300 req | 1 min |
Before hashing, every URL is normalized so variations of the same address don't create duplicate entries:
http and https accepted — javascript: and
data: schemes rejectedTwo endpoints. That's it.
/api/shorten
Shortens a URL. Returns the same short URL for the same input (idempotent). Optionally accepts a custom alias.
{
"url": "https://example.com/very/long/path", // required
"customId": "myAlias" // optional, 3–10 alphanumeric
}
{ "shortUrl": "https://smawl.vercel.app/myAlias" }
{ "error": "Custom ID must be 3-10 alphanumeric characters" }
/:shortId
Resolves a short ID and issues an HTTP 302 redirect to the original URL. Unknown IDs return an HTML error page — not a bare 404.
HTTP/1.1 302 Found
Location: https://example.com/very/long/path
The API is designed to be called programmatically from any language.
curl -X POST https://smawl.vercel.app/api/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/very/long/path"}'
const res = await fetch('https://smawl.vercel.app/api/shorten', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: 'https://example.com/very/long/path' }),
});
const { shortUrl } = await res.json();
console.log(shortUrl); // https://smawl.vercel.app/aBcDe1
import requests
res = requests.post(
'https://smawl.vercel.app/api/shorten',
json={'url': 'https://example.com/very/long/path'}
)
print(res.json()['shortUrl'])
Lean by design — two serverless functions, one KV store, and a static frontend.
| File | Role |
|---|---|
api/shorten.js |
Serverless entry point for POST /api/shorten |
api/[shortId].js |
Serverless entry point for GET /:shortId |
src/shorten.js |
Core logic: validation, SHA-256 hashing, deduplication, KV storage |
src/rateLimit.js |
Multi-tier rate limiting with KV backend and in-memory fallback |
public/ |
Static frontend (HTML + vanilla JS) |
server.js |
Local Express dev server mirroring Vercel routing at localhost:3000
|
tests/ |
Jest suite — validation, hashing, Base62, collision, idempotency, rate limiting |
Requires Node.js v18+ and a Vercel account with KV enabled. Without KV credentials, URL mappings fall back to process memory — fine for local testing.
git clone <repo-url>
cd smawl && npm install
cp .env.local.example .env.local
# fill in KV_REST_API_URL + KV_REST_API_TOKEN
npm start
# Deploy to Vercel
vercel deploy
Run the test suite with npm test or npm test -- --coverage.
http and https protocols are accepted —
javascript: and data: URI schemes are rejected at validation time,
preventing open-redirect abuse.
customId is explicitly provided and
validated.
The live instance is at smawl.vercel.app. The source is on GitHub — fork it, deploy your own instance, or embed the API in your next project.