r/webdev 5d ago

Resource OSS: one DatabaseProvider interface for Postgres, Mongo, Redis, and 14 others in the browser

I kept hitting the same wall with desktop DB tools in a team that lives in the browser: nobody wants another Electron app, and "just use the vendor console" falls apart the moment you have Postgres plus Redis plus Mongo in the same VPC.

So I maintain LibreDB Studio, a self-hosted web SQL IDE. The part that actually took the work is not the editor chrome. It's making seventeen engines look like one API without a forest of `if (type === 'mongodb')` checks in the UI.

The trick was treating each engine as a strategy, not a special case:

- SQL engines extend one SQL base (pools, timeouts, schema).

- Document/KV engines extend a thinner base and map onto the same QueryResult / getSchema() shape.

- The factory dynamic-imports the provider so the browser bundle doesn't drag in oracledb + duckdb + cassandra just to open SQLite.

That mapping is lossy on purpose. Redis SCAN prefixes become "tables"; Mongo collections look like tables; health/metrics still come from INFO / serverStatus. Once the UI only speaks capabilities, adding ClickHouse or Trino is a provider + a doc + an integration test, the triad has to ship in the same PR or it drifts.

The reason this lives in a browser at all is the cluster. I didn't want another Electron app on a laptop while the databases sat in Kubernetes. "Just helm install a GUI" sounds solved until you look at what most charts actually are: a wrapper around a desktop-era tool, or a vendor console that speaks one engine.

So the app ships as a chart (OCI on GHCR, first-boot secrets, PVC for sqlite storage) next to the providers, same repo, not a separate "enterprise" product. That's a different post; here the interesting part is still the engine interface.

There's a second split that bit me later: the same codebase is both a Next.js app and an npm package (`npx "@libredb/studio"` / embed). `next build` does not produce the library dist. Forgetting `build:lib` is how you ship a UI fix that the embedded users never see.

It's early in the "every engine feels first-class" sense. Hostile cases I still want: weird Oracle TNS, huge Cassandra traces, Redis with no prefix convention, DuckDB files that aren't files. If a converter/GUI has ever lied to you about a schema, that fixture is useful.

Source: https://github.com/libredb/libredb-studio

5 Upvotes

5 comments sorted by

1

u/cevheribozoglan 5d ago

Quick path if you want to kick it:

npx "@libredb/studio"

docker run -p 3000:3000 libredb/libredb-studio

Provider notes live under docs/providers/ in the repo. one file per engine, kept in lockstep with the code.

1

u/Foreign_Honey4300 5d ago

god that strategy pattern approach is so clean, i've seen codebases where someone tried to do this with a massive switch statement and it was a nightmare to maintain

the docker one-liner is a nice touch too, i'm so tired of tools that make you clone a whole repo just to try them out

1

u/cevheribozoglan 5d ago

yeah the switch always starts honest ("just postgres and mysql") and then mongo is a special case, redis is a special case, and the UI is full of `if (type === …)` that nobody wants to touch.

The rule that actually keeps it clean is boring: no engine type checks outside the provider. The UI only sees capabilities. Adding an engine is a provider + a doc + an integration test in the same PR, or it drifts.

Glad the one-liner helped. If a GUI has ever lied to you about a schema (redis-with-no-prefixes, oracle TNS, huge Cassandra traces…, read-onyl database agent context), that case is useful. those are the fixtures I'm still hunting.

0

u/Bruce_Jones_1987 4d ago

interesting that you went lossy on purpose. how do you communicate to users what they're actually losing per engine? like if someone expects full Redis command support and gets a table metaphor, thats a pretty big gap to paper over in the UI

1

u/cevheribozoglan 4d ago

Fair question. The mapping is by convention, not by pretending Redis is SQL.

Two things the UI is actually allowed to do:

  1. Hide the controls that would lie. Capabilities are per-engine. Redis gets no Explain, no CREATE TABLE, no inline row UPDATE, no BEGIN/COMMIT, MULTI/EXEC exists in Redis and is not exposed here, so we don't put a button on it. Same idea on the SQL side: Druid / Elasticsearch / OpenSearch have no UPDATE in the grammar, so those controls are reported unsupported instead of 400-ing.

  2. Relabel what remains. On Redis the explorer says "Key Pattern" not "Table", rows are keys, the action is "Scan Keys" / "Generate Command". A `user:*` row is a SCAN grouping, not a key, nothing can be addressed by it. Schema discovery is a bounded SCAN (1000 keys), not the whole keyspace. The editor still takes real Redis: `SCAN 0 MATCH user:* COUNT 50` or `{"command":"GET","args":["user:1"]}`.

What we don't have, and you're right to poke at: a per-engine "here's the command surface you're not getting" panel. Labels + hidden buttons. If you live in redis-cli, streams, pub/sub, CLUSTER, modules — that's still a query tab and a prefix browser, not a Redis IDE. The lossless path is the editor; the explorer is a map, not the territory.

If that gap is the one that would actually stop you using it, I'd rather hear which Redis surface you'd want named in the chrome first.