URL Shortener (Cloudflare Workers)
A URL shortener service deployed to Cloudflare Workers. Demonstrates KV storage, redirect responses, scheduled cleanup, and open CORS -- all in one edge function.
The Full Application
Create shortener.tova:
shared {
type ShortenRequest {
url: String
ttl: Int
}
type ShortenResponse {
code: String
short_url: String
}
}
edge {
target: "cloudflare"
kv LINKS
env BASE_URL = "https://s.example.com"
secret ADMIN_KEY
cors {}
health "/healthz"
// Generate a random 6-character code
fn make_code() -> String {
chars = "abcdefghijklmnopqrstuvwxyz0123456789"
code = ""
for i in range(6) {
idx = Math.floor(Math.random() * len(chars))
code = code ++ chars[idx]
}
code
}
// Shorten a URL
route POST "/api/shorten" => fn(req) {
body = await req.json()
code = make_code()
ttl = body.ttl || 86400
await LINKS.put(code, body.url, { expirationTtl: ttl })
ShortenResponse(code, "{BASE_URL}/{code}")
}
// Look up stats without redirecting
route GET "/api/links/:code" => fn(req, params) {
url = await LINKS.get(params.code)
if url == nil {
Response.new(JSON.stringify({ error: "Not found" }), { status: 404 })
} else {
{ code: params.code, url: url }
}
}
// Redirect to the original URL
route GET "/:code" => fn(req, params) {
url = await LINKS.get(params.code)
if url == nil {
Response.new("Not found", { status: 404 })
} else {
Response.redirect(url, 302)
}
}
// Scheduled cleanup logging (KV handles TTL expiry natively)
schedule "cleanup-log" cron("0 */6 * * *") {
print("Link cleanup check at {Date.new().toISOString()}")
}
}Running It
Build and deploy to Cloudflare Workers:
tova build shortener.tova
npx wrangler dev .tova-out/shortener.edge.jsThe compiler generates a wrangler.toml with your KV namespace binding. Add your KV namespace ID, then deploy:
npx wrangler deployTest it locally:
# Shorten a URL
curl -X POST http://localhost:8787/api/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://tovalang.dev", "ttl": 3600}'
# Follow the short link
curl -L http://localhost:8787/abc123What This Demonstrates
KV Store
The kv LINKS declaration creates a Cloudflare KV namespace binding. On Cloudflare, this is wired from the env parameter in the fetch handler. KV operations like LINKS.put() and LINKS.get() work directly with the platform's key-value store, including TTL-based expiration.
Redirect Responses
Route handlers can return Response objects for full control. The /:code route uses Response.redirect(url, 302) for temporary redirects instead of returning JSON.
Scheduled Tasks
The schedule block defines a cron job that runs every 6 hours. On Cloudflare, this compiles to a scheduled() export that matches event.cron. Cloudflare KV handles TTL expiry natively, so the schedule here is used for logging and monitoring.
Open CORS
The empty cors {} block enables wildcard CORS -- any origin can call the API. The compiler generates preflight handling and adds CORS headers to all responses, including error responses.
Key Patterns
- Shared types define the API contract between producer and consumer
- Inline lambdas on routes (
fn(req) { ... }) keep handlers close to their routes - Named functions (
fn make_code()) can be called from any route in the edge block envwith defaults provides configuration that can be overridden at deploy timesecretdeclares values that must be set in the deployment environment (no defaults)
What's Next
- Add authentication with a Feature Flag Service
- Use middleware chains with an API Proxy
- Learn more in the Edge Block guide