Skip to content

Repository files navigation

Ashurbanipal

Ashurbanipal logo

No-bullshit database browser for schemaful databases; self-contained, embeddable, read-only. No separate DB client, no extra credentials, no build step.

Ashurbanipal demo

Try it live: synthetic data, no backend.

Why

Most engineers just want to browse their database. Having such functionality in a corporate environment currently means:

  • Did you request AWS access? Wait for approval.
  • Approved? Now add your username and SSH key to a repo nobody's heard of, and wait for that owner to approve you too.
  • Follow a Confluence page to wire up AWS + SSH + your pick of DBeaver/pgcli/psql/pgAdmin/TablePlus.
    • ssh timed out, oh too bad, you should use mosh instead
  • Get your session killed by fucking Okta re-auth every 4 hours. Repeat.
    • blindly accept the MFA prompt, or else your session dies and you have to start over
  • The bastion host is being patched, so none of the above even works.
  • "You don't need to have db access, you just need to slice your stories thinly enough so you can test your code without needing db access" a wise engineer in an unwise org.
  • Can't deploy a sidecar container to run a db client, because...you get the point.

all I need is to just see a row in the db, so I can complete my feature story.

Ashurbanipal skips the whole chain by not needing a new connection: it runs inside the process that already has one. If your service can query its own database, then you can look at a table from your browser.

What it does

  • Browse tables: paginated rows, click-to-sort columns, primary/foreign-key and column-comment hints.
  • Filter rows with a subset of SQL WHERE syntax (no joins, no subqueries, no CTEs, no DML).
  • Works with Postgres, MySQL/MariaDB, and SQLite.
  • Register more than one database and switch between them (currently only one db type at a time); browse across schemas where the engine has them.
  • Link sibling instances and see which are reachable.
  • Ships as one static file compiled into your binary; nothing extra to host or deploy.

What it doesn't do

  • No write access, no migrations, no schema changes.
  • At the moment, this is not a replacement for a full-featured DB client like DBeaver, pgcli, however I do plan to support some of that functionality in the future

Security

Ashurbanipal ships no authentication or authorization. Access is a perimeter concern: run the host service behind your corporate VPN, reachable by you and your team but not the outside world. If you want a login or per-user rules, add that in front of the mounted router.

Two things back that up:

  • Fail-safe default: the enabled flag defaults to off. Ashurbanipal has no concept of "environment"; where to turn it on is the host's call.
  • Read-only by construction: SELECT only, no DDL or DML.

Quick usage

Postgres

Rust / Axum
// cargo add ashurbanipal-axum
use ashurbanipal_axum::{Config, PgPoolSource};
app.merge(ashurbanipal_axum::router(config, vec![("primary".to_string(), PgPoolSource::new(pool))]));
Rust / Actix-web
// cargo add ashurbanipal-actix-web
use ashurbanipal_actix_web::{app_state, service, Config, PgPoolSource};
App::new().service(service(app_state(config, vec![("primary".to_string(), PgPoolSource::new(pool))])));
Spring Boot
# implementation("io.github.mt-empty:ashurbanipal-spring-boot-starter:0.4.0")
ashurbanipal:
  enabled: true
  # backend: postgres is the default
Go / net-http
// go get github.com/mt-empty/ashurbanipal/implementations/go-nethttp@latest
source := ashurbanipal.NewPostgresSource(db, timeoutSecs)
viewer := ashurbanipal.Router(cfg, []ashurbanipal.NamedSource{{Name: "primary", Source: source}})
Node / Express
// pnpm install ashurbanipal-node-express
import { createRouter, PostgresSource } from "ashurbanipal-node-express";
const viewer = createRouter(config, [{ name: "primary", source: new PostgresSource(pool) }]);
Python / Flask
# uv add ashurbanipal-flask
from ashurbanipal.db.postgres import PgSource
app.register_blueprint(router(config, [("primary", PgSource(dsn=os.environ["DATABASE_URL"]))]))

MySQL / MariaDB

