Skip to content

Quick Start

Get started with Quickback definitions in minutes. This guide walks you through the folder structure and shows a complete working example.

Folder Structure

definitions/
├── schema/                  # Database table definitions
│   ├── users.ts
│   └── rooms.ts
├── features/
│   └── {feature-name}/
│       ├── resource.ts      # Firewall, Guards, CRUD access, Masking
│       ├── actions.ts       # Custom actions/routes
│       └── handlers/        # Complex action handlers (optional)
│           └── my-action.ts

Complete Example

Here's a complete example defining a rooms resource with all security layers configured.

Schema Definition

First, define your database table using Drizzle ORM (see Database Schema for details):

typescript
// definitions/schema/rooms.ts
import { pgTable, text, integer, boolean, timestamp } from 'drizzle-orm/pg-core';

export const rooms = pgTable('rooms', {
  id: text('id').primaryKey(),
  name: text('name').notNull(),
  description: text('description'),
  capacity: integer('capacity').notNull().default(10),
  roomType: text('room_type').notNull(),
  isActive: boolean('is_active').notNull().default(true),
  deactivationReason: text('deactivation_reason'),

  // Ownership - Quickback auto-detects this for the firewall
  organizationId: text('organization_id').notNull(),
});

Resource Definition

typescript
// definitions/features/rooms/resource.ts
import { rooms } from '../../schema';
import { defineResource } from '@quickback/core';

export default defineResource(rooms, {
  guards: {
    createable: ["name", "description", "capacity", "roomType"],
    updatable: ["name", "description", "capacity"],
    protected: {
      isActive: ["activate", "deactivate"],
    },
  },

  crud: {
    list: {
      access: { roles: ["member", "admin"] },
      pageSize: 25,
    },
    get: {
      access: { roles: ["member", "admin"] },
    },
    create: {
      access: { roles: ["admin"] },
    },
    update: {
      access: { roles: ["admin"] },
    },
    delete: {
      access: { roles: ["admin"] },
      mode: "soft",
    },
  },
});

Actions Definition

typescript
// definitions/features/rooms/actions.ts
import { rooms } from '../../schema';
import { defineActions } from '@quickback/core';
import { eq } from 'drizzle-orm';
import { z } from 'zod';

export default defineActions(rooms, {
  activate: {
    description: "Activate a deactivated room",
    input: z.object({}),
    guard: {
      roles: ["admin"],
      record: { isActive: { equals: false } },
    },
    execute: async ({ db, record }) => {
      const [updated] = await db
        .update(rooms)
        .set({ isActive: true })
        .where(eq(rooms.id, record.id))
        .returning();
      return updated;
    },
  },

  deactivate: {
    description: "Deactivate a room",
    input: z.object({
      reason: z.string().optional(),
    }),
    guard: {
      roles: ["admin"],
      record: { isActive: { equals: true } },
    },
    execute: async ({ db, record, input }) => {
      const [updated] = await db
        .update(rooms)
        .set({ isActive: false, deactivationReason: input.reason })
        .where(eq(rooms.id, record.id))
        .returning();
      return updated;
    },
  },
});

What This Example Does

  1. Firewall: Quickback auto-detects organizationId in the schema and isolates data by organization. Users in Org A can never see Org B's data.
  2. Guards: Users can create rooms with name, description, capacity, roomType. Only name, description, capacity can be updated. The isActive field is protected and can only be changed via the activate and deactivate actions.
  3. Access: Members and admins can list and get rooms. Only admins can create, update, or delete.
  4. Actions: Two custom actions to activate/deactivate rooms with preconditions.

Next Steps

Dive deeper into each topic:

Backend security, simplified.