Empty Scaffolding Templates
Start from scratch with just the config file. Cloudflare Workers + D1.
The empty template creates a project with only the configuration file — no example features. Use this when you know exactly what you want to build and don't need starter code.
Available Empty Templates
| Template | Runtime | Database | Command |
|---|---|---|---|
empty | Cloudflare Workers | D1 | quickback create empty my-app |
Alias: quickback create scaffold my-app (same as empty)
Create the Project
quickback create empty my-appThis scaffolds a minimal project:
my-app/
├── quickback/
│ ├── quickback.config.ts # Compiler configuration
│ └── features/ # Empty — add your own
├── package.json
└── tsconfig.jsonGenerated Configuration
export default {
name: "my-app",
template: "hono",
auth: {
// Role hierarchy: lowest → highest privilege.
// Use "role+" in access rules to mean "this role and above".
// e.g. roles: ["member+"] expands to ["member", "admin", "owner"].
roleHierarchy: ["member", "admin", "owner"],
},
providers: {
runtime: { name: "cloudflare", config: {} },
database: {
name: "cloudflare-d1",
config: { binding: "DB" },
},
auth: { name: "better-auth", config: {} },
},
};Organization-backed auth is the default. If you want a fixed-org deployment with no org switching UI, add features.pinnedOrganizationId as described in Pinned Organization Mode.
Adding Your First Feature
Create a feature directory with a schema file:
mkdir -p quickback/features/products// quickback/features/products/products.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { defineTable } from "@quickback/compiler";
export const products = sqliteTable("products", {
id: text("id").primaryKey(),
name: text("name").notNull(),
description: text("description"),
price: integer("price").notNull(),
organizationId: text("organization_id").notNull(),
// `ownerId` is the owner column the firewall auto-detects — `userId` is not
// auto-detected on feature tables.
ownerId: text("owner_id").notNull(),
// Compiler-managed columns — the audit quartet on every table, plus the
// soft-delete pair because `delete.mode` is "soft".
createdAt: text("created_at").notNull().default('1970-01-01T00:00:00.000Z').$defaultFn(() => new Date().toISOString()),
modifiedAt: text("modified_at").notNull().default('1970-01-01T00:00:00.000Z').$defaultFn(() => new Date().toISOString()).$onUpdate(() => new Date().toISOString()),
createdBy: text("created_by"),
modifiedBy: text("modified_by"),
deletedAt: text("deleted_at"),
deletedBy: text("deleted_by"),
});
export default defineTable(products, {
firewall: {
organization: {},
owner: {},
softDelete: {},
},
guards: {
createable: ["name", "description", "price"],
updatable: ["name", "description", "price"],
},
read: {
access: { roles: ["member+"] },
},
create: { access: { roles: ["member+"] } },
update: { access: { roles: ["member+"] } },
delete: { access: { roles: ["admin+"] }, mode: "soft" },
});Then compile:
quickback login # First time only
quickback compileSetup Steps
Follow the same setup steps as the Cloudflare template. Compiling with zero features works (you get an auth-only API); add feature files as you go and recompile.
Next Steps
- Schema Definitions — Define tables with Drizzle ORM
- Firewall — Data isolation and tenant scoping
- Access Control — Role-based permissions
- Guards — Field-level write protection
- Full Example — Complete feature walkthrough