No backend, but a real database

  • architecture
  • serverless
  • cloudflare
  • sqlite
  • webassembly

I built Scrolls Buddy, a planning tool for a MapleStory private-server community. The audience is about twenty people a day. That number is the whole story of the architecture, because it made one decision for me before I wrote any code: there would be no backend.

Not “a small backend.” None. No server to secure, no database to operate, no bill that grows with traffic. The interesting engineering was refusing to give up any real capability to honour that — because “no backend” usually means “no persistence, no analytics, no data,” and I wanted all three.

Here is how each one survived the constraint.

Persistence: SQLite in the browser

The app has session history, saved item configurations, and scrolling outcomes I want to query — “show me this session’s profit,” “what did I roll last time.” That is relational data, and localStorage is a bad place to keep it the moment you want to ask it a question: it is a string key-value store, and you end up hand-rolling a query engine over JSON blobs.

So the app runs SQLite compiled to WebAssembly, persisted into the browser through IndexedDB. Real SQL, real tables, real queries — the database just happens to live in the tab instead of on a host. The data never leaves the device, which means there is nothing to breach, nothing to migrate, and nothing to back up. For a single-user tool, the user’s own browser is a perfectly good place for their data to live.

External data: pulled, not hosted

Item names, icons, and base stats are not mine to own — they are game data with canonical public sources. Instead of scraping and hosting them, the app fetches from public game-data APIs on demand and lets Cloudflare’s edge cache the responses. No asset pipeline, no scraper to babysit, no copy of someone else’s data drifting out of date. The client stays thin over sources that are already authoritative.

Analytics: first-party, still serverless

The one thing people assume forces a backend is knowing whether anyone uses your thing. It does not. The app sends product events to PostHog — but through a first-party /ingest path on its own domain rather than straight to PostHog’s servers. That rewrite keeps the telemetry first-party and lets it survive ad blockers, while the actual event storage is PostHog’s problem, not mine. I run no analytics infrastructure and still get a real funnel: it is how I know the tool holds ~20 daily actives and which features earn their place.

What runs it

A static React/Vite bundle on Cloudflare Pages, deployed on push, on a subdomain of a domain I already manage. SQLite-WASM for the data layer, PostHog for the feed, public APIs for game data. It is a PWA, so once the data is cached it works offline — which follows naturally from the logic and storage already being local.

Total cost to keep running: zero. Total servers to operate: zero.

The point

The reflex, when you want persistence and analytics, is to reach for a backend. Most of the time you are reaching for it out of habit. Between WebAssembly builds of real databases, edge caching in front of public data, and analytics vendors that only need a beacon, a surprising amount of “backend” is just a client-side concern wearing a server costume.

Scoping the tool to twenty people is what made this possible — and, honestly, what made it finished. The serverless architecture was not a clever trick bolted on afterward; it was the direct consequence of building the right size of thing for the actual need. The full write-up, including the MapleRoyals scrolling problem it solves, is on the project page.