Introduction
This year, I participated in Kiroween, and for this hackathon, I built SheetDB—a project that transforms Google Sheets into a fully operational relational database with SQL capabilities, multiple tables, relationships, and performance that's 500-1000x faster than making direct API calls to Google Sheets to achieve a similar goal.
You might wonder: why use Google Sheets as a database when there are fully-featured database solutions like PostgreSQL, MySQL, or managed services like Supabase, Firebase, and Appwrite? The answer is simple: because it is fun to hack something like this and a lot useful at the same time and more importantly, because I can
The Benefits of Using Google Sheets as a Database
- Zero Hosting Costs - Leverage Google's infrastructure for free, with near-unlimited bandwidth and reliability
- Built-in Admin Interface - Google Sheets provides a familiar, powerful UI that everyone already knows how to use
- Instant Setup - No database installation, no server configuration, no DevOps—just publish your sheet and start querying
- Real-time Collaboration - Multiple users can edit data simultaneously with Google's built-in collaboration features
- Version History - Every change is tracked automatically, providing a built-in audit trail
- Unlimited Writes via Forms - Use Google Forms for data collection without consuming API quota
The SheetDB Ecosystem
SheetDB is now a comprehensive ecosystem with three main components:
- SheetDB Core - Python library and CLI tool providing the core database functionality
- sheetdb-js - JavaScript/TypeScript client with React hooks for frontend integration
- sheetdb-server - FastAPI REST API server that exposes SheetDB to any language or client
The Inspiration
There have been several attempts at using Google Sheets as a database for simple MVPs and CRUD applications. The most notable is Retrosheet, which elegantly turns Google Sheets into a single-table database and which was the main inspiration to this project(all credits to theapache64). It's brilliant for quick prototypes, but as I had more ambitious project in my mind on top of this which addresses following issues:
- No real database queries - Couldn't write SQL or perform complex filtering
- Single table only - All data had to live in one sheet with no relationships
- No relational data - Impossible to model users → posts, products → orders, or any other relationships
- Slow performance - Every query hits the Google Sheets API directly (~500ms per query)
- Rate limits - Limited to 100 requests per 100 seconds
- Limited flexibility - Rigid architecture with no room for customisation
I needed something more powerful, more flexible, but still free and easy to deploy.
That's when I decided to build SheetDB to fill exactly those gaps.
What SheetDB Does: A Complete Solution
SheetDB transforms Google Sheets into a full-featured relational database with all the power of traditional databases, but zero hosting costs.
Core Features
🗄️ True Relational Database
- Multiple tables with relationships - Define foreign keys and join data across sheets
- Full SQL support - JOINs, aggregations, subqueries, WHERE clauses, GROUP BY, ORDER BY
- Schema validation - Type checking and constraint enforcement
- Primary keys and referential integrity - Maintain data consistency
# Complex joins across multiple tables
hunts = db.query("""
SELECT h.hunt_date, m.name as monster, hu.name as hunter, h.success
FROM hunts h
JOIN monsters m ON h.monster_id = m.id
JOIN hunters hu ON h.hunter_id = hu.id
WHERE h.success = true
ORDER BY h.hunt_date DESC
""")
⚡ 500x Performance Boost
The secret sauce: DuckDB caching layer
- Sync data once from Google Sheets into in-memory DuckDB
- All queries run against the cache (~1ms vs ~500ms)
- Smart sync strategies: aggressive, lazy, scheduled, or manual
Performance Comparison:
| Operation | Direct API (Retrosheet) | SheetDB (Cached) | Speedup |
|---|---|---|---|
| Single query | 500ms | 1ms | 500x |
| 100 queries | 50 seconds | 0.1 seconds | 500x |
| Join query | Not possible | 1ms | ∞ |
# One sync, unlimited fast queries
db.sync() # ~2 seconds (one-time cost)
for i in range(1000):
data = db.query('SELECT * FROM monsters') # ~1ms each!
# Total: ~2 seconds vs Retrosheet's ~500 seconds
🎯 Three Flexible Operating Modes
1. Public Mode (No Authentication)
- Read from published Google Sheets without any credentials
- Unlimited reads from public CSV exports
- Optional unlimited writes via Google Forms
- Perfect for prototypes and public data
2. Authenticated Mode (Full CRUD)
- Full create, read, update, delete operations
- Private data support with service account authentication
- Complete control over all operations
3. Hybrid Mode (Best of Both) ⚡ Recommended
- Public reads (unlimited, fast)
- Authenticated writes (controlled)
- Optimal performance for production apps
# Public mode - zero setup!
db = SheetDB.from_public_url("https://docs.google.com/spreadsheets/d/YOUR_ID/")
# Authenticated mode - full control
db = SheetDB(credentials="creds.json", spreadsheet_id="YOUR_ID")
# Hybrid mode - best of both worlds
db = SheetDB(
sheet_url="https://...",
credentials="creds.json",
mode="hybrid"
)
📝 Multiple Read/Write Options
For Reads:
- Direct SQL queries with DuckDB
- Django-like ORM for Python
- REST API for any frontend
- JavaScript client with React hooks
For Writes:
- Direct API writes (authenticated mode)
- Google Forms integration (unlimited writes, no API quota!)
- Automated form creation via Forms API
# Unlimited writes via Google Forms (no API quota!)
form_url = db.enable_forms_writer('monsters', auto_create=True)
for i in range(1000): # Write 1000 records - no limits!
db.insert_via_form('monsters', {
'name': f'Monster_{i}',
'danger_level': (i % 10) + 1
})
🤖 Everything Automated
- Auto-generate schemas from existing sheets
- Auto-create sheets from schema definitions
- Auto-create forms for data entry
- Auto-sync data on schedule
- Auto-validate against schema
# Generate schema from existing sheets
sheetdb schema generate --credentials creds.json --spreadsheet-id YOUR_ID
# Create sheets from schema
sheetdb sheets create --schema schema.yaml
# Create forms for all tables
sheetdb forms init --schema schema.yaml
🌐 Framework Agnostic
Works with any Python or JavaScript framework:
- ✅ Django (with ORM adapter)
- ✅ FastAPI (REST API server)
- ✅ Flask
- ✅ React (with hooks)
- ✅ Vue
- ✅ Angular
- ✅ Next.js
- ✅ Vanilla JS/Python
How I Built It: Architecture and Technical Decisions
Building SheetDB required careful architectural decisions to balance simplicity, performance, and flexibility.
Core Architecture
┌─────────────────────────────────────────────────────┐
│ Application Layer (React, Django, FastAPI, etc.) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Interface Layer (JS Client, REST API, ORM) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Core SheetDB Library (Unified API) │
│ ├─ Sheets Gateway (API + Public URLs) │
│ ├─ DuckDB Engine (Relational Cache) │
│ └─ Schema Config (Validation) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Data Sources (Google Sheets, Public URLs, Forms) │
└─────────────────────────────────────────────────────┘
Key Technical Decisions
1. DuckDB as Cache Layer
The Problem: Google Sheets API is slow (~500ms per query) and rate-limited (100 requests per 100 seconds).
The Solution: Cache data in DuckDB, an embedded analytical database.
Why DuckDB?
- In-memory - No disk I/O, blazing fast queries
- Columnar storage - Optimized for analytical queries
- Vectorized execution - SIMD operations for speed
- Full SQL support - JOINs, aggregations, subqueries
- Embedded - No separate server process
Implementation:
class DuckDBEngine:
def __init__(self):
self.conn = duckdb.connect(':memory:')
def load_table(self, table_name, data, schema):
# Create table from sheet data
self.conn.execute(f"CREATE OR REPLACE TABLE {table_name} AS SELECT * FROM data")
def execute_sql(self, query, params=None):
# Execute with parameter binding (SQL injection safe)
return self.conn.execute(query, params).fetchall()
Result: 500-1000x performance improvement!
2. Three Operating Modes
The Problem: Different use cases need different authentication strategies.
The Solution: Support three modes with seamless switching.
Public Mode Implementation:
class PublicSheetsReader:
def read_sheet(self, sheet_url, gid=0):
# Construct CSV export URL
csv_url = f"https://docs.google.com/spreadsheets/d/{id}/export?format=csv&gid={gid}"
# Fetch and parse CSV (no authentication!)
response = urllib.request.urlopen(csv_url)
return list(csv.DictReader(io.StringIO(response.read().decode('utf-8'))))
Benefit: Zero setup for prototypes, full control for production.
3. Forms API for Unlimited Writes
The Problem: Google Sheets API has strict rate limits for writes.
The Solution: Use Google Forms API to create forms that write to sheets without consuming API quota.
Implementation:
class FormsAPIGateway:
def create_form(self, title, spreadsheet_id, sheet_name):
# Create form via Forms API
form = self.forms_service.forms().create(body={'info': {'title': title}}).execute()
# Link to spreadsheet
self.link_to_spreadsheet(form['formId'], spreadsheet_id, sheet_name)
return form['formId']
def submit_response(self, form_id, data, entry_mapping):
# Submit via HTTP POST (no API quota!)
form_data = {entry_mapping[field]: value for field, value in data.items()}
response = requests.post(submit_url, data=form_data)
return response.url
Benefit: Unlimited writes for high-volume data collection!
4. Schema-First Design
The Problem: Google Sheets are untyped and unstructured.
The Solution: YAML schema configuration with validation.
Schema Example:
tables:
- name: monsters
sheet_name: Monsters
primary_key: id
columns:
- name: id
type: integer
- name: name
type: string
- name: danger_level
type: integer
relationships:
- name: hunt_monster
from_table: hunts
from_column: monster_id
to_table: monsters
to_column: id
Benefits:
- Type safety and validation
- Automatic code generation
- Clear documentation
- Enables relational queries
5. Framework Agnostic Core
The Problem: Different projects use different frameworks.
The Solution: Build a clean core library with adapters for specific frameworks.
Architecture:
- Core Library - Framework-independent SheetDB class
- Django Adapter - ORM layer mimicking Django models
- REST API - FastAPI server for universal access
- JS Client - npm package for frontend frameworks
Result: Works with any framework, no vendor lock-in!
Challenges I Faced and How I Solved Them
Challenge 1: Google API Rate Limits
Problem: The Sheets API has strict rate limits (100 requests per 100 seconds).
Solution: DuckDB caching. Sync once, query unlimited times. This turned a limitation into our biggest performance advantage.
Impact: 500x performance improvement and no rate limit issues for reads.
Challenge 2: Relational Data in Flat Sheets
Problem: Google Sheets are inherently flat, but we needed relationships.
Solution: Built a schema system that maps sheets to tables and defines foreign keys, then DuckDB handles the joins in-memory.
Implementation:
# Schema defines relationships
relationships:
- from_table: hunts
from_column: monster_id
to_table: monsters
to_column: id
# DuckDB executes joins
SELECT h.*, m.name as monster_name
FROM hunts h
JOIN monsters m ON h.monster_id = m.id
Challenge 3: Type Safety Without a Real Database
Problem: Sheets store everything as strings.
Solution: Implemented automatic type inference and validation, converting strings to proper types (integers, dates, booleans) based on the schema.
def infer_type(value):
try:
return int(value)
except ValueError:
try:
return float(value)
except ValueError:
if value.lower() in ('true', 'false'):
return value.lower() == 'true'
return value
Challenge 4: Multiple Framework Support
Problem: Supporting Django, FastAPI, React, and vanilla JS meant building for many different environments.
Solution: Used the adapter pattern to keep framework-specific code separate from the core library.
Result: Clean architecture with well-defined interfaces.
Challenge 5: Authentication Complexity
Problem: Google's authentication can be tricky and intimidating for beginners.
Solution: Simplified it with three modes: public (no auth), authenticated (service account), and hybrid. Each mode has clear setup instructions and examples.
Documentation: Created comprehensive guides with step-by-step instructions for each mode.
Challenge 6: Schema Management
Problem: Manually writing schemas is tedious and error-prone.
Solution: Built three ways to create schemas:
- Interactive CLI - Step-by-step guided creation
- Auto-generation - Infer from existing sheets
- Manual YAML - Full control for advanced users
# Interactive creation
sheetdb schema create
# Auto-generation
sheetdb schema generate --credentials creds.json --spreadsheet-id YOUR_ID
Challenge 7: Forms API Integration
Problem: The Forms API is powerful but complex.
Solution: Abstracted it into simple functions: create a form, link it to a sheet, and get unlimited writes. All automated.
# One line to enable unlimited writes
form_url = db.enable_forms_writer('monsters', auto_create=True)
Accomplishments I'm Proud Of
🎯 True Relational Database
We didn't just add features—we fundamentally changed what's possible. Multiple tables, foreign keys, SQL joins, aggregations. It's a real database now.
🆓 Still Completely Free
Despite all the advanced features, SheetDB remains free to use. No hosting costs, no bandwidth limits, no hidden fees. Google's infrastructure handles everything.
🤖 Automation That Actually Works
From schema generation to form creation, everything can be automated. This saves hours of manual setup and reduces errors.
📚 Comprehensive Documentation
I built extensive documentation with examples, tutorials, troubleshooting guides, and API references. The Django app and React examples show real-world usage.(check repo link)
🎨 Beautiful Demo Apps
The Monster Hunt Tracker (Django) showcases the full stack with a Kiro dark mode + Halloween theme. It's functional and fun. (check repo link)
🌐 Framework Flexibility
Works with Python, JavaScript, Django, FastAPI, React—whatever you're building with. Not locked into any specific framework.
📊 Production-Ready
Type safety, validation, error handling, logging, testing. SheetDB isn't just a prototype—it's ready for real applications.
What I Learned
Technical Lessons
1. Caching is Everything
The DuckDB layer transformed performance. Sometimes the best solution isn't faster APIs, it's not calling them at all.
2. Constraints Breed Creativity
Google Sheets' limitations forced us to think differently. The result is more innovative than a traditional database wrapper.
3. Simplicity Wins
The three operating modes (public, authenticated, hybrid) cover 99% of use cases. We resisted adding complexity.
4. Documentation Matters
Good docs with examples are as important as good code. We invested heavily in guides, tutorials, and working demos.
5. Framework Agnostic is Hard
Supporting multiple frameworks requires careful architecture. The adapter pattern and clean interfaces were essential.
How I Used Kiro: Spec-Driven Development Meets Vibe Coding
Building SheetDB with Kiro combined two approaches: spec-driven development for core architecture and vibe coding for rapid iteration—achieving ~20x faster development.
Spec-Driven Development
For critical features, I used Kiro's structured workflow with formal requirements (.kiro/specs/sheetdb/requirements.md), detailed design documents with correctness properties, and phased task lists. This provided clear direction, testable acceptance criteria, and maintainable architecture for the database engine, ORM layer, and API contracts.
Vibe Coding
For UI/UX and documentation, I used conversational iteration with Kiro:
- Theme Design: "Make it like Kiro dark mode with Halloween emojis" → Complete CSS in 20 minutes (vs 4-6 hours)
- Documentation: "Cover authentication for 3 Google APIs" → Production-ready guides in 30 minutes (vs 2-3 days)
- Debugging: Screenshot of error → Complete fix with automation scripts in 10 minutes (vs 1-2 hours)
The Key Insight
Use spec-driven when correctness matters most (architecture, core logic). Use vibe coding when speed matters most (styling, docs, automation). Together: production-ready system in ~2 hours vs ~4-5 days traditional development.
What's Next for SheetDB
Short-term (Next 3 Months)
1. Incremental Sync and some improvements
- Only update changed data instead of full reloads
- Track modifications and sync deltas
- Free analytics for data via Google Forms
- Finish and police the product, write detailed documentation and host it for users.
- expose sheetdb server to be used for any other project.
2. More Examples
- Flask app example
- Full React App example using sheetdb-js
- Show SheetDB working with every major framework
Medium-term (6 Months)
1. Multi-Spreadsheet Support
- Combine data from multiple Google Sheets
- Join across spreadsheets
- Aggregate data from multiple sources
2. Advanced and more complete SQL Relationships
- Many-to-many relationships
- Polymorphic associations
- Self-referential relationships
3. Schema Migrations
- Version control for schemas
- Add columns, change types
- Migrate data safely
4. Offline Support
- Local-first architecture with sync
- Work offline, sync when connected
- Conflict resolution
Long-term (1 Year+)
1. Visual Schema Builder
- Web UI for designing schemas
- No YAML required
- Drag-and-drop table relationships
2. GraphQL API
- Alternative to REST API
- More flexible queries from frontends
- Better for complex data fetching
Try SheetDB Today!
Ready to build your next project with zero hosting costs and database power?
Quick Start (5 Minutes)
# Install
pip install git+https://github.com/yourusername/sheetdb.git
# Initialize from public URL (no auth required!)
sheetdb init --url "https://docs.google.com/spreadsheets/d/YOUR_ID/"
# Start querying!
from sheetdb import SheetDB
# Connect to your sheet
db = SheetDB.from_public_url("https://docs.google.com/spreadsheets/d/YOUR_ID/")
db.sync()
# Query with SQL
monsters = db.query("SELECT * FROM monsters WHERE danger_level > 7")
# That's it! 🎉
Links
- GitHub: github.com/whitebumblebee/sheetdb
- Documentation: Complete guides and tutorials
- Examples: Working code examples
- Demo App: Monster Hunt Tracker
Join the Community
- ⭐ Star the project on GitHub
- 🐛 Report bugs and request features
- 💡 Share your projects built with SheetDB
- 📝 Contribute to documentation
- 🤝 Help other developers
Built with 🎃 Kiroween spirit
Because the best solutions come from working around limitations, not throwing money at them.
Top comments (0)