The Zero-Backend Pattern: Storing an App's Entire State in the URL
When I built iZicards, a free digital business card tool, I made a decision that shaped everything else: there is no signup, no database, and no server storing your card. All of your details live inside the link itself. Share the link, and you've shared the card. Lose the link, and — well, make a new one; it takes thirty seconds.
This is an old pattern that deserves more use than it gets. Here's how it works and what I learned shipping it.
The mechanics
The idea is simple: serialize the app's state, encode it, and put it in the URL — either the query string or, better, the fragment.
// Encode: state → JSON → compressed → base64url
function encodeState(state: CardState): string {
const json = JSON.stringify(state);
const compressed = compress(json); // any deflate-style codec
return base64UrlEncode(compressed);
}
// The link is the database
const url = `https://app.example.net/#${encodeState(card)}`;
On load, the app reads the fragment, decodes, and renders. That's the whole backend.
Two implementation details matter more than they look:
- Use the fragment (
#), not the query string, for personal data. Fragments are never sent to the server in the HTTP request — the data goes from the sharer's device to the viewer's device without your server ever seeing the contents. That's not just less infrastructure; it's a genuinely better privacy story. - Compress before you encode. JSON is repetitive and base64 inflates by ~33%. A deflate pass typically shrinks card-sized payloads enough to keep URLs comfortable and QR codes scannable.
Why this wins (when it fits)
Nothing to maintain. No database migrations, no backups, no GDPR deletion requests for data you never had. For a free product, this is the difference between "runs forever on static hosting" and "monthly bill plus operational duty."
Nothing to breach. The most secure user database is the one that doesn't exist.
Instant, honest sharing. The link isn't a pointer to the card — it is the card. There's no "this content is no longer available" failure mode, no dead links when a startup shuts its servers down.
Trivially forkable. Anyone can take their card, tweak a field, and get a new link. Versioning for free.
The limits — and where the pattern breaks
Be honest about these before you commit:
- URL length. Browsers and messengers are generous (usually 2,000+ characters is safe in practice), but QR code density is the real ceiling — the longer the URL, the denser and harder-to-scan the code. Compression buys headroom; photos don't fit. iZicards keeps images out of the payload for exactly this reason.
- No update-in-place. If you've shared a link and your phone number changes, the old link keeps saying the old number. That's a feature (immutability) and a bug (stale data), depending on your product. If update-in-place is core to your product, you want a database.
- No analytics or revocation. You can't count views or kill a shared link. Again: privacy feature, product limitation — pick your lens.
- Anything secret doesn't belong there. The state is client-readable by design. It's encoding, not encryption.
When to reach for it
My heuristic: if the thing being shared is small, self-contained, and better off immutable — a business card, a color palette, a config snippet, a game seed, a filter set for a dashboard — the URL is the right database. If it's large, private, or needs to change after sharing, it isn't.
We've collectively over-normalized "every app needs a backend." Some of the most durable software I've shipped is durable precisely because there's nothing behind it to break.