A terminal UI for backend developers to explore and operate backend stacks (Postgres, MongoDB, Redis, RabbitMQ, Kafka, Loki) from one place.
querypro is plugin-first: the Go core owns the TUI, plugin lifecycle,
connections and storage, and knows nothing about any datastore. Every backend
is a TypeScript plugin in plugins/ that runs as a subprocess and talks to
the core over gRPC, with streaming for live data (tails, subscriptions,
consumers).
- Go (version in
go.mod) to build - Node.js 24.2+ on
PATHat runtime (plugins run TypeScript directly) - Docker, only for
make demoand the integration tests
make build && ./bin/querypro # or: make run
make demo # start every backend in docker, seeded tabsctrl+t opens a connection, enter runs a query, ctrl+o opens a resource,
ctrl+x shows its actions, / opens the command palette, !name query runs
against another tab. Wide results stay readable: a table shows the columns
that fit, shift+←/→ (or a horizontal wheel) scrolls to the rest, and
clicking a row opens every column of it in a popup (c copies it). The
palette's Help lists every key.
| Plugin | URI | Queries |
|---|---|---|
| postgres | postgres://user:pass@host:5432/db?sslmode=require |
SQL, \d name, \dt, \dn, \l |
| mongodb | mongodb://host:27017/db |
mongo shell: db.x.find({}), use db, show collections |
| redis | redis://:pass@host:6379/0, rediss://… |
any command, SUBSCRIBE/PSUBSCRIBE/MONITOR stream live |
| rabbitmq | amqp://user:pass@host:5672/vhost?management=http://host:15672 |
queues, peek q, tap exchange #, publish, … |
| kafka | kafka://user:pass@h1:9092,h2:9092?ssl=true&mechanism=scram-sha-512 |
topics, describe t, tail t, produce t v, … |
| loki | https://user:pass@host?org=tenant |
LogQL, since 6h {app="x"}, tail {app="x"}, labels |
Unknown commands reply with the list of supported ones.
Everything lives in $QUERYPRO_HOME, default os.UserConfigDir()/querypro
(~/Library/Application Support/querypro on macOS, ~/.config/querypro on
Linux). The directory is 0700 and every file 0600.
| File | Contents |
|---|---|
connections.json |
saved connections, including their URIs and secrets |
settings.json |
theme, sidebar, mouse |
history.json |
last 500 queries |
logs/<kind>.log |
plugin stderr, truncated past 10 MB |
Writes are atomic (temp file + rename). A corrupt file stops startup with its path instead of being overwritten.
Plugins are found in -plugins, $QUERYPRO_PLUGINS, plugins/ next to the
binary, or ./plugins.
make tools # pinned golangci-lint, buf, protoc-gen-go(-grpc)
make test # go test -race + plugin unit tests
make it # integration tests, needs `make services`
make lint # golangci-lint, tsc, buf lint
make proto # regenerate internal/plugin/pb from proto/
make dist # dist/querypro-<version>-<os>-<arch>.tar.gzA release is the binary plus plugins/ (with production node_modules) and
proto/ side by side; make dist builds it for the current GOOS/GOARCH.
A plugin is a directory in plugins/ with a plugin.json:
{
"kind": "postgres",
"code": "PG",
"color": "#5b9bd5",
"uri": "postgres://postgres:postgres@localhost:5432/postgres",
"placeholder": "SELECT * FROM users LIMIT 10;",
"command": ["node", "index.ts"]
}and an entry point that calls serve from plugins/sdk:
import { serve, table, type Session } from "../sdk/index.ts";
serve(async (uri): Promise<Session> => ({
server: "Example 1.0",
resources: async () => [{ kind: "table", name: "users" }],
actions: (r) => [{ name: "Preview", query: `SELECT * FROM ${r.name}` }],
query: async (q, signal) => table(["q"], [[q]]),
close: async () => {},
}));query returns table(...), text(...), or live(signal, summary, start)
for streams; dispatch maps small command languages. Any executable that
speaks the protocol below works; the SDK is just the TypeScript way.
The contract is proto/querypro/plugin/v1/plugin.proto.
- The core starts
commandin the plugin directory withQUERYPRO_SOCKET=<path>and a stdin pipe. - The plugin serves
PluginServiceon that unix socket and then writesquerypro-plugin 1\nto stdout. Anything else fails the start. Connect(uri)returns a session id used by every other call.Querystreams onetableortextevent for a finished result, or oneliveevent followed bylineevents until the core cancels.- A call on a session whose backend connection is gone fails with
UNAVAILABLE; the core then reconnects the tab. - The plugin exits when its stdin closes. If it dies, the core marks its tabs disconnected and respawns it on the next connect.