Moogo

Moogo

A lightweight database for serverless applications. Every project gets its own SQLite file and its own key, and you talk to it over HTTP — no connection string, no driver, no connection pool to manage. Object storage lives under the same project, behind its own credential.

This repository is the whole service: a Go API, the React dashboard that it serves from the same origin, and the small news service behind the dashboard's update feed.

What you get

What Moogo is for

Moogo is built for applications that want simplicity, isolation, and speed over raw scale.

Use caseWhy Moogo fits
Serverless functions / Edge workersNo connection pool, no driver, no cold start. Just POST /query with a key.
Small SaaS / Multi-tenant appsOne SQLite file per project = structural isolation. No RLS policies to get wrong.
AI agents / LLM toolsNative MCP server (cmd/mcp) exposes SQL + object storage as tools. No HTTP client to write.
Internal tools / Admin panelsDashboard included — table browser, SQL console, schema visualizer, bucket browser.
Prototypes → ProductionSame API from day one. No migration to "real" database needed.
Apps ≤ 256 MB data / projectHonest ceiling, reported in every response.

The pattern: a few tables, a handful of queries, a few MB of files, and you want zero operational overhead.

What Moogo is not for

You needUse instead
PostGIS / spatial queriesPostGIS on Postgres
Cross-region read replicasManaged Postgres (Supabase, Neon, RDS)
Concurrent analytical scans (OLAP)ClickHouse, DuckDB, BigQuery
Database > 256 MB per projectPostgres, MySQL, or a larger SQLite host
Row-level security across tenantsYou get file isolation — RLS not needed
GRANT/REVOKE, multiple DB rolesMoogo has one key per project
Triggers, ATTACH, load_extensionNot supported (security design)
Self-hosting (this version)Planned for future release

The trade-off is intentional: Moogo optimizes for the 80% of apps that don't need Postgres features, giving them structural isolation, zero-driver integration, and honest limits.

What is in here

The two planes share no tables. A client on the data plane never learns whether a project exists on the control plane; a signed-in user never holds a secret they did not ask for.

Build

The binary embeds the built frontend, so the frontend has to exist first. In a fresh clone web/dist is committed, so this is only needed after changing web/ui:

cd web/ui && npm install && npm run build && cd ../..
go build ./cmd/api
go build ./cmd/mcp   # optional: the MCP server for coding agents
go build ./cmd/moogo # optional: the command line client

Install CLI (binary)

The fastest way, no Go required:

curl -fsSL https://get.moogo.dev | sh

Or build from source (requires Go): Frontend scripts (in web/ui):

ScriptWhat it does
npm run devVite dev server — serves files and proxies /auth and /api to the Go API, which still runs separately
npm run buildtsc -b && vite build → web/dist, embedded by go build
npm run lintoxlint
npm run verify:docsrenders every docs page outside a browser and checks links

Run it against a Postgres instance and a data directory:

export MOOGO_DATABASE_URL='postgres://user:pass@localhost:5432/moogo?sslmode=disable'
export MOOGO_SESSION_SECRET="$(openssl rand -base64 32)"
export MOOGO_DATA_DIR=./data

./api

dev.sh exports the local defaults and starts the API if you would rather not do it by hand, along with the Postgres it uses. It is development only — the session secret it defaults to is published in this repository, and production refuses to start with it.

./dev.sh              # the "dev" environment: localhost:8080
./dev.sh staging      # a second one, running beside it

An environment is one self-contained set of state — its own Postgres cluster and database, its own directory of project files, its own ports — living under ~/.moogo-dev/ (~/.moogo-dev/<name>/ for a named one). The first run creates it; later runs reuse it. That is what makes a scratch environment safe to create, reset or leave broken without touching the one you work in every day. Ports are derived from the name and printed at start, so the same environment lands on the same ports every time; MOOGO_HTTP_PORT, MOOGO_PGPORT, MOOGO_DATA_DIR, MOOGO_DATABASE_URL and the rest still override any of it.

MOOGO_ENV accepts exactly development, test or production. It is the switch the production rules hang on, so a value the code does not branch on stops the server at startup instead of quietly running with the development ones.

Every other setting has a default; internal/config/config.go is the list.

The one rule

EndpointAcceptsRejects
/queryReads — SELECT, VALUES, PRAGMA, EXPLAIN, WITH that selectsWrites, with not_a_read
/execWrites — INSERT, UPDATE, DELETE, CREATE, ALTER, DROPReads, with not_a_write

Sending the wrong kind of statement is rejected rather than quietly accepted, so you find out immediately instead of after it has written something.

Documentation

Tests

# Unit tests and race detector; database integration tests skip themselves.
go test -race ./...

# Full suite, integration included — needs a scratch Postgres.
MOOGO_TEST_DATABASE_URL='postgres://user:pass@localhost:5432/moogo_test?sslmode=disable' \
  go test -race ./...

Support Moogo — help, sponsor, fund

Moogo is free, open source (MIT), and needs help to keep going. If it is useful to you, here is how to give back — all of it matters:

Sponsors get listed in the repository (with permission) and early access to the roadmap discussions.

License

MIT — see LICENSE. Use it, change it, sell it; keep the notice.