Skip to content

Shorten a URL

Accept a URL, store it under a short random code, and resolve that code back to the URL with an HTTP redirect. Codes should expire on their own, and malformed input should be rejected before any handler runs.

src/codes.bynk
commons codes
---
A short code: 6–12 url-safe characters. Every `Slug` that exists has already
passed this check, so it can be used as a key without re-validation. A refined
`String`, so it projects structurally across the context boundary.
---
type Slug = String where MinLength(6) and MaxLength(12)
---
A target URL — non-empty and bounded so a malformed body is rejected at the
boundary, before any handler runs.
---
type Url = String where MinLength(1) and MaxLength(2048)
---
The KV key for a slug. Namespacing keeps the link table separate from anything
else stored in the same namespace.
---
fn keyOf(code: Slug) -> String {
"link:\(code)"
}
src/links.bynk
context links
uses codes
consumes bynk { Random }
consumes bynk.cloudflare { Kv } -- locks this unit to Cloudflare
type CreateRequest = {
target: Url,
}
type CreatedView = {
code: Slug,
target: Url,
}
service api from http {
-- Create a short link. A random uuid is sliced to a slug; the mapping is
-- stored for a day (putTtl), after which it expires on its own.
on POST("/links") by Visitor (body: CreateRequest) -> Effect[HttpResult[CreatedView]] given Random, Kv {
let id <- Random.uuid()
let raw = "\(id)"
match Slug.of(raw.slice(0, 8)) {
Err(_) => ServerError("could not generate a slug")
Ok(code) => {
let _ <- Kv.putTtl(keyOf(code), body.target, 86400)
Created(CreatedView { code: code, target: body.target })
}
}
}
-- Resolve a short link: a `302 Found` redirect to the stored target, or a
-- `404` if the slug is unknown or has expired. The redirect carries no body —
-- the target URL travels in the `Location` header.
on GET("/links/:code") by Visitor (code: Slug) -> Effect[HttpResult[Url]] given Kv {
let stored <- Kv.get(keyOf(code))
match stored {
Some(target) => Found(target)
None => NotFound
}
}
}
Open the full project ↗

This example reaches Workers-only shapes (storage bindings, agents, or cron), so it runs with bynk dev rather than in the browser playground. See Install to get started.

The boundary types live in commons codes. Slug is a refined String of 6–12 url-safe characters, and Url is a non-empty String bounded at 2048 characters, so an invalid code can never be stored and an over-long URL is rejected with 400 before a handler sees it. The keyOf helper namespaces a slug into its KV key — "link:\(code)" — keeping the link table distinct from anything else in the same namespace.

The service lives in context links, which consumes bynk { Random } and consumes bynk.cloudflare { Kv }. That second line locks the unit to Cloudflare: KV is a platform-specific capability, declared in the signature and injected by the toolchain rather than constructed in the code.

POST("/links") mints a code by calling Random.uuid(), slicing the result to eight characters, and passing it through Slug.of. On success it stores the mapping with Kv.putTtl(keyOf(code), body.target, 86400) — a one-day TTL, so the entry disappears on its own with no sweep to write — and returns Created with the code and target. GET("/links/:code") takes a Slug directly, reads Kv.get(keyOf(code)), and matches the Option: Some(target) resolves to a Found redirect carrying the URL in the Location header with no body, and None becomes a 404.