TL;DR
MoonDB is a backend-as-a-service designed for AI coding agents. You describe your data model in JSON, and MoonDB gives you a full REST API with built-in auth, file storage, and AI endpoints. I built it because every BaaS I tried made my coding agents hallucinate endpoints that didn’t exist. This tutorial walks through standing up a complete backend for a notes app in under five minutes.
Why I Built This
I’ve spent the last year building side projects with Cursor, Claude Code, and Windsurf. The pattern was always the same: the agent writes a beautiful frontend in minutes, then crashes into a wall when it needs a backend. Tell Cursor “add a Supabase backend” and it hallucinates table names, invents RPC functions, and generates SQL that doesn’t match your actual schema. The agent doesn’t know what your backend looks like because there’s no single source of truth it can read.
I wanted something where the agent describes what it needs (tables, columns, access rules) in a format it already understands, JSON, and the backend figures out the rest. So I built MoonDB: a single Cloudflare Worker, about 20,000 lines of TypeScript, where each project gets its own isolated D1 database. I’ll show the architecture briefly at the end, but first, let’s build something.
What We’re Building
A notes app with these features:
- User accounts with email/password auth
- CRUD operations on notes (create, read, update, delete)
- Categories for organizing notes
- File attachments on notes
- An AI endpoint that summarizes a note’s content
All of this will be running on Cloudflare’s edge, served from the nearest data center to your users.
Step 1: Create a MoonDB Account and Project
Sign up at moondb.ai. The free tier gives you 1 project, 100 MB of storage, and 1 million reads per month. After signing up, grab your API key (mk_...) from the dashboard.
Create a project:
curl -X POST https://moondb.ai/v1/projects \
-H "X-API-Key: mk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "notes-app"}'
Response:
{
"data": {
"id": "d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a",
"name": "notes-app",
"api_url": "https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a",
"schema_version": 0,
"plan": "new_moon",
"admin_key": "sk_...",
"public_key": "pk_..."
}
}
Save that admin_key immediately — it’s shown only once. The public_key is safe for client-side code.
Step 2: Define Your Schema
This is the core of MoonDB. Instead of writing SQL migrations or clicking through a dashboard, you send a JSON object describing your entire data model. MoonDB parses it, validates it, diffs it against the current state, and applies migrations automatically.
curl -X PUT https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/v1/schema \
-H "X-Admin-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"tables": {
"users": {
"auth_table": true,
"columns": {
"display_name": "string",
"bio": "text"
}
},
"categories": {
"columns": {
"name": "string required unique",
"color": "string default #3b82f6",
"user_id": "ref users required"
},
"access": {
"read": "public",
"create": "authenticated",
"update": "owner",
"delete": "owner"
},
"owner_field": "user_id"
},
"notes": {
"columns": {
"title": "string required",
"body": "text required",
"category_id": "ref categories",
"user_id": {
"type": "ref",
"ref": "users",
"required": true,
"on_delete": "cascade"
},
"pinned": "bool default false",
"attachment": "file"
},
"access": {
"read": "owner",
"create": "authenticated",
"update": "owner",
"delete": "owner"
},
"owner_field": "user_id"
}
},
"ai_endpoints": {
"summarize": {
"model": "gemma",
"prompt": "Summarize the following note in 2-3 sentences, keeping the key points: {{body}}",
"access": "auth"
}
}
}'
That single request created three tables with proper foreign keys, indexes, check constraints, and an AI endpoint. Every table gets id, created_at, and updated_at columns automatically. The users table with auth_table: true gets built-in email/password authentication with scrypt hashing and JWT tokens.
Look at the short-form column syntax: "title": "string required" expands to a TEXT column with a NOT NULL constraint. "category_id": "ref categories" creates a foreign key. "pinned": "bool default false" adds a boolean with a default. For options like on_delete or constraints like min/max, switch to the object form (see user_id in the notes table above). For most columns, the short form is cleaner and easier for agents to generate.
Step 3: Your API Is Live
The moment the schema is applied, you have a full REST API. Let’s use it.
Create a note (requires authentication, which we’ll set up in Step 4, but here’s the admin-key version for testing):
curl -X POST https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/api/notes \
-H "X-Admin-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"title": "Project ideas for Q3",
"body": "1. Rewrite the billing service in Go\n2. Add WebSocket support\n3. Build a CLI tool for schema management",
"pinned": true
}'
{
"data": {
"id": "8b3f7a2e-1c4d-4e5f-9a6b-7c8d9e0f1a2b",
"title": "Project ideas for Q3",
"body": "1. Rewrite the billing service in Go...",
"pinned": true,
"category_id": null,
"user_id": null,
"attachment": null,
"created_at": "2026-05-13T11:00:00Z",
"updated_at": "2026-05-13T11:00:00Z"
}
}
List notes with filtering and sorting:
# Get pinned notes, newest first
curl "https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/api/notes?pinned=eq.true&sort=created_at.desc" \
-H "X-Admin-Key: sk_..."
The filtering syntax uses PostgREST-style operators: eq, neq, gt, gte, lt, lte, like, in, is_null, not_null. Sorting accepts field.asc or field.desc, and you can chain multiple sort keys.
Update a note:
curl -X PATCH https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/api/notes/8b3f7a2e-1c4d-4e5f-9a6b-7c8d9e0f1a2b \
-H "X-Admin-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{"pinned": false}'
Include related data:
# Get notes with their category details joined in
curl "https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/api/notes?include=category_id" \
-H "X-Admin-Key: sk_..."
This LEFT JOINs the categories table and nests the result. Instead of just "category_id": "a2b3c4d5-...", you get the full category object with its name and color fields.
Step 4: Wire Up User Authentication
Because users has auth_table: true, MoonDB already created auth endpoints for signup, login, token refresh, and logout. Passwords are hashed with scrypt, access tokens expire in 1 hour, and refresh tokens last 30 days. No external auth provider needed, and no auth library to install in your frontend.
Sign up a user:
curl -X POST https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"password": "Correct-horse-42",
"display_name": "Alice"
}'
{
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "rt_a8f3...",
"user": {
"id": "c5d6e7f8-a9b0-4c1d-8e2f-3a4b5c6d7e8f",
"email": "alice@example.com",
"display_name": "Alice"
}
}
}
Now create a note as Alice:
curl -X POST https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/api/notes \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"title": "Meeting notes — May sync",
"body": "Discussed the migration timeline. Target: end of June.",
"user_id": "c5d6e7f8-a9b0-4c1d-8e2f-3a4b5c6d7e8f"
}'
Because the notes table has "read": "owner" and "owner_field": "user_id", Alice can only see her own notes. MoonDB injects WHERE user_id = ? into every query automatically.
If you already use Clerk, Auth0, or another provider, MoonDB supports external JWT validation too. Point it at your provider’s JWKS endpoint and MoonDB will verify tokens and auto-create users on first request:
curl -X PUT https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/v1/auth-config \
-H "X-Admin-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"provider": "external",
"jwks_url": "https://your-clerk-instance.clerk.accounts.dev/.well-known/jwks.json",
"user_id_claim": "sub"
}'
Step 5: Attach Files
Notes have an attachment column of type file. Upload works through multipart form data:
# Upload a file
curl -X POST https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/storage/upload \
-H "Authorization: Bearer eyJhbG..." \
-F "file=@screenshot.png"
{
"data": {
"id": "f1e2d3c4-b5a6-4978-8d0e-1f2a3b4c5d6e",
"url": "https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/storage/f1e2d3c4-b5a6-4978-8d0e-1f2a3b4c5d6e?sig=a8f3...&exp=1747224000",
"size": 84210,
"mime_type": "image/png"
}
}
Then store the URL on the note:
curl -X PATCH https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/api/notes/8b3f7a2e-1c4d-4e5f-9a6b-7c8d9e0f1a2b \
-H "Authorization: Bearer eyJhbG..." \
-H "Content-Type: application/json" \
-d '{"attachment": "https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/storage/f1e2d3c4-b5a6-4978-8d0e-1f2a3b4c5d6e?sig=a8f3...&exp=1747224000"}'
Files are stored in Cloudflare R2 (S3-compatible) and served with HMAC-signed URLs. If you delete a note that has an attachment bound through a file column, the file gets cleaned up automatically.
Step 6: Call the AI Endpoint
We defined a summarize endpoint in the schema. Use it:
curl -X POST https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/ai/summarize \
-H "Authorization: Bearer eyJhbG..." \
-H "Content-Type: application/json" \
-d '{"body": "1. Rewrite the billing service in Go — current Node.js version has memory leaks under sustained load. Go gives us better concurrency primitives and a smaller memory footprint. Target: 2 weeks. 2. Add WebSocket support for real-time collaboration. Three users editing the same doc is our most-requested feature. Evaluate Cloudflare Durable Objects vs a standalone WebSocket server. 3. Build a CLI tool for schema management so developers can version-control their schemas alongside code."}'
{
"data": {
"result": "The team has three Q3 priorities: migrating the billing service from Node.js to Go for better performance, adding WebSocket-based real-time collaboration (the top user request), and building a CLI for schema version control.",
"model": "gemma",
"credits_used": 2
}
}
MoonDB runs inference on Cloudflare Workers AI, so there’s no external API key to manage. The free tier includes 5,000 AI credits per month.
Feeding Context to Your Coding Agent
After applying your schema, pull the generated context file:
curl https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/v1/llm-context
This returns a text block describing your exact schema, every endpoint, the auth flow, column types, and access rules. Paste it into .cursorrules or CLAUDE.md and your agent knows the exact endpoints and column names available.
MoonDB also generates tool-specific files:
# For Cursor
curl https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/v1/cursor-rules > .cursorrules
# For Claude Code
curl https://moondb.ai/p/d4e5f6a7-b8c9-4d0e-a1f2-3b4c5d6e7f8a/v1/claude-md > CLAUDE.md
Every error response includes a suggestion field with a concrete fix instruction — the kind of guardrail that prevents agent spirals. When your agent hits a 400, it reads the suggestion and self-corrects, usually in one retry.
Under the Hood
MoonDB is a single Cloudflare Worker deployed globally. Each project gets its own D1 database (SQLite at the edge), so your data is fully isolated from other users.
The schema engine works in four stages: parse the short-form JSON into a normalized structure, validate constraints and references, diff against the current schema to generate a migration plan, then compile that plan into SQL and execute it as an atomic batch.
For constraint changes that SQLite can’t handle through ALTER (most of them), the engine runs the 12-step table rebuild — create a temporary table with the new schema, copy data over, drop the original, rename. Referencing tables get rebuilt too so foreign keys stay intact.
The REST engine interprets the schema at request time rather than generating code from it: it parses your query filters, builds a parameterized SELECT, runs it against D1, strips hidden fields, and returns JSON. Adding a column to your schema takes effect immediately. The next request picks it up.
Pricing
| Plan | Price | Projects | Storage | Reads/mo | Writes/mo | AI Credits |
|---|---|---|---|---|---|---|
| New Moon | Free | 1 | 100 MB | 1M | 25K | 5K |
| Half Moon | $9/mo | 10 | 2 GB | 50M | 500K | 25K |
| Full Moon | $29/mo | Unlimited | 10 GB | 500M | 10M | 100K |
| Eclipse | $99/mo | Unlimited | 50 GB | 2B | 25M | 500K |
The free tier covers prototyping and small apps. Most side projects I’ve shipped stay comfortably within New Moon limits — 25K writes is roughly 800 writes per day, enough for any personal tool or demo.
Full pricing is on the MoonDB pricing page.
FAQ
Which backends work well with AI coding agents?
For AI-driven development where agents write most of the code, you want a backend that the agent can configure programmatically and understand fully. MoonDB lets the agent send JSON and fetch a context file describing every endpoint. Supabase and Firebase work too but require more manual setup and their APIs are harder for agents to reason about without hallucinating.
Can I use MoonDB with Cursor, Claude Code, or Bolt?
Yes. MoonDB generates context files for Cursor (.cursorrules), Claude Code (CLAUDE.md), and a generic LLM context format. Bolt and Lovable work through the public REST API with the public key. The /v1/llm-context endpoint gives any agent a complete description of your project’s schema and endpoints. You can pair it with tools like Chrome DevTools MCP for end-to-end agent workflows.
How does MoonDB compare to Supabase?
Supabase gives you a full Postgres database with a dashboard, edge functions, and a large community of plugins and extensions. MoonDB trades that flexibility for speed: you describe your schema in JSON and the API exists instantly, with no SQL, no dashboard configuration, and no deploy step. MoonDB is built for agent-driven development where the coding tool needs to set up and modify the backend programmatically. Supabase is better if you need raw SQL access, complex queries, or more third-party integrations.
Is my data isolated, and can I migrate away?
Yes to both. Each MoonDB project gets its own D1 database. Your data is physically separated from every other project on the platform. And because the schema is plain JSON and the underlying storage is standard SQLite, you can export both at any time. There are no proprietary data formats or lock-in mechanisms.
Sources
- MoonDB documentation — full API reference, schema syntax, auth flow, and storage docs
- MoonDB pricing — current plan tiers and limits
- Cloudflare D1 documentation — the underlying database engine MoonDB runs on
- SQLite ALTER TABLE limitations — why table rebuilds are necessary for constraint changes
Bottom Line
After your agent finishes the frontend, MoonDB gives it a backend to call. Three API calls, create project, apply schema, start querying, and the agent has a full backend it can reason about. I built it because I was tired of debugging hallucinated Supabase RPC calls at 2 AM, and every backend I’ve shipped with it since has worked on the first agent attempt.