Rust / Axum
// cargo add ashurbanipal-axum --features mysql
use ashurbanipal_axum::{Config, MySqlSource};
app.merge(ashurbanipal_axum::router(config, vec![("primary".to_string(), MySqlSource::new(pool))]));
Rust / Actix-web
// cargo add ashurbanipal-actix-web --features mysql
use ashurbanipal_actix_web::{app_state, service, Config, MySqlSource};
App::new().service(service(app_state(config, vec![("primary".to_string(), MySqlSource::new(pool))])));
Spring Boot
# implementation("io.github.mt-empty:ashurbanipal-spring-boot-starter:0.4.0")
ashurbanipal:
  enabled: true
  backend: mysql   # covers MariaDB too
Go / net-http
// go get github.com/mt-empty/ashurbanipal/implementations/go-nethttp@latest
// go build -tags mysql
source := ashurbanipal.NewMySQLSource(db, timeoutSecs)
viewer := ashurbanipal.Router(cfg, []ashurbanipal.NamedSource{{Name: "primary", Source: source}})
Node / Express
// pnpm install ashurbanipal-node-express mysql2
import { createRouter } from "ashurbanipal-node-express";
import { MySqlSource } from "ashurbanipal-node-express/dist/src/db/mysql.js";
const viewer = createRouter(config, [{ name: "primary", source: new MySqlSource(pool) }]);
Python / Flask
# uv add ashurbanipal-flask PyMySQL
from ashurbanipal.db.mysql import MySqlSource, connect_kwargs_from_url
app.register_blueprint(router(config, [("primary", MySqlSource(**connect_kwargs_from_url(os.environ["MYSQL_URL"])))]))

SQLite

Rust / Axum
// cargo add ashurbanipal-axum --features sqlite
use ashurbanipal_axum::{Config, SqliteSource};
app.merge(ashurbanipal_axum::router(config, vec![("primary".to_string(), SqliteSource::new(pool))]));
Rust / Actix-web
// cargo add ashurbanipal-actix-web --features sqlite
use ashurbanipal_actix_web::{app_state, service, Config, SqliteSource};
App::new().service(service(app_state(config, vec![("primary".to_string(), SqliteSource::new(pool))])));
Spring Boot
# implementation("io.github.mt-empty:ashurbanipal-spring-boot-starter:0.4.0")
ashurbanipal:
  enabled: true
  backend: sqlite
Go / net-http
// go get github.com/mt-empty/ashurbanipal/implementations/go-nethttp@latest
// go build -tags sqlite
source := ashurbanipal.NewSQLiteSource(db, timeoutSecs)
viewer := ashurbanipal.Router(cfg, []ashurbanipal.NamedSource{{Name: "primary", Source: source}})
Node / Express
// pnpm install ashurbanipal-node-express sqlite3
import { createRouter } from "ashurbanipal-node-express";
import { SqliteSource } from "ashurbanipal-node-express/dist/src/db/sqlite.js";
const viewer = createRouter(config, [{ name: "primary", source: new SqliteSource(new Database("app.db")) }]);
Python / Flask
# uv add ashurbanipal-flask
from ashurbanipal.db.sqlite import SqliteSource
app.register_blueprint(router(config, [("primary", SqliteSource(path="./demo.db"))]))

Once mounted, the viewer is served under /__ashurbanipal (the default mount path; it's implementation-defined, see spec/protocol.md §3).

Configuration

Option Default Purpose
enabled false Master on/off. Absent or malformed config means off.
limits.default_page_size 50 Rows per page when the request doesn't specify.
limits.max_page_size 100 Hard, server-enforced page-size cap.
limits.query_timeout_secs 5 Per-query timeout.
siblings none Other instances to show reachability for; each has name, base_url, health_path.

Implementations

Implementation Package Protocol version Conformance CI
rust/axum ashurbanipal-axum · crates.io 1 rust-axum-conformance
rust/actix-web ashurbanipal-actix-web · crates.io 1 rust-actix-web-conformance
spring-boot-starter io.github.mt-empty:ashurbanipal-spring-boot-starter · Maven Central 1 spring-boot-conformance
go-nethttp github.com/mt-empty/ashurbanipal/implementations/go-nethttp · Go modules 1 go-conformance
node-express ashurbanipal-node-express · npm 1 node-conformance
flask-python ashurbanipal-flask · PyPI 1 flask-conformance

Docs

About

No bullshit web UI for browsing a service's own schemaful databases

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages