Quickback Docs

Complete Example

See what you define and what Quickback generates

This example shows a complete resource definition using all five security layers, and the production code Quickback generates from it.

What You Define

A customers resource for a multi-tenant SaaS application with:

  • Organization-level data isolation
  • Role-based CRUD permissions
  • Field-level write protection
  • PII masking for sensitive fields
  • Column-level views for different access levels

Schema

definitions/features/customers/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const customers = sqliteTable('customers', {
  id: text('id').primaryKey(),
  organizationId: text('organization_id').notNull(),
  name: text('name').notNull(),
  email: text('email').notNull(),
  phone: text('phone'),
  ssn: text('ssn'),
});

// Note: createdAt, createdBy, modifiedAt, modifiedBy are auto-injected

Resource Configuration

definitions/features/customers/resource.ts
import { defineResource } from '@quickback/define';
import { customers } from './schema';

export default defineResource(customers, {
  // FIREWALL: Data isolation
  firewall: {
    organization: {}
  },

  // ACCESS: Role-based permissions
  crud: {
    list:   { access: { roles: ['owner', 'admin', 'member', 'support'] } },
    get:    { access: { roles: ['owner', 'admin', 'member', 'support'] } },
    create: { access: { roles: ['admin'] } },
    update: { access: { roles: ['admin', 'support'] } },
    delete: { access: { roles: ['admin'] }, mode: 'hard' },
  },

  // GUARDS: Field-level protection
  guards: {
    createable: ['name', 'email', 'phone', 'ssn'],
    updatable: ['name', 'email', 'phone'],
    immutable: ['ssn'],  // Can set once, never change
  },

  // MASKING: PII redaction
  masking: {
    ssn:   { type: 'ssn',   show: { roles: ['admin'] } },
    phone: { type: 'phone', show: { roles: ['admin', 'support'] } },
    email: { type: 'email', show: { roles: ['admin'] } },
  },

  // VIEWS: Column-level projections
  views: {
    summary: {
      fields: ['id', 'name', 'email'],
      access: { roles: ['owner', 'admin', 'member', 'support'] },
    },
    full: {
      fields: ['id', 'name', 'email', 'phone', 'ssn'],
      access: { roles: ['owner', 'admin'] },
    },
  },
});

What Quickback Generates

Run quickback compile and Quickback generates production-ready code.

Security Helpers

src/features/customers/customers.resource.ts
import { eq, and } from 'drizzle-orm';
import { customers } from './schema';
import type { AppContext } from '../../lib/types';
import { masks } from '../../lib/masks';

/**
 * Firewall conditions for customers
 * Pattern: Organization only
 */
export function buildFirewallConditions(ctx: AppContext) {
  const conditions = [];

  // Organization isolation
  conditions.push(eq(customers.organizationId, ctx.activeOrgId!));

  return and(...conditions);
}

/**
 * Guards configuration for field modification rules
 */
export const GUARDS_CONFIG = {
  createable: new Set(['name', 'email', 'phone', 'ssn']),
  updatable: new Set(['name', 'email', 'phone']),
  immutable: new Set(['ssn']),
  protected: {},
  systemManaged: new Set([
    'createdAt', 'createdBy',
    'modifiedAt', 'modifiedBy',
    'deletedAt', 'deletedBy'
  ]),
};

/**
 * Validates field input for CREATE operations.
 */
export function validateCreate(input: Record<string, any>) {
  const systemManaged: string[] = [];
  const protectedFields: Array<{ field: string; actions: string[] }> = [];
  const notCreateable: string[] = [];

  for (const field of Object.keys(input)) {
    if (GUARDS_CONFIG.systemManaged.has(field)) {
      systemManaged.push(field);
      continue;
    }
    if (field in GUARDS_CONFIG.protected) {
      const actions = [...GUARDS_CONFIG.protected[field]];
      protectedFields.push({ field, actions });
      continue;
    }
    if (!GUARDS_CONFIG.createable.has(field) && !GUARDS_CONFIG.immutable.has(field)) {
      notCreateable.push(field);
    }
  }

  const valid = systemManaged.length === 0 &&
                protectedFields.length === 0 &&
                notCreateable.length === 0;

  return { valid, systemManaged, protected: protectedFields, notCreateable };
}

