Create short links that redirect to long URLs. Uses D1 as the source of truth with KV as a read-through cache for fast redirects.
Features
POST /shorten : Create a short link from a long URL
GET /:code : Redirect to the original URL (302 redirect)
D1 database : Primary storage for all links
KV cache : Read-through cache for fast redirects
6-character codes : Random alphanumeric short codes (e.g., a1B2c3)
Collision handling : Automatic retry on duplicate codes
Architecture
Shorten : Insert into D1, then cache in KV
Redirect : Read from KV first; on cache miss, read from D1 and populate cache
Source of truth : D1 database (links table)
Setup
Install dependencies
cd experiments/link-shortener
npm install
Create D1 database
npx wrangler d1 create link-shortener-db
This returns output like: database_id = "a1b2c3d4-1234-5678-abcd-123456789abc"
Copy the database_id value.
Create KV namespace
npx wrangler kv namespace create LINKS_CACHE
This returns output like: id = "0123456789abcdef0123456789abcdef"
Copy the id value. For local development, create a preview namespace: npx wrangler kv namespace create LINKS_CACHE --preview
Update wrangler.json
Replace the placeholder IDs with your actual values: {
"d1_databases" : [
{
"binding" : "DB" ,
"database_name" : "link-shortener-db" ,
"database_id" : "a1b2c3d4-1234-5678-abcd-123456789abc"
}
],
"kv_namespaces" : [
{
"binding" : "LINKS_CACHE" ,
"id" : "0123456789abcdef0123456789abcdef"
}
]
}
Run migrations
Apply the database schema: Local database (for development):Remote database (for production):This creates the links table: CREATE TABLE links (
code TEXT PRIMARY KEY ,
url TEXT NOT NULL ,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
);
Run locally
Your Worker will be available at http://localhost:8787
API Reference
Shorten URL
POST /shorten Create a short link for a long URL.
Request Body
The long URL to shorten. Must start with http:// or https://.
Response
The generated short code (6 alphanumeric characters)
Example
curl -X POST https://your-worker.workers.dev/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://www.cloudflare.com/products/workers/"}'
Response (201 Created)
Error: Invalid URL (400)
Error: Invalid JSON (400)
{
"code" : "a1B2c3" ,
"url" : "https://www.cloudflare.com/products/workers/"
}
Usage
After creating a short link, redirect by visiting:
https://your-worker.workers.dev/a1B2c3
Redirect
GET /:code Redirect to the original URL associated with the short code.
Path Parameters
The short code (6 alphanumeric characters)
Response
302 Found : Redirects to the original URL
404 Not Found : If the code doesn’t exist
Example
# Follow redirects
curl -L https://your-worker.workers.dev/a1B2c3
# Show redirect without following (see Location header)
curl -I https://your-worker.workers.dev/a1B2c3
Success (302 Found)
Error: Not found (404)
Error: Invalid code (404)
HTTP / 1.1 302 Found
Location : https://www.cloudflare.com/products/workers/
Database Schema
links table
Unix timestamp (auto-generated)
Schema SQL
CREATE TABLE IF NOT EXISTS links (
code TEXT PRIMARY KEY ,
url TEXT NOT NULL ,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
);
How It Works
Shortening flow
Client sends POST /shorten with {"url": "https://..."}
Worker validates URL (must be http/https)
Generate random 6-character code from [a-zA-Z0-9]
Insert into D1: INSERT INTO links (code, url) VALUES (?, ?)
If UNIQUE constraint fails (collision), retry with new code (max 5 attempts)
Cache in KV: LINKS_CACHE.put("link:code", url)
Return {"code": "...", "url": "..."} with 201 status
Redirect flow
Client requests GET /:code
Validate code format (alphanumeric only)
Check KV cache: LINKS_CACHE.get("link:code")
Cache hit : Return 302 redirect immediately
Cache miss : Query D1: SELECT url FROM links WHERE code = ?
If found: cache result in KV, then return 302 redirect
If not found: return 404 error
Code generation
Codes are generated using cryptographically random bytes:
const CODE_LENGTH = 6 ;
const CODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ;
function generateCode () : string {
let code = "" ;
const bytes = new Uint8Array ( CODE_LENGTH );
crypto . getRandomValues ( bytes );
for ( let i = 0 ; i < CODE_LENGTH ; i ++ ) {
code += CODE_CHARS [ bytes [ i ] % CODE_CHARS . length ];
}
return code ;
}
Total possible codes: 62^6 = 56.8 billion
Testing
Local testing
# Start dev server
npm run dev
# Shorten a URL
curl -X POST http://localhost:8787/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'
# Output: {"code":"xY3pQz","url":"https://example.com"}
# Test redirect
curl -I http://localhost:8787/xY3pQz
# Output: Location: https://example.com
Query the database
# Local database
npx wrangler d1 execute link-shortener-db --local --command "SELECT * FROM links"
# Remote database
npx wrangler d1 execute link-shortener-db --command "SELECT * FROM links"
Inspect KV cache
# List all keys
npx wrangler kv key list --namespace-id=YOUR_KV_ID
# Get a specific cached link
npx wrangler kv key get "link:xY3pQz" --namespace-id=YOUR_KV_ID
Use Cases
Marketing campaigns
Create memorable short links for campaigns:
curl -X POST https://short.example.com/shorten \
-d '{"url": "https://example.com/summer-sale?utm_campaign=twitter"}'
# Share: https://short.example.com/a1B2c3
API URL shortening
Integrate into your app:
const response = await fetch ( 'https://your-worker.workers.dev/shorten' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ url: longUrl })
});
const { code , url } = await response . json ();
const shortUrl = `https://your-worker.workers.dev/ ${ code } ` ;
QR code generation
Shorter URLs = simpler QR codes:
# Create short link
curl -X POST https://short.example.com/shorten \
-d '{"url": "https://example.com/very/long/path/to/product"}'
# Generate QR code for: https://short.example.com/x7Y2kL
Bindings
D1 database binding (name: link-shortener-db)
KV namespace binding for caching redirects
Deploy
Deploy Worker
Click the deploy button above or run:
Create production resources
If not already created: # Create D1 database
npx wrangler d1 create link-shortener-db
# Create KV namespace
npx wrangler kv namespace create LINKS_CACHE
Update wrangler.json with the returned IDs.
Run migrations
Apply the schema to your production database:
Test production
curl -X POST https://your-worker.workers.dev/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://www.cloudflare.com"}'
Monitoring
View logs
# Tail production logs
npx wrangler tail
# Filter for errors
npx wrangler tail --status error
Analytics
View request metrics in the Cloudflare dashboard :
Workers & Pages → your Worker → Metrics
See request volume, error rate, and response times
Limitations
No expiration: Links never expire (implement TTL if needed)
No analytics: No tracking of clicks or usage (add D1 table if needed)
No custom codes: Codes are always randomly generated
No link updates: Cannot change the URL for an existing code
No deletion: Cannot delete links after creation (add DELETE endpoint if needed)
Error Responses
All errors return JSON:
{
"error" : "Human-readable error message" ,
"code" : "ERROR_CODE"
}
Description of what went wrong
Error code: INVALID_BODY, INVALID_URL, NOT_FOUND, INVALID_CODE, INTERNAL_ERROR
Source Code
View the complete source code on GitHub: link-shortener
Cloudflare Features
Workers : Serverless compute at the edge
D1 : SQLite database at the edge (primary storage)
KV : Key-value store (read-through cache)
Hono : Lightweight web framework