Skip to content

playwjj/d1-sql-studio

Repository files navigation

D1 SQL Studio

A management UI and REST API gateway for Cloudflare D1 β€” bringing your edge database to the outside world, built with Vue 3 + Naive UI + TypeScript + Vite.

License Cloudflare Workers Vue 3 TypeScript

πŸ’‘ Motivation

Cloudflare D1 is built to live inside the Cloudflare ecosystem. Data flows in and out through Worker bindings and platform-native APIs β€” which works perfectly for edge applications, but leaves a gap: accessing D1 from the outside world requires writing custom Worker code for every use case.

D1 SQL Studio bridges that gap with a single, self-hosted Worker that exposes your database as a standardized REST API β€” authenticated, paginated, and fully queryable β€” so any external system (scripts, dashboards, data pipelines, BI tools) can interact with D1 in a consistent, predictable way. The management UI is the human-friendly layer on top of that same API.

✨ Features

  • 🎨 Professional UI - Built with Vue 3 + Naive UI for a tooling-grade experience
  • πŸ“Š Table Management - Create, view, and delete tables with a visual builder and SQL editor
  • πŸ“ Data Browser - Browse and manage table data with pagination, sorting, and search
  • ⚑ SQL Query Editor - Execute custom SQL with CodeMirror 6, syntax highlighting, and autocompletion
  • πŸ—‚οΈ Query History - Persistent query history with search and filter
  • πŸ” Secure Authentication - Multi-user API key management with KV storage
  • 🎯 Visual Table Builder - Build table schemas with a GUI β€” real-time SQL preview
  • πŸ”‘ API Key Management - Generate, manage, and revoke API keys through the UI
  • πŸ›‘οΈ Security Hardened - SQL injection protection and identifier validation
  • 🌐 Edge Computing - Runs on Cloudflare Workers for global performance
  • πŸ”Œ REST API - Complete REST API for programmatic access
  • πŸ”€ Multi-Field Sorting - Sort data by multiple columns simultaneously
  • πŸ”— Multi-Table Joins - Structured JOIN queries with RESTful API

πŸ—οΈ Architecture

Backend (Cloudflare Worker)

  • Runtime: Cloudflare Workers
  • Database: Cloudflare D1 (SQLite-based)
  • API Keys Storage: Cloudflare KV
  • Static Assets: Workers Assets
  • Location: src/worker/