/**
 * Validates field input for UPDATE operations.
 */
export function validateUpdate(input: Record<string, any>) {
  const systemManaged: string[] = [];
  const immutable: string[] = [];
  const protectedFields: Array<{ field: string; actions: string[] }> = [];
  const notUpdatable: string[] = [];

  for (const field of Object.keys(input)) {
    if (GUARDS_CONFIG.systemManaged.has(field)) {
      systemManaged.push(field);
      continue;
    }
    if (GUARDS_CONFIG.immutable.has(field)) {
      immutable.push(field);
      continue;
    }
    if (field in GUARDS_CONFIG.protected) {
      const actions = [...GUARDS_CONFIG.protected[field]];
      protectedFields.push({ field, actions });
      continue;
    }
    if (!GUARDS_CONFIG.updatable.has(field)) {
      notUpdatable.push(field);
    }
  }

  const valid = systemManaged.length === 0 &&
                immutable.length === 0 &&
                protectedFields.length === 0 &&
                notUpdatable.length === 0;

  return { valid, systemManaged, immutable, protected: protectedFields, notUpdatable };
}

/**
 * Masks sensitive fields based on user role
 */
export function maskCustomer<T extends Record<string, any>>(
  record: T,
  ctx: AppContext
): T {
  const masked: any = { ...record };

  // email: show to admin only
  if (!ctx.roles?.includes('admin')) {
    if (masked['email'] != null) {
      masked['email'] = masks.email(masked['email']);
    }
  }

  // phone: show to admin, support
  if (!ctx.roles?.some(r => ['admin', 'support'].includes(r))) {
    if (masked['phone'] != null) {
      masked['phone'] = masks.phone(masked['phone']);
    }
  }

  // ssn: show to admin only
  if (!ctx.roles?.includes('admin')) {
    if (masked['ssn'] != null) {
      masked['ssn'] = masks.ssn(masked['ssn']);
    }
  }

  return masked as T;
}

export function maskCustomers<T extends Record<string, any>>(
  records: T[],
  ctx: AppContext
): T[] {
  return records.map(r => maskCustomer(r, ctx));
}

// CRUD Access configuration
export const CRUD_ACCESS = {
  list:   { access: { roles: ['owner', 'admin', 'member', 'support'] } },
  get:    { access: { roles: ['owner', 'admin', 'member', 'support'] } },
  create: { access: { roles: ['admin'] } },
  update: { access: { roles: ['admin', 'support'] } },
  delete: { access: { roles: ['admin'] }, mode: 'hard' },
};

// Views configuration
export const VIEWS_CONFIG = {
  summary: {
    fields: ['id', 'name', 'email'],
    access: { roles: ['owner', 'admin', 'member', 'support'] },
  },
  full: {
    fields: ['id', 'name', 'email', 'phone', 'ssn'],
    access: { roles: ['owner', 'admin'] },
  },
};

API Routes

The generated routes wire everything together:

src/features/customers/customers.routes.ts
import { Hono } from 'hono';
import { eq, and } from 'drizzle-orm';
import { customers } from './schema';
import {
  buildFirewallConditions,
  validateCreate,
  validateUpdate,
  maskCustomer,
  maskCustomers,
  CRUD_ACCESS
} from './customers.resource';
import { evaluateAccess } from '../../lib/access';
import { AuthErrors, AccessErrors, GuardErrors } from '../../lib/errors';

const app = new Hono();

// GET /customers - List with firewall + masking
app.get('/', async (c) => {
  const ctx = c.get('ctx');
  const db = c.get('db');

  // Access check
  if (!await evaluateAccess(CRUD_ACCESS.list.access, ctx)) {
    return c.json(AccessErrors.roleRequired(
      CRUD_ACCESS.list.access.roles,
      ctx.roles
    ), 403);
  }

  // Query with firewall conditions
  const results = await db.select().from(customers)
    .where(buildFirewallConditions(ctx));

  // Apply masking before returning
  return c.json({
    data: maskCustomers(results, ctx),
  });
});

