TypeScript · Cloudflare · Edge-native
Your co-founder isthis monorepo. & AI.
One coherent stack: Auth, OttaORM, Queues, Cron, Realtime, Blog/CMS, RBAC, RLS & UI components wired together for multi-tenant SaaS apps on Cloudflare Workers.An idea on Friday, a product on Monday: ottabase+AI makes it a ✨magical reality.
Edge KV·D1 + Drizzle·Durable Objects·MIT
Everything your needs.
In Packages.
Open-source monorepo on Cloudflare Workers - not a hosted backend. Fat models, RBAC, RLS, queues, realtime, and blog/CMS when you enable them.
Expressive APIs.
For Humans and Agents alike.
Even 100% AI-generated code stays readable, intentional, and truly yours.
Fields, relationships, and domain logic live in one class. CRUD APIs, forms, and tables are derived from it - one place defines it all. Inspired by Laravel Eloquent, extended for fat-models, and designed from day one for TypeScript and Cloudflare Workers.
import { BaseModel } from '@ottabase/ottaorm';
export class Post extends BaseModel {
static entity = 'posts';
static softDeletes = true;
// Drives auto-generated forms, tables, and API.
static fields = {
title: { type: 'text', required: true },
status: { type: 'select', options: ['draft', 'published'] },
authorId: { type: 'belongs-to', model: 'users' },
};
static casts = { publishedAt: 'date', featured: 'boolean' };
// Domain logic on the model - not in a forwarder.
async publish() {
this.set('status', 'published');
this.set('publishedAt', new Date());
return this.save();
}
author() { return this.belongsTo(() => import('./User'), 'authorId'); }
comments() { return this.hasMany (() => import('./Comment'), 'postId'); }
}
// GET / POST / PATCH / DELETE /api/ottaorm/posts
// ↑ zero extra code.import { createModelHooks } from '@ottabase/ottaorm/client';
// One line. Five hooks.
export const {
useList: usePosts,
useDetail: usePost,
useCreate: useCreatePost,
useUpdate: useUpdatePost,
useDelete: useDeletePost,
} = createModelHooks<Post>({ entityName: 'posts' });
// In your component:
const { data } = usePosts({
filters: { status: 'published' },
perPage: 20,
});
const publish = useUpdatePost();
await publish.mutateAsync({ id, status: 'published' });
// TanStack Query handles caching, deduplication,
// and optimistic updates automatically.// Call once per request.
await initRLS({
organizationId: session.orgId,
userId: session.userId,
});
// Every query is scoped to this tenant.
// No WHERE clause. No accident possible.
const posts = await Post.all();
// RBAC - roles cached in Cloudflare KV.
if (await user.hasPermission('post.publish:any')) {
await post.publish();
}
// Audit log written automatically on every write.
// Multi-tenancy is the foundation,
// not the feature you add in month 6.$ pnpm cf:setup
✓ D1 database (3 GB free, SQLite at the edge)
✓ KV namespace (sessions + permission cache)
✓ R2 bucket (files + Cloudflare Images)
✓ Queues (background jobs)
✓ Durable Objects (realtime WebSocket pub/sub)
$ pnpm dev → browser http://localhost:3003
✓ /__bootstrap__ GUI: migrations, RBAC seed, owner, finalize
# After READY, model changes still go through POST /api/ottaorm/init or Admin → Migrate.
$ git push origin main
✓ Built in 24 s
✓ Live on 275+ edge locations
✓ https://my-new-idea.my-account.workers.dev ✓ https://neoidea.com # custom domains work too
# First-run wizard or curl API - then you're in. $0-$5/mo covers MVPs on the same edge stack.Edge-first.
Not ported-from.
Ottabase was designed for Cloudflare Workers from day one - not ported from Node.js. Every layer is a first-class Cloudflare primitive, not a wrapper hiding something slower underneath.
D1 for your relational database (SQLite, replicated globally). KV for sub-millisecond RBAC permission checks and session lookups. R2 for file storage with Cloudflare Images processing. Durable Objects for stateful WebSocket pub/sub without a separate Realtime bill. Queues for reliable background jobs. Analytics Engine for high-volume event tracking at the edge.
You own every bit of it - deployed under your own Cloudflare account. Sub-100ms latency to users in 275+ cities worldwide, with no cold starts and pricing that stays predictable as you scale. Not sponsored. Just convinced.
Read the philosophy →RBAC cache
Files
Realtime
Jobs
From an empty folder to going live.
Clone, env, dev servers, then a built-in bootstrap wizard on first open - no mystery init route. Under five minutes to a logged-in tenant app.
- Clone & install - git clone + pnpm install + pnpm build:pkg - shared packages must compile before first dev.
- Env secrets - Copy apps/ottabase-template-app-tanstack/.env.example to .env.local; set three required secrets.
- Setup Cloudflare - pnpm cf:setup when you want real D1/KV/R2/Queues bindings; for a quick local spin a dummy CLOUDFLARE_API_TOKEN is enough with wrangler --local.
- pnpm dev - Runs Vite on 3003, Wrangler on 3004.
- Bootstrap wizard - First visit to localhost:3003 redirects to /__bootstrap__: migrations, RBAC seed, owner account, finalize via a GUI wizard.
- Deploy - wrangler deploy - first production boot uses the same wizard or API sequence once; later schema tweaks use Admin → Migrate from the UI.
Own 100% of the stack.
Stop Rebuilding.
Start Shipping!
Open sourced under the MIT License.
v1.0.0 · github.com/thinkdj/ottabase