Quickback Docs

Definitions Overview

Understand how Quickback's security layers work together. Learn the mental model for firewall, access, guards, and masking to build secure APIs.

Before diving into specific features, let's understand how Quickback's pieces connect. This page gives you the mental model for everything that follows.

The Big Picture

Quickback is a backend compiler. You write definition files, and Quickback compiles them into a production-ready API.

  1. You write definitions - Table files with schema and security config using defineTable
  2. Quickback compiles them - Analyzes your definitions at build time
  3. You get a production API - GET /rooms, POST /rooms, PATCH /rooms/:id, DELETE /rooms/:id, batch operations, plus custom actions

File Structure

Your definitions live in a definitions/ folder organized by feature:

my-app/
├── quickback/
│   ├── quickback.config.ts    # Compiler configuration
│   └── definitions/
│       └── features/
│           └── {feature-name}/
│               ├── claims.ts           # Table + config (defineTable)
│               ├── claim-versions.ts   # Secondary table + config
│               ├── claim-sources.ts    # Internal table (no routes)
│               ├── actions.ts          # Custom actions (optional)
│               └── handlers/           # Action handlers (optional)
│                   └── my-action.ts
├── src/                       # Generated code (output)
├── drizzle/                   # Generated migrations
└── package.json

Table files use defineTable to combine schema and security config:

// features/claims/claims.ts
import { sqliteTable, text } from "drizzle-orm/sqlite-core";
import { defineTable } from "@quickback/compiler";

export const claims = sqliteTable("claims", {
  id: text("id").primaryKey(),
  organizationId: text("organization_id").notNull(),
  content: text("content").notNull(),
});

export default defineTable(claims, {
  firewall: { organization: {} },
  guards: {
    createable: ["content"],
    updatable: ["content"],
  },
  crud: {
    list: { access: { roles: ["owner", "admin", "member"] } },
    get: { access: { roles: ["owner", "admin", "member"] } },
    create: { access: { roles: ["owner", "admin", "member"] } },
    update: { access: { roles: ["owner", "admin", "member"] } },
    delete: { access: { roles: ["owner", "admin"] } },
  },
});

export type Claim = typeof claims.$inferSelect;

Key points:

  • Tables with export default defineTable(...) → CRUD routes generated
  • Tables without default export → internal/junction tables (no routes)
  • Route path derived from filename: claim-versions.ts/api/v1/claim-versions

1 Resource = 1 Security Boundary

Each defineTable() defines its own complete security boundary — its own firewall (who sees the data), access rules (role requirements), guards (field validation), and masking (redaction). These are compiled into a single resource file that wraps all CRUD routes for that table.

A table without defineTable() is a supporting table — used internally by actions or handlers, but never exposed as its own API endpoint. See Internal Tables for details.

The Four Security Layers

Every API request passes through four security layers, in order:

Request → Firewall → Access → Guards → Masking → Response
            │          │        │         │
            │          │        │         └── Hide sensitive fields
            │          │        └── Block field modifications
            │          └── Check roles & conditions
            └── Isolate data by owner/org/team

Security Applies Everywhere

These security layers protect both API responses and Realtime broadcasts. When you enable Realtime, the same masking rules apply to WebSocket messages - ensuring users only see data they're authorized to view.

1. Firewall (Data Isolation)

The firewall controls which records a user can see. It automatically adds WHERE clauses to every query based on your schema columns:

Column in SchemaWhat happens
organization_idData isolated by organization
user_idData isolated by user (personal data)

No manual configuration needed - Quickback applies smart rules based on your schema.

Learn more about Firewall →

2. Access (CRUD Permissions)

Access controls which operations a user can perform. It checks roles and record conditions.

crud: {
  list:   { access: { roles: ["owner", "admin", "member"] } },
  create: { access: { roles: ["owner", "admin"] } },
  update: { access: { roles: ["owner", "admin"] } },
  delete: { access: { roles: ["owner", "admin"] } },
}

Learn more about Access →

3. Guards (Field Modification Rules)

Guards control which fields can be modified in each operation.

Guard TypeWhat it means
createableFields that can be set when creating
updatableFields that can be changed when updating
protectedFields that can only be changed via specific actions
immutableFields that can never be changed after creation

Learn more about Guards →

4. Masking (Data Redaction)

Masking hides sensitive fields from users who shouldn't see them.

masking: {
  ssn: { type: 'ssn' },           // Shows: *****1234
  email: { type: 'email' },       // Shows: j***@y*********.com
  salary: { type: 'redact' },     // Shows: [REDACTED]
}

Learn more about Masking →

How They Work Together

Scenario: A member requests GET /employees/123

  1. Firewall checks: Is employee 123 in the user's organization?

    • Yes → Continue
    • No → 404 Not Found (as if it doesn't exist)
  2. Access checks: Can members perform GET?

    • Yes → Continue
    • No → 403 Forbidden
  3. Guards don't apply to GET (they're for writes)

  4. Masking applies: User is a member, not admin

    • SSN: 123-45-6789*****6789
    • Salary: 85000[REDACTED]
  5. Response sent with masked data

Locked Down by Default

Quickback is secure by default. Nothing is accessible until you explicitly allow it.

LayerDefaultWhat you must do
FirewallAUTOAuto-detects from organization_id/user_id columns. Only configure for exceptions.
AccessDENIEDExplicitly define access rules with roles
GuardsLOCKEDExplicitly list createable, updatable fields
ActionsBLOCKEDExplicitly define access for each action

You must deliberately open each door. This prevents accidental data exposure.

Next Steps

  1. Database Schema — Define your tables
  2. Firewall — Set up data isolation
  3. Access — Configure CRUD permissions
  4. Guards — Control field modifications
  5. Masking — Hide sensitive data
  6. Views — Column-level security
  7. Validation — Field validation rules
  8. Actions — Add custom business logic

On this page