gresui is a web app for browsing PostgreSQL databases, built with Bun and React. Tbh there was ai coding in this, code was reviewed. I did this because I found that I had a necessity for a simpler and good looking postgres client without all the visual clutter pgadmin gives. feel free to do what you want with this. I decided to share this because it ended up being useful for me, hopefully also for you.
npm install -g gresui-webThen run:
gresui-webThis starts a small local backend (it brings its own Bun runtime — nothing
extra to install) and opens the app in your default browser. Everything runs
locally on your machine; the backend is only reachable on 127.0.0.1.
Requires Node.js 22+.
- Connection management with save/delete
- Schema and table browser sidebar
- Data grid with filtering, sorting, and pagination
- SQL editor with syntax highlighting and history
- EXPLAIN support
- Full CRUD operations (insert, update, delete)
- Dark and light themes
- MCP server — exposes the connected database to AI clients (Claude Desktop, Cursor, …) with per-key tool scopes and table allowlists
- Node.js 22+
- Bun 1.3.x — for
npm run dev(the published CLI bundles its own Bun runtime, so end users don't need it) - Docker (optional, for a local test database)
# 1. Install dependencies
npm install
# 2. Build the React frontend once
npm run build
# 3. Start the backend (serves the built frontend)
npm run devAll config — connections, settings, SQL history — lives in a single SQLite
database (gresui.db) in the config directory:
| OS | Location |
|---|---|
| Linux | $XDG_CONFIG_HOME/gresui or ~/.config/gresui |
| macOS | ~/Library/Application Support/gresui |
| Windows | %APPDATA%\gresui |
GRESUI_CONFIG_DIR overrides the location (used by tests; also handy for
portable setups). The directory is created mode 0700 and gresui.db mode
0600. Pre-SQLite connections.json / settings.json / history.json
files are imported once on first launch, then removed.
Connection passwords are encrypted at rest with AES-256-GCM under a per-machine random key, decrypted only in-process when the app reads them.
- Key storage: the key lives in the OS keychain where available — macOS
Keychain, Linux Secret Service (GNOME/KDE keyring) — otherwise in a
0600gresui.keyfile in the config directory (headless Linux, Windows v1). - Keychain migration: existing file-key installs move the key into the keychain automatically on the next launch with a keychain present; the file is then removed, so config-dir backups stop carrying the key.
- Key loss: if the key can't be found (keychain entry deleted, config dir wiped), stored passwords read as empty — reconnect and re-enter them; the next save re-encrypts.
- Troubleshooting:
GRESUI_KEY_SOURCE=file|keychain|autoforces a provider (useful on headless machines or to re-run keychain migration).
What this protects against: exposure of the database file alone (backups, sync tools, file indexing, casual reads). It does not defend against full compromise of the running app or, on Linux, same-user processes — the Secret Service answers the same user without prompting.
Adds an MCP service layer to the tables you choose — it's fancy.
gresui ships a built-in MCP server that exposes the database you're connected to as read-only tools for AI clients (Claude Desktop, Cursor, any MCP client). No separate server to install, nothing to configure by hand: open the app, connect to your database, hit the MCP button in the top bar, enable the server, and create an API key.
| Tool | What it does |
|---|---|
list_schemas |
All non-system schemas in the connected database |
list_tables |
Tables/views in a schema (respects the key's table allowlist) |
get_table |
Columns, primary keys, row estimate for a table |
get_rows |
Fetch rows with optional where (raw SQL), orderBy, limit, offset |
row_count |
Exact row count, optionally filtered |
list_indexes |
Indexes on a table with their definitions |
get_status |
Connection status (never throws) |
Every key is a bearer API key that can be scoped to a subset of the tools
and optionally restricted to specific schema.table names — so you can hand
Claude exactly the tables you want it to query directly and nothing else.
Tools are read-only by construction: there is no run_sql. Two honest
caveats: the table allowlist gates direct table access (a restricted key
can still read other tables the connected DB user can read via where
subqueries), and get_rows/row_count take raw SQL in where — same trust
level as the filter bar. Give keys only to clients you trust with the
connected user's database, and connect with a least-privileged DB user if you
need real isolation. The tools run against the app's active connection, so
disconnect and they answer "Not connected" until you reconnect.
- Launch gresui and connect to your database.
- Click MCP in the top bar (left of "Open SQL"), then Enable.
- New Key — name it, pick tool scopes (or "Select all"), optionally restrict tables, and copy the generated key (shown once).
- Add the copied config to your MCP client. Claude Desktop example
(
claude_desktop_config.json):
{
"mcpServers": {
"gresui": {
"url": "http://127.0.0.1:3939/mcp",
"headers": { "Authorization": "Bearer <KEY>" }
}
}
}The server listens on 127.0.0.1:3939 (loopback only — never exposed to the
network); if the port is taken it falls back to a random port, and the URL
shown in the MCP page is always the authoritative one. Disabled by default;
the toggle persists across restarts. Keys are stored encrypted at rest with
the same machinery as connection passwords.
The backend (backend/main.ts, backend/) is a Bun process running a
loopback-only HTTP static file server (never exposed beyond 127.0.0.1) and a
PostgreSQL driver wrapper (postgres.js). It serves the prebuilt React app and
exposes a typed JSON-RPC endpoint (/rpc) that the frontend calls over HTTP.
When enabled, it also runs the MCP server (backend/mcp.ts) on loopback port
3939, which authenticates per-request bearer keys against encrypted key
storage and dispatches read-only tool calls to the same active session.
The frontend (web/src/) is a React 19 SPA built with Vite 7, Tailwind CSS 4,
and Radix UI primitives. It runs in your browser and talks to the backend
through the RPC contract.
Shared types (shared/types.ts, shared/rpc.ts) define the RPC contract
between backend and frontend.
| Backend | Frontend |
|---|---|
| Bun | React 19 |
| postgres.js | Vite 7 |
| @modelcontextprotocol/sdk | Tailwind CSS 4 |
| zod | Radix UI |
| CodeMirror (SQL editor) | |
| TanStack Table / Virtual |
MIT — see LICENSE.


