# Getting Started

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

## Start a Project

```bash
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`.

Already know what you want? Pass the template (and optionally a name) to skip the prompts:

```bash
npx @quickback-dev/cli start blank        # config only, no features
npx @quickback-dev/cli start todos        # working CRUD example
npx @quickback-dev/cli start blog my-site # blog starter, named my-site
```

Prefer a scriptable form?

```bash
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
- `todos` — Working CRUD example with masking + actions
- `blog` — Single-tenant blog, PUBLIC reads, admin writes
- `blank` — Cloudflare scaffold, no example features (alias: `empty`)
- `saas` — Full B2B SaaS with orgs, R2 file storage, webhooks

## 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/            # Custom actions (optional) — one file each
            └── close-job.ts    # export default defineAction({ ... })
```

## Complete Example

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

```typescript
// 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"),
    // Compiler-managed columns — declared, not magic. `...q.audit()` expands
    // to createdAt/modifiedAt/createdBy/modifiedBy; `...q.softDelete()` adds
    // deletedAt/deletedBy and is required because this resource soft-deletes.
    ...q.audit(),
    ...q.softDelete(),
  },

  // 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:

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

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

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() — the canonical form](/define/feature) and [Schema: Drizzle interop](/define/schema#drizzle-interop) 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

```bash
# 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.

### Local development requirements

Two things the local `wrangler dev` server needs that production gets from Cloudflare config:

- **`BETTER_AUTH_SECRET`** — read from a `.dev.vars` file in the project root. Projects scaffolded with `quickback start`/`create` get one generated automatically (git-ignored). If yours is missing, every auth call returns `503 MISSING_ENV`; create it with:
  ```bash
  echo "BETTER_AUTH_SECRET=$(openssl rand -hex 32)" > .dev.vars
  ```
- **An `Origin` header on cookie-backed writes** — CSRF protection rejects mutations (`403 CSRF_ORIGIN_REJECTED`) unless the request carries a trusted origin. Browsers send it automatically; when testing with curl or an agent, add `-H "Origin: http://localhost:8787"` to POST/PATCH/DELETE requests (reads don't need it). Bearer-token requests are exempt.

## Generated Endpoints

Quickback generates these endpoints from the example above:

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/jobs` | List jobs (all roles) |
| `GET` | `/api/v1/jobs/:id` | Get single job |
| `POST` | `/api/v1/jobs` | Create job (hiring managers only) |
| `PATCH` | `/api/v1/jobs/:id` | Update job (hiring managers only) |
| `DELETE` | `/api/v1/jobs/:id` | Soft 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:

```bash
# 1. Create a user account
curl -X POST http://localhost:8787/auth/v1/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/auth/v1/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 an organization and make it the session's active org.
#    The example table is org-firewalled, so a session with no active org
#    sees nothing and can write nothing. Signing up does not create one.
curl -X POST http://localhost:8787/auth/v1/organization/create \
  -H "Content-Type: application/json" \
  -H "Origin: http://localhost:8787" \
  -H "Cookie: better-auth.session_token=<token>" \
  -d '{"name": "Acme", "slug": "acme"}'
# → Response includes the organization id

curl -X POST http://localhost:8787/auth/v1/organization/set-active \
  -H "Content-Type: application/json" \
  -H "Origin: http://localhost:8787" \
  -H "Cookie: better-auth.session_token=<token>" \
  -d '{"organizationId": "<org-id>"}'

# 4. Create a record (use the session cookie from step 2).
#    `Origin` is required on cookie-backed writes — see the CSRF note above.
curl -X POST http://localhost:8787/api/v1/jobs \
  -H "Content-Type: application/json" \
  -H "Origin: http://localhost:8787" \
  -H "Cookie: better-auth.session_token=<token>" \
  -d '{"title": "Senior Engineer", "department": "Engineering", "status": "open"}'

# 5. List records (reads need no Origin header)
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

You have a live local API. To ship it:

- [**Deploy**](/start/deploy) — `quickback deploy` provisions D1/KV/R2, writes the real ids back into your config, applies migrations, and ships the worker

- [Template Walkthroughs](/start/templates) — Detailed setup guides
- [Full Example](/start/first-api) — Complete resource walkthrough
- [Database Schema](/define/schema) — Column types, relations, audit fields
- [Firewall](/define/firewall) — Data isolation patterns
- [Access](/define/access) — Role & condition-based control
- [Guards](/define/guards) — Field modification rules
- [Masking](/define/masking) — Field redaction for sensitive data
- [Actions](/define/actions) — Custom business logic endpoints

## See Also

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