Quickback Docs

Getting Started

Get started with Quickback in minutes. Learn how to define database tables with security configuration and compile them into a production-ready API.

Get started with Quickback in minutes. This guide shows you how to define a complete table with security configuration.

Start a Project

mkdir my-app && cd my-app
npx @quickback-dev/cli start

quickback start is interactive — it asks for a template and project name, scaffolds the project on disk, and only prompts for an account just before compiling. If you cancel at the login prompt the scaffold stays put; finish later with quickback login && quickback compile.

Prefer a scriptable form?

npm install -g @quickback-dev/cli
quickback create cloudflare my-app
cd my-app

Both paths scaffold the same project:

  • quickback.config.ts — Project configuration
  • quickback/features/ — Your table definitions
  • Working example feature with full security configuration (when you pick todos/blog/saas)

Available templates:

  • cloudflare — Cloudflare Workers + D1 + Better Auth, bare scaffold (free)
  • todos — Working CRUD example with masking + actions (free)
  • blog — Single-tenant blog, PUBLIC reads, admin writes (free)
  • empty — Cloudflare scaffold, no example features (free)
  • saas — Full B2B SaaS with orgs, R2 file storage, webhooks (pro)

File Structure

Each table gets its own file with schema and security config together:

quickback/
├── quickback.config.ts
└── features/
    └── jobs/
        ├── jobs.ts             # Table + security config
        ├── applications.ts     # Related table + config
        ├── actions.ts          # Custom actions (optional)
        └── handlers/           # Action handlers (optional)
            └── close-job.ts

Complete Example

Here's a complete jobs table with all security layers in a single feature() call:

// quickback/features/jobs/jobs.ts
import { feature, q } from '@quickback/compiler';

export default feature('jobs', {
  columns: {
    id:             q.id(),
    title:          q.text().required(),
    department:     q.text().required(),
    status:         q.text().default('draft').required(),  // draft | open | closed
    salaryMin:      q.int().optional(),
    salaryMax:      q.int().optional(),
    // Ownership — required for firewall data isolation
    organizationId: q.scope("organization"),
  },

  // 1. FIREWALL — Data isolation
  firewall: [{ field: 'organizationId', equals: 'ctx.activeOrgId' }],

  // 2. GUARDS — Field modification rules
  guards: {
    createable: ["title", "department", "status", "salaryMin", "salaryMax"],
    updatable:  ["title", "department", "status"],
  },

  // 3. READ — Collection + per-id GET (gates GET / and GET /:id)
  read: {
    access: { roles: ["owner", "hiring-manager", "recruiter", "interviewer"] },
    pageSize: 25,
  },

  // 4. WRITE OPERATIONS
  create: { access: { roles: ["owner", "hiring-manager"] } },
  update: { access: { roles: ["owner", "hiring-manager"] } },
  delete: { access: { roles: ["owner", "hiring-manager"] }, mode: "soft" },
});

One import, one export, every security layer declared in one place. Secure by default — no write routes ship until you opt in with top-level create, update, delete, or upsert.

Alternatives — same result, different shape

If you'd rather split the table declaration from the security config (e.g. to reference the table in an action file), use the two-export form. Identical output:

import { q, defineTable } from '@quickback/compiler';

export const jobs = q.table('jobs', {
  id:             q.id(),
  title:          q.text().required(),
  // … same columns
});

export default defineTable(jobs, {
  firewall: [{ field: 'organizationId', equals: 'ctx.activeOrgId' }],
  // … same config
});

export type Job = typeof jobs.$infer;

Or if you're interop'ing with existing Drizzle schemas, you can author with sqliteTable / pgTable directly — the compiler dispatches per file, and all forms can coexist in the same project.

See feature() — single-export sugar and Schema: choosing between q and Drizzle for the full picture.

What Each Layer Does

  1. Firewall: Automatically adds WHERE organizationId = ? to every query. Users in Org A can never see Org B's data.
  2. Guards: Controls which fields can be modified — createable for POST, updatable for PATCH, protected for action-only fields.
  3. Write Access: Role-based access control for each operation. All roles can read, only hiring managers can write.

Compile and Run

# Compile your definitions — prompts you to sign in or sign up
# if you haven't already (no separate `quickback login` step needed)
quickback compile

# Run locally
npm run dev

The first compile prompts for an account; subsequent compiles re-use the stored session.

Generated Endpoints

Quickback generates these endpoints from the example above:

MethodEndpointDescription
GET/api/v1/jobsList jobs (all roles)
GET/api/v1/jobs/:idGet single job
POST/api/v1/jobsCreate job (hiring managers only)
PATCH/api/v1/jobs/:idUpdate job (hiring managers only)
DELETE/api/v1/jobs/:idSoft delete job (hiring managers only)

Test Your API

After quickback compile and npm run dev, your API is running locally. Open a second terminal and try these requests:

# 1. Create a user account
curl -X POST http://localhost:8787/api/auth/sign-up/email \
  -H "Content-Type: application/json" \
  -d '{"email": "admin@example.com", "password": "securepassword123", "name": "Admin"}'

# 2. Sign in and get a session token
curl -X POST http://localhost:8787/api/auth/sign-in/email \
  -H "Content-Type: application/json" \
  -d '{"email": "admin@example.com", "password": "securepassword123"}'
# → Response includes a session token in Set-Cookie header

# 3. Create a record (use the session cookie from step 2)
curl -X POST http://localhost:8787/api/v1/jobs \
  -H "Content-Type: application/json" \
  -H "Cookie: better-auth.session_token=<token>" \
  -d '{"title": "Senior Engineer", "department": "Engineering", "status": "open"}'

# 4. List records
curl http://localhost:8787/api/v1/jobs \
  -H "Cookie: better-auth.session_token=<token>"

Cloudflare templates run on port 8787 (wrangler dev). Check your terminal output for the exact URL.

Next Steps

See Also

  • Quickback Stack — The runtime environment where your compiled API runs (D1, KV, R2, auth)
  • Account UI — Pre-built authentication and account management UI
  • Using the API — CRUD endpoints, filtering, and batch operations

On this page