Skip to main content
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

  1. Shorten: Insert into D1, then cache in KV
  2. Redirect: Read from KV first; on cache miss, read from D1 and populate cache
  3. Source of truth: D1 database (links table)

Setup

1

Install dependencies

cd experiments/link-shortener
npm install
2

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.
3

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
4

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"
    }
  ]
}
5

Run migrations

Apply the database schema:Local database (for development):
npm run db:migrate:local
Remote database (for production):
npm run db:migrate
This creates the links table:
CREATE TABLE links (
  code TEXT PRIMARY KEY,
  url TEXT NOT NULL,
  created_at INTEGER NOT NULL DEFAULT (unixepoch())
);
6

Run locally

npm run dev
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

url
string
required
The long URL to shorten. Must start with http:// or https://.

Response

code
string
The generated short code (6 alphanumeric characters)
url
string
The original long URL

Example

curl -X POST https://your-worker.workers.dev/shorten \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.cloudflare.com/products/workers/"}'
{
  "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

code
string
required
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
HTTP/1.1 302 Found
Location: https://www.cloudflare.com/products/workers/

Database Schema

code
TEXT
required
Short code (PRIMARY KEY)
url
TEXT
required
Original long URL
created_at
INTEGER
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

  1. Client sends POST /shorten with {"url": "https://..."}
  2. Worker validates URL (must be http/https)
  3. Generate random 6-character code from [a-zA-Z0-9]
  4. Insert into D1: INSERT INTO links (code, url) VALUES (?, ?)
  5. If UNIQUE constraint fails (collision), retry with new code (max 5 attempts)
  6. Cache in KV: LINKS_CACHE.put("link:code", url)
  7. Return {"code": "...", "url": "..."} with 201 status

Redirect flow

  1. Client requests GET /:code
  2. Validate code format (alphanumeric only)
  3. Check KV cache: LINKS_CACHE.get("link:code")
  4. Cache hit: Return 302 redirect immediately
  5. Cache miss: Query D1: SELECT url FROM links WHERE code = ?
  6. If found: cache result in KV, then return 302 redirect
  7. 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

DB
D1Database
D1 database binding (name: link-shortener-db)
KV namespace binding for caching redirects

Deploy

Deploy to Cloudflare Workers
1

Deploy Worker

Click the deploy button above or run:
npm run deploy
2

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.
3

Run migrations

Apply the schema to your production database:
npm run db:migrate
4

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"
}
error
string
Description of what went wrong
code
string
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