Frontend (Vue 3 SPA)

  • Framework: Vue 3 + Composition API
  • UI Library: Naive UI
  • State Management: Pinia
  • Routing: Vue Router
  • Editor: CodeMirror 6
  • Language: TypeScript
  • Bundler: Vite
  • Location: src/ui/
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Cloudflare    β”‚
β”‚   Workers Edge  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Static Assets  β”‚ ← Vite build output
β”‚  (index.html)   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   API Routes    β”‚ ← REST API (/api/*)
β”‚   (/api/*)      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   D1 Database   β”‚ ← SQLite (Data)
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   KV Storage    β”‚ ← API Keys
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“¦ Installation

npm install

πŸ› οΈ Development

Local Development

Start the frontend development server (with HMR):

npm run dev

Access at http://localhost:5173

Start the Worker development server (in a separate terminal):

npm run dev:worker

Worker runs at http://localhost:8787

The frontend proxies /api/* requests to the Worker automatically.

Database Setup

Option 1: Configure in wrangler.toml (Local Development)

  1. Create D1 database:
npx wrangler d1 create d1-sql-studio-db
  1. Copy the database_id from the output and update wrangler.toml:
[[d1_databases]]
binding = "DB"
database_name = "d1-sql-studio-db"
database_id = "your-database-id-here"
  1. Restart the dev server

Option 2: Cloudflare Dashboard (Production, Recommended)

  1. Deploy your Worker
  2. Go to Cloudflare Dashboard β†’ Workers & Pages β†’ Your Worker β†’ Settings β†’ Bindings
  3. Add D1 binding: Variable name = DB, select/create your database
  4. Save and wait a few seconds

API Keys Setup

API keys are managed through the UI and stored in Cloudflare KV.

First-time setup:

  1. Deploy the app (see below)
  2. Visit your app URL β€” you'll see the first-time setup screen
  3. Create your first API key
  4. Save the generated key β€” it's only shown once
  5. Log in with your new key

πŸ—οΈ Build

# Build frontend only
npm run build:ui

# Type-check worker
npm run build:worker

# Full build (frontend + type check)
npm run build

πŸš€ Deployment

Prerequisites

1. Create D1 Database

npx wrangler d1 create d1-sql-studio

Copy the database_id and update wrangler.toml:

[[d1_databases]]
binding = "DB"
database_name = "d1-sql-studio"
database_id = "your-database-id-here"

2. Create KV Namespace for API Keys

npx wrangler kv:namespace create "API_KEYS"

Copy the id and update wrangler.toml:

[[kv_namespaces]]
binding = "API_KEYS"
id = "your-kv-id-here"

Deploy

npm run deploy

This builds the frontend and deploys to Cloudflare Workers.

First-Time Setup

  1. Visit your Worker URL
  2. See the First-Time Setup screen
  3. Enter a name for your first API key and click Generate API Key
  4. Copy and save the key securely β€” it won't be shown again
  5. Click Continue to Dashboard

πŸ“ Project Structure

d1-sql-studio/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ worker/                  # Backend (Cloudflare Worker)
β”‚   β”‚   β”œβ”€β”€ index.ts            # Worker entry point
β”‚   β”‚   β”œβ”€β”€ router.ts           # API routing
β”‚   β”‚   β”œβ”€β”€ db.ts               # D1 database operations
β”‚   β”‚   β”œβ”€β”€ auth.ts             # Authentication
β”‚   β”‚   β”œβ”€β”€ apikeys.ts          # API key management (KV)
β”‚   β”‚   β”œβ”€β”€ security.ts         # Security validation
β”‚   β”‚   └── types.ts            # TypeScript types
β”‚   └── ui/                      # Frontend (Vue 3 SPA)
β”‚       β”œβ”€β”€ main.ts             # App entry point
β”‚       β”œβ”€β”€ App.vue             # Root component (Naive UI providers)
β”‚       β”œβ”€β”€ env.d.ts            # Vue module declarations
β”‚       β”œβ”€β”€ router/
β”‚       β”‚   └── index.ts        # Vue Router + navigation guards
β”‚       β”œβ”€β”€ stores/
β”‚       β”‚   β”œβ”€β”€ auth.ts         # Auth state (apiKey, login/logout)
β”‚       β”‚   β”œβ”€β”€ tables.ts       # Table list + selected table
β”‚       β”‚   └── notification.ts # showToast / showConfirm
β”‚       β”œβ”€β”€ composables/
β”‚       β”‚   β”œβ”€β”€ useTableSchema.ts  # Schema loading with 5-min cache
β”‚       β”‚   └── useExport.ts       # CSV / JSON / SQL export
β”‚       β”œβ”€β”€ layouts/
β”‚       β”‚   └── DashboardLayout.vue  # Sidebar + main content layout
β”‚       β”œβ”€β”€ views/
β”‚       β”‚   β”œβ”€β”€ auth/
β”‚       β”‚   β”‚   β”œβ”€β”€ LoginView.vue
β”‚       β”‚   β”‚   └── FirstTimeSetupView.vue
β”‚       β”‚   β”œβ”€β”€ tables/TablesView.vue
β”‚       β”‚   β”œβ”€β”€ data/DataBrowserView.vue
β”‚       β”‚   β”œβ”€β”€ query/QueryEditorView.vue
β”‚       β”‚   └── keys/ApiKeysView.vue
β”‚       β”œβ”€β”€ components/
β”‚       β”‚   β”œβ”€β”€ tables/
β”‚       β”‚   β”‚   β”œβ”€β”€ CreateTableModal.vue
β”‚       β”‚   β”‚   β”œβ”€β”€ VisualTableBuilder.vue  # GUI builder + SQL preview
β”‚       β”‚   β”‚   └── EditTableModal.vue       # Columns / Rename / Indexes tabs
β”‚       β”‚   β”œβ”€β”€ data-browser/
β”‚       β”‚   β”‚   β”œβ”€β”€ AddRowModal.vue
β”‚       β”‚   β”‚   β”œβ”€β”€ EditRowModal.vue
β”‚       β”‚   β”‚   └── ApiDocumentation.vue     # Collapsible REST API reference
β”‚       β”‚   β”œβ”€β”€ query-editor/
β”‚       β”‚   β”‚   β”œβ”€β”€ SqlEditor.vue            # CodeMirror 6 wrapper
β”‚       β”‚   β”‚   β”œβ”€β”€ ResultsTable.vue
β”‚       β”‚   β”‚   β”œβ”€β”€ QueryHistory.vue
β”‚       β”‚   β”‚   └── KeyboardShortcuts.vue
β”‚       β”‚   └── shared/
β”‚       β”‚       └── NullValue.vue
β”‚       └── lib/                 # Pure TypeScript utilities (no framework deps)
β”‚           β”œβ”€β”€ api.ts          # ApiClient class
β”‚           β”œβ”€β”€ exportUtils.ts  # CSV / JSON / SQL export
β”‚           β”œβ”€β”€ queryHistory.ts # localStorage query history
β”‚           └── utils.ts
β”œβ”€β”€ public/                      # Static assets
β”œβ”€β”€ dist/                        # Build output
β”œβ”€β”€ index.html                  # HTML template
β”œβ”€β”€ vite.config.ts              # Vite configuration
β”œβ”€β”€ tsconfig.json               # TypeScript config (UI)
β”œβ”€β”€ tsconfig.worker.json        # TypeScript config (Worker)
β”œβ”€β”€ wrangler.toml               # Cloudflare Workers config
└── package.json

πŸ”§ API Usage

All API endpoints require the Authorization header:

Authorization: Bearer your-api-key

List Tables

GET /api/tables

Get Table Schema

GET /api/tables/:tableName/schema

Get Table Data

GET /api/tables/:tableName/rows?page=1&limit=50&sortBy=id&sortOrder=asc&search=keyword

Query Parameters:

  • page β€” Page number (default: 1)
  • limit β€” Rows per page (default: 50, max: 1000)
  • sortBy β€” Column to sort by (single field)
  • sortOrder β€” asc or desc (default: asc)
  • sort β€” Multi-field sort, format: field1:order1,field2:order2
  • search β€” Keyword search across all TEXT columns

Multi-field sort examples:

# Sort by name ascending, then created_at descending
GET /api/tables/users/rows?sort=name:asc,created_at:desc

Execute SQL Query

POST /api/query
Content-Type: application/json

{
  "sql": "SELECT * FROM users WHERE created_at > ? LIMIT ?",
  "params": ["2024-01-01", 10]
}

Allowed statements: SELECT, INSERT, UPDATE, DELETE, PRAGMA

Security: parameterized queries, multiple statements blocked, DDL not allowed via this endpoint.

Multi-Table Join Query

POST /api/join
Content-Type: application/json

{
  "baseTable": "users",
  "joins": [
    { "table": "orders", "type": "LEFT", "on": "users.id = orders.user_id" }
  ],
  "select": ["users.*", "COUNT(orders.id) as order_count"],
  "where": "users.created_at > ?",
  "groupBy": ["users.id"],
  "orderBy": "order_count DESC",
  "limit": 20,
  "params": ["2024-01-01"]
}
Parameter Type Required Description
baseTable string βœ… Base table name
joins JoinConfig[] βœ… JOIN configurations (1–10 joins)
select string[] ❌ Columns to select (default: ["*"])
where string ❌ WHERE clause with ? placeholders
groupBy string[] ❌ GROUP BY columns
having string ❌ HAVING clause
orderBy string ❌ ORDER BY clause
limit number ❌ Max records (max: 1000)
offset number ❌ Pagination offset
params any[] ❌ Values for WHERE/HAVING

Table CRUD

POST   /api/tables                      # Create table (body: { sql })
DELETE /api/tables/:tableName           # Drop table
POST   /api/tables/:tableName/rows      # Insert row
PUT    /api/tables/:tableName/rows/:id  # Update row
DELETE /api/tables/:tableName/rows/:id  # Delete row

Column Management

POST   /api/tables/:tableName/columns/:columnName   # Add column
PUT    /api/tables/:tableName/columns/:columnName   # Rename column
DELETE /api/tables/:tableName/columns/:columnName   # Drop column
PUT    /api/tables/:tableName/rename                # Rename table

Index Management

GET    /api/tables/:tableName/indexes                     # List indexes
GET    /api/tables/:tableName/indexes/:indexName/columns  # Index columns
POST   /api/tables/:tableName/indexes                     # Create index
DELETE /api/tables/:tableName/indexes/:indexName          # Drop index

API Key Management

GET    /api/keys          # List keys
POST   /api/keys          # Create key (body: { name, description? })
DELETE /api/keys/:id      # Delete key
GET    /api/keys/status   # Check if any keys exist (unauthenticated)

πŸ“ Scripts Reference

Command Description
npm run dev Start frontend dev server (port 5173)
npm run dev:worker Start Worker dev server (port 8787)
npm run build Full build (UI + type check)
npm run build:ui Build frontend only
npm run build:worker Type-check Worker
npm run preview Preview production build
npm run deploy Build UI + deploy to Cloudflare

🎯 Tech Stack

Frontend:

  • 🟒 Vue 3 β€” Composition API
  • 🎨 Naive UI β€” Professional component library
  • 🍍 Pinia β€” State management
  • πŸ”€ Vue Router β€” Client-side routing
  • πŸ“ CodeMirror 6 β€” SQL editor with syntax highlighting
  • πŸ“˜ TypeScript 5
  • ⚑ Vite 5

Backend:

  • ☁️ Cloudflare Workers β€” Edge computing
  • πŸ—„οΈ Cloudflare D1 β€” SQLite database
  • πŸ“¦ Workers Assets β€” Static asset serving

Tools:

  • πŸ”¨ Wrangler β€” Cloudflare CLI
  • πŸ“¦ npm

πŸ”’ Security Features

  • API Key Management β€” Multi-user support, bcrypt-hashed storage in KV
  • SQL Injection Protection β€” Identifier validation with strict regex
  • Query Whitelisting β€” Only SELECT/INSERT/UPDATE/DELETE/PRAGMA via /api/query
  • Identifier Quoting β€” All SQL identifiers automatically quoted
  • Same-Origin CORS β€” API restricted to same-origin requests by default
  • Session Persistence β€” Auto-login from localStorage, cleared on logout

πŸ› Troubleshooting

"DATABASE_NOT_BOUND" Error

  1. Create D1 database: npx wrangler d1 create d1-sql-studio
  2. Update database_id in wrangler.toml
  3. Redeploy: npm run deploy

First-Time Setup Screen Not Appearing

  1. Create KV namespace: npx wrangler kv:namespace create "API_KEYS"
  2. Update wrangler.toml with namespace ID
  3. Redeploy and clear browser cache

Build Errors

rm -rf node_modules package-lock.json
npm install
npm run build

TypeScript Errors

tsc --noEmit                           # Frontend
tsc --project tsconfig.worker.json    # Worker

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“„ License

MIT License β€” see LICENSE for details.

πŸ™ Acknowledgments


Star ⭐ this repo if you find it useful!

About

A modern database management tool for Cloudflare D1, featuring a beautiful Web UI (similar to phpMyAdmin) and a complete REST API for database operations.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors