High-Level Architecture - LibreDB Studio
August 4, 2026 · View on GitHub
This document outlines the architectural patterns, tech stack, and system design for LibreDB Studio, a web-based SQL IDE for cloud-native teams.
System Overview
LibreDB Studio is a hybrid, cloud-native database management tool that provides an IDE-like experience in the browser. It supports 11 database backends via a Strategy Pattern abstraction: PostgreSQL, MySQL, SQLite, Oracle, SQL Server, MongoDB, Couchbase, ClickHouse, Apache Druid, Redis, LibreDB.
It runs in two modes: as a standalone Next.js app and as an embedded npm package (@libredb/studio) consumed by libredb-platform. See §4.6.
1. Core Tech Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) with React 19 |
| Runtime | Bun / Node.js |
| Language | TypeScript (strict mode) |
| Styling | Tailwind CSS 4 + Shadcn/UI |
| Animations | Framer Motion v12 |
| SQL Editor | Monaco Editor |
| Data Grid | TanStack React Table + react-virtual |
| AI | Multi-model (Gemini, OpenAI, Ollama, Custom) |
| Auth | JWT (jose) + OIDC SSO (openid-client) |
| Charts | Recharts |
| Containerization | Docker (multi-stage Bun build) |
2. High-Level Architecture Diagram
graph TD
User((User)) -->|HTTPS| Frontend[Next.js Frontend<br/>App Router + React 19]
Frontend -->|API Calls| API[Next.js API Routes<br/>src/app/api]
subgraph "Application Core"
API -->|Auth| AuthLib[src/lib/auth.ts<br/>JWT + OIDC]
API -->|Query| DBFactory[Provider Factory<br/>src/lib/db/factory.ts]
API -->|AI| LLMFactory[LLM Factory<br/>src/lib/llm/]
end
subgraph "Database Providers (Strategy Pattern)"
DBFactory --> SQL[SQL Providers]
DBFactory --> Document[Document Providers]
DBFactory --> KeyValue[Key-Value Providers]
SQL --> PG[(PostgreSQL)]
SQL --> MySQL[(MySQL)]
SQL --> SQLite[(SQLite)]
SQL --> Oracle[(Oracle)]
SQL --> MSSQL[(SQL Server)]
SQL --> ClickHouse[(ClickHouse)]
SQL --> Druid[(Apache Druid)]
Document --> MongoDB[(MongoDB)]
Document --> Couchbase[(Couchbase)]
KeyValue --> Redis[(Redis)]
end
subgraph "AI Providers (Strategy Pattern)"
LLMFactory --> Gemini[[Gemini]]
LLMFactory --> OpenAI[[OpenAI]]
LLMFactory --> Ollama[[Ollama]]
LLMFactory --> CustomLLM[[Custom]]
end
subgraph "Security"
AuthLib -->|Session| JWT[HTTP-Only JWT Cookies]
AuthLib -->|SSO| OIDC[OIDC Provider<br/>Auth0 / Keycloak / Okta / Azure AD]
end
3. Database Provider Architecture
classDiagram
class BaseDatabaseProvider {
<<abstract>>
+connect()
+disconnect()
+executeQuery()
+getSchema()
+getHealth()
+getCapabilities() ProviderCapabilities
+getLabels() ProviderLabels
+prepareQuery() PreparedQuery
}
class SQLBaseProvider {
<<abstract>>
+beginTransaction()
+commitTransaction()
+rollbackTransaction()
+cancelQuery()
}
BaseDatabaseProvider <|-- SQLBaseProvider
BaseDatabaseProvider <|-- MongoDBProvider
BaseDatabaseProvider <|-- CouchbaseProvider
BaseDatabaseProvider <|-- RedisProvider
SQLBaseProvider <|-- PostgresProvider
SQLBaseProvider <|-- MySQLProvider
SQLBaseProvider <|-- SQLiteProvider
SQLBaseProvider <|-- OracleProvider
SQLBaseProvider <|-- MSSQLProvider
SQLBaseProvider <|-- ClickHouseProvider
SQLBaseProvider <|-- DruidProvider
Each provider implements:
getCapabilities()- queryLanguage, supportsExplain, supportsCreateTable, maintenanceOperations, etc.getLabels()- entityName, selectAction, searchPlaceholder, etc. (drives all UI text)prepareQuery()- handles query limiting per-provider (SQL LIMIT injection vs MongoDB native)
Adding a new database type requires: 1 provider class + 1 entry in db-ui-config.ts.
CouchbaseProvider extends BaseDatabaseProvider even though SQL++ is a SQL dialect: SQL++ quotes identifiers with doubled backticks, which escapeIdentifier() produces for no existing type, so it owns its quoting and declares its SQL-ness through queryLanguage: 'sql' instead. Being reached over HTTP is not the reason — ClickHouseProvider and DruidProvider add no driver either, and both extend SQLBaseProvider, because double-quoted identifiers and LIMIT n OFFSET m are correct in both dialects. Each of the three is a directory rather than a single file, with its wire format behind a transport seam that provider logic never bypasses. See docs/providers/couchbase.md, clickhouse.md and druid.md.
4. Key Architectural Patterns
4.1. Strategy Pattern (Database & LLM)
Both database and LLM layers use the Strategy Pattern with a factory:
src/lib/db/factory.ts- Creates the correct database provider based on connection typesrc/lib/llm/factory.ts- Creates the correct LLM provider based on configuration
No isMongoDB / === 'mongodb' checks outside provider classes. All behavior differences are driven through capabilities and labels.
4.2. Authentication Flow
sequenceDiagram
participant U as User
participant F as Frontend
participant A as API (/api/auth)
participant O as OIDC Provider
alt Local Auth
U->>F: Email + Password
F->>A: POST /api/auth/login
A->>F: Set HTTP-Only JWT Cookie
else OIDC SSO
U->>F: Click SSO Login
F->>O: Redirect (PKCE)
O->>F: Authorization Code
F->>A: GET /api/auth/oidc/callback
A->>O: Token Exchange
A->>F: Set HTTP-Only JWT Cookie
end
Controlled by NEXT_PUBLIC_AUTH_PROVIDER (local | oidc). Both flows result in the same JWT session cookie. Proxy (src/proxy.ts) enforces RBAC (admin vs user roles).
4.3. Multi-Statement Execution
src/lib/sql/statement-splitter.ts splits SQL input into individual statements, handling:
- String literals (single/double quotes)
- Block and line comments
- Dollar-quoting (PostgreSQL)
Multi-statement queries execute sequentially via POST /api/db/multi-query.
4.4. Storage Abstraction Layer
- Write-through cache architecture: localStorage (L1 cache) + optional server storage (L2 persistent)
- Three storage modes controlled by
STORAGE_PROVIDERenv var:local(default): Browser localStorage only, zero configurationsqlite: Server-side SQLite file viabetter-sqlite3postgres: Server-side PostgreSQL viapg
useStorageSynchook in Studio.tsx: discovers mode at runtime via/api/storage/config, pulls on mount, pushes mutations (debounced 500ms)- Migration: First login auto-migrates localStorage to server;
libredb_server_migratedflag prevents re-migration - Graceful degradation: If server unreachable, localStorage continues working
4.5. Client State Management
- Storage module (
src/lib/storage/) for persistent data: connections, query history, saved queries, schema snapshots, chart configs, audit log, masking config, threshold config - React hooks for UI state: tabs, active connection, execution status
- Custom hooks extracted from Studio.tsx:
useAuth,useConnectionManager,useTabManager,useTransactionControl,useQueryExecution,useInlineEditing
4.6. Workspace Abstraction (npm package embedding)
Studio ships both as a standalone app and as the @libredb/studio npm package consumed by libredb-platform (built with tsup via build:lib).
src/workspace/—StudioWorkspace.tsxis the embeddable shell. Its adapter hooks (hooks/use-connection-adapter,hooks/use-query-adapter) let the host (standalone or platform) supply connections and query execution, so the same UI runs in both contexts.src/exports/— barrel modules (components.ts,providers.ts,workspace.ts,types.ts) that define the package's public surface;package.jsonexports/main/modulepoint at the tsupdist/output.- Platform integration rules (Tailwind tokens, Lucide stroke widths, chunk scanning) live in
CLAUDE.md.
4.7. Standalone Boot Flow (src/instrumentation.ts)
Next.js runs register() once per server worker, only when Studio boots its own server (never when @libredb/studio is imported by libredb-platform) and only on the Node.js runtime. On standalone boot it:
- Bootstraps missing auth env (
src/lib/auth-bootstrap.ts, #109). WhenJWT_SECRET/ADMIN_PASSWORDare absent they are generated once, persisted to<data dir>/auth-bootstrap.json(mode0600), and injected intoprocess.envbefore any secret reader runs; the admin password is printed once. Explicitly set env vars always win. Disable withAUTH_BOOTSTRAP=off|false|0(case-insensitive); an unrecognized value warns and stays on. In OIDC mode only the JWT secret is generated. - Runs the auth-config preflight (
src/lib/config/auth-preflight.ts, #227). AJWT_SECRETthat is set but shorter than 32 characters prints an operator-facing banner (length only, never the value) and exits with code 1. It runs after bootstrap so a generated secret is validated too. This is the one step that intentionally stops boot:GET /api/db/healthis the Kubernetes livenessProbe and the Docker/PaaS health check, so signalling the failure there would restart the pod forever and hide the login screen's actionable 503; refusing to start costs nothing because a too-short secret can sign no session at all. - Seeds the embedded LibreDB sample (
src/lib/seed/libredb-sample.ts). UnlessLIBREDB_EMBEDDED_SAMPLE=false, it creates<data dir>/sample.libredb(idempotently, atomic rename) andGET /api/connections/managedthen advertises an editable, dismissable "Sample (LibreDB)" connection pointing at it. - Seeds the embedded SQLite sample, asynchronously (
src/lib/seed/sqlite-sample.ts). UnlessSQLITE_EMBEDDED_SAMPLE=false, it fires-and-forgets a copy of the vendoredseed-assets/sqlite/employee.dbtemplate to<data dir>/sample-employees.db(idempotent, atomic rename) — boot never waits. While the copy is in flight the managed-connections API advertises the seed id inpendingSeeds;useConnectionManagerpolls (1s, max 30) so "Sample (Employees)" appears without a page refresh.
Failures in the bootstrap and seeding steps are logged and swallowed — boot never breaks. The preflight in step 2 is the deliberate exception.
4.8. SQLite Driver Selection (src/lib/db/providers/sql/sqlite-driver.ts)
The SQLite DB provider is runtime-adaptive: it loads bun:sqlite under Bun and node:sqlite under plain Node (npx / brew / deb installs run node server.js). LIBREDB_SQLITE_DRIVER=bun|node forces a driver (used by tests). This is distinct from the storage layer, whose SQLite backend uses better-sqlite3.
5. Directory Structure
src/
├── app/ # Next.js App Router
│ ├── api/
│ │ ├── auth/ # Login/logout/me + OIDC (PKCE, callback)
│ │ ├── ai/ # chat, nl2sql, explain, query-safety, index-advisor, impact, describe-schema, autopilot
│ │ ├── db/ # Query, schema, health, maintenance, transactions
│ │ ├── storage/ # Storage sync API (config, CRUD, migrate)
│ │ ├── connections/ # managed/ — built-in (seeded) connections listing
│ │ └── admin/ # Fleet health, audit
│ ├── admin/ # Admin dashboard (RBAC protected) — layout.tsx renders the
│ │ │ # shell; one route per section, each independently
│ │ │ # linkable/refreshable. `/admin` redirects to the default
│ │ │ # section and maps legacy `?tab=` links (src/lib/admin-sections.ts)
│ │ ├── overview/ # Fleet health, quick actions
│ │ ├── operations/ # Maintenance operations
│ │ ├── monitoring/ # Embedded monitoring dashboard
│ │ ├── security/ # Data masking, access control
│ │ └── audit/ # Audit log
│ ├── monitoring/ # Monitoring dashboard page
│ └── login/ # Login page
├── components/
│ ├── Studio.tsx # Main application shell (standalone)
│ ├── QueryEditor.tsx # Monaco SQL editor wrapper
│ ├── ResultsGrid.tsx # Virtualized data grid
│ ├── SchemaDiagram.tsx # React Flow ERD viewer
│ ├── sidebar/ # ConnectionsList, ConnectionItem
│ ├── studio/ # StudioTabBar, QueryToolbar, BottomPanel
│ ├── results-grid/ # ResultCard, RowDetailSheet, StatsBar
│ ├── admin/ # AdminDashboard shell (5 section routes) + tabs/ panels
│ ├── monitoring/ # MonitoringDashboard + tabs
│ ├── schema-explorer/ # SchemaExplorer
│ └── ui/ # Shadcn/UI primitives
├── workspace/ # Embeddable shell (StudioWorkspace) + host adapter hooks
├── exports/ # Public npm-package barrel exports (tsup build:lib)
├── hooks/ # Custom React hooks
└── lib/
├── db/ # Database provider module
│ ├── providers/
│ │ ├── sql/ # postgres, mysql, sqlite (+ sqlite-driver runtime adapter), oracle, mssql, clickhouse/ (transport seam + SQL over HTTP), druid/ (transport seam + SQL over POST /druid/v2/sql)
│ │ ├── document/ # mongodb, couchbase/ (transport seam + SQL++ over REST)
│ │ ├── keyvalue/ # redis
│ │ └── embedded/ # libredb (built-in embedded provider for the sample connection)
│ ├── factory.ts # Provider factory
│ └── types.ts # Database types
├── llm/ # LLM provider module
├── editor/ # Monaco completions (SQL + MongoDB)
├── schema-diff/ # Diff engine + migration SQL generator
├── sql/ # Statement splitter, alias extractor
├── seed/ # Seed connections (config, filter, credential resolver) + libredb-sample seeding
├── config/ # auth-env.ts — single JWT_SECRET reader (auth.ts, proxy.ts, oidc.ts)
├── api/ # API error codes + schema-route helpers
├── ssh/ # SSH tunnel support
├── auth.ts # JWT utilities
├── auth-bootstrap.ts # Zero-config first-run auth bootstrap (runs in instrumentation)
├── oidc.ts # OIDC utilities
└── storage/ # Storage abstraction layer
├── index.ts # Barrel export
├── storage-facade.ts # Public sync API + CustomEvent dispatch
├── local-storage.ts # Pure localStorage CRUD
├── factory.ts # Env-based provider factory
└── providers/ # SQLite + PostgreSQL backends
6. Deployment
- Docker / Helm: Multi-stage Bun build with standalone Next.js output; these channels bind
0.0.0.0. Canonical imageghcr.io/libredb/libredb-studio. - Native channels (
bin/studio.jsnpx launcher, Homebrew tap,.deb/.rpm, Snap, standalone tarballs; sources underbin/andpackaging/): local-first, bind127.0.0.1by default unless--host/HOSTNAMEopts in. The npx launcher ships as a pure library and downloads the SHA256-verified standalone server tarball from GitHub Releases. Full matrix and per-channel details indocs/DISTRIBUTION.md. - Health Check:
GET /api/db/health - Stateless API: API routes are stateless, suitable for horizontal scaling
- Environment: Configured via
.env.local(see CLAUDE.md for full variable list). Missing auth secrets are generated on first standalone boot — see §4.7.