// POST /customers - Create with guards
app.post('/', async (c) => {
  const ctx = c.get('ctx');
  const db = c.get('db');

  // Access check
  if (!await evaluateAccess(CRUD_ACCESS.create.access, ctx)) {
    return c.json(AccessErrors.roleRequired(
      CRUD_ACCESS.create.access.roles,
      ctx.roles
    ), 403);
  }

  const body = await c.req.json();

  // Guards validation
  const validation = validateCreate(body);
  if (!validation.valid) {
    if (validation.systemManaged.length > 0) {
      return c.json(GuardErrors.systemManaged(validation.systemManaged), 400);
    }
    if (validation.notCreateable.length > 0) {
      return c.json(GuardErrors.fieldNotCreateable(validation.notCreateable), 400);
    }
  }

  // Apply ownership and audit fields
  const data = {
    id: 'cst_' + crypto.randomUUID().replace(/-/g, ''),
    ...body,
    organizationId: ctx.activeOrgId,
    createdAt: new Date().toISOString(),
    createdBy: ctx.userId,
    modifiedAt: new Date().toISOString(),
    modifiedBy: ctx.userId,
  };

  const result = await db.insert(customers).values(data).returning();
  return c.json(maskCustomer(result[0], ctx), 201);
});

// PATCH /customers/:id - Update with guards
app.patch('/:id', async (c) => {
  const ctx = c.get('ctx');
  const db = c.get('db');
  const id = c.req.param('id');

  // Fetch with firewall
  const [record] = await db.select().from(customers)
    .where(and(buildFirewallConditions(ctx), eq(customers.id, id)));

  if (!record) {
    return c.json({ error: 'Not found', code: 'NOT_FOUND' }, 404);
  }

  // Access check
  if (!await evaluateAccess(CRUD_ACCESS.update.access, ctx)) {
    return c.json(AccessErrors.roleRequired(
      CRUD_ACCESS.update.access.roles,
      ctx.roles
    ), 403);
  }

  const body = await c.req.json();

  // Guards validation
  const validation = validateUpdate(body);
  if (!validation.valid) {
    if (validation.immutable.length > 0) {
      return c.json(GuardErrors.fieldImmutable(validation.immutable), 400);
    }
    if (validation.notUpdatable.length > 0) {
      return c.json(GuardErrors.fieldNotUpdatable(validation.notUpdatable), 400);
    }
  }

  // Apply audit fields
  const data = {
    ...body,
    modifiedAt: new Date().toISOString(),
    modifiedBy: ctx.userId,
  };

  const result = await db.update(customers).set(data)
    .where(eq(customers.id, id)).returning();

  return c.json(maskCustomer(result[0], ctx));
});

export default app;

Runtime Behavior

Masking by Role

Roleemailphonessn
adminjohn@example.com555-123-4567123-45-6789
supportj***@e******.com555-123-4567*****6789
memberj***@e******.com******4567*****6789

Views + Masking Interaction

Views control which fields are returned. Masking controls what values are shown.

EndpointRolessn in response?ssn value
/customers/views/summarymemberNoN/A
/customers/views/summaryadminNoN/A
/customers/views/fullmemberYes*****6789
/customers/views/fulladminYes123-45-6789

Guards Enforcement

# ✅ Allowed: Create with createable fields
POST /customers
{ "name": "Jane", "email": "jane@example.com", "ssn": "987-65-4321" }

# ❌ Rejected: Update immutable field
PATCH /customers/cst_123
{ "ssn": "000-00-0000" }
# → 400: "Field 'ssn' is immutable and cannot be modified"

# ❌ Rejected: Set system-managed field
POST /customers
{ "name": "Jane", "createdBy": "hacker" }
# → 400: "System-managed fields cannot be set: createdBy"

Generated API Endpoints

MethodEndpointAccess
GET/customersowner, admin, member, support
GET/customers/:idowner, admin, member, support
POST/customersadmin
PATCH/customers/:idadmin, support
DELETE/customers/:idadmin
GET/customers/views/summaryowner, admin, member, support
GET/customers/views/fulladmin

Key Takeaways

  1. ~50 lines of configuration generates ~500+ lines of production code
  2. Security is declarative - you describe what you want, not how
  3. All layers work together - Firewall → Access → Guards → Masking
  4. Code is readable and auditable - no magic, just TypeScript
  5. Type-safe from definition to API - Drizzle schema drives everything

On this page