Skip to content

How It All Fits Together

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 - Schema files (schema/rooms.ts) and feature configs (features/rooms/resource.ts, features/rooms/actions.ts)
  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, plus custom actions like POST /rooms/:id/activate

File Structure

Your definitions live in a definitions/ folder with two main areas:

definitions/
├── schema/                  # Database tables (Drizzle ORM)
│   ├── users.ts
│   └── rooms.ts

└── features/                # Feature-by-feature configuration
    └── {feature-name}/
        ├── resource.ts      # Security config (firewall, access, guards, masking)
        ├── actions.ts       # Custom actions/routes
        └── handlers/        # Complex action handlers (optional)
            └── my-action.ts

Schema files define your database tables using Drizzle ORM. These are the raw table structures.

Resource files configure how each table is exposed via the API - who can access it, what fields can be modified, and how data is isolated.

Action files define custom business logic beyond basic CRUD operations.

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

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. If a table has organization_id, users in Org A can never see Org B's data, even if they guess the record ID.

You only need to define a firewall when:

  • You need an exception for public/global data
  • Both organization_id and user_id exist and you need to clarify which takes priority

Learn more about Firewall →

2. Access (CRUD Permissions)

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

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

You can also add conditions like "only update your own records" or "only delete drafts".

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

Example: A status field might be protected - users can't directly PATCH it, but they can call an approve action that changes it.

Learn more about Guards →

4. Masking (Data Redaction)

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

typescript
masking: {
  ssn: { mask: 'ssn' },           // Shows: ***-**-1234
  email: { mask: 'email' },       // Shows: j***@example.com
  salary: { mask: 'redact' },     // Shows: [REDACTED]
}

You can conditionally show the real value based on roles or ownership.

Learn more about Masking →

How They Work Together

Here's a real example of how a request flows through all four layers:

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]
    • Email: john@company.comj***@company.com
  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 guard for each action

This means:

  • A resource with no definition = completely inaccessible
  • Forgot to add a field to createable? = 400 error on create
  • No access rule? = 403 forbidden

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

Next Steps

Now that you understand the mental model, dive into the specifics:

  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. Actions - Add custom business logic

Backend security, simplified.