Skip to content

Database Schema

Quickback uses Drizzle ORM to define your database schema. Drizzle provides type-safe database access with a SQL-like syntax that compiles to efficient queries.

What is Drizzle?

Drizzle is a TypeScript ORM that lets you define your database tables as code. Unlike traditional ORMs that abstract away SQL, Drizzle embraces SQL syntax while adding type safety. Your schema definitions become the source of truth for both your database structure and TypeScript types.

Defining Tables

Tables are defined using Drizzle's schema builders. Here's an example:

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),

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

Column Types

Drizzle supports all standard SQL column types:

TypeDrizzle FunctionExample
Stringtext(), varchar()text('name')
Integerinteger(), bigint()integer('count')
Booleanboolean()boolean('is_active')
Timestamptimestamp()timestamp('created_at')
JSONjson(), jsonb()jsonb('metadata')
UUIDuuid()uuid('id')
Decimaldecimal(), numeric()decimal('price', { precision: 10, scale: 2 })

Column Modifiers

typescript
// Required field
name: text('name').notNull()

// Default value
isActive: boolean('is_active').default(true)

// Primary key
id: text('id').primaryKey()

// Unique constraint
email: text('email').unique()

// Default to current timestamp
createdAt: timestamp('created_at').defaultNow()

Schema Organization

Organize your schemas by feature or domain:

definitions/
├── schema/
│   ├── index.ts          # Re-exports all schemas
│   ├── users.ts          # User-related tables
│   ├── rooms.ts          # Room-related tables
│   ├── bookings.ts       # Booking-related tables
│   └── organizations.ts  # Organization tables

Re-export all schemas from an index file:

typescript
// definitions/schema/index.ts
export * from './users';
export * from './rooms';
export * from './bookings';
export * from './organizations';

Audit Fields

Quickback automatically adds and manages these audit fields - you don't need to define them in your schema:

FieldTypeDescription
createdAttimestampSet when record is created
createdBytextUser ID who created the record
modifiedAttimestampUpdated on every change
modifiedBytextUser ID who last modified
deletedAttimestampSet on soft delete
deletedBytextUser ID who deleted

These columns are added to your database automatically and populated by Quickback on every operation.

Ownership Fields

For the firewall to work, include the appropriate ownership columns:

typescript
// For user-owned data
ownerId: text('owner_id').notNull()

// For organization-scoped data
organizationId: text('organization_id').notNull()

// For team-scoped data
teamId: text('team_id').notNull()

The column names can be customized in your firewall configuration.

Relations (Optional)

Drizzle supports defining relations for type-safe joins:

typescript
import { relations } from 'drizzle-orm';

export const roomsRelations = relations(rooms, ({ one, many }) => ({
  organization: one(organizations, {
    fields: [rooms.organizationId],
    references: [organizations.id],
  }),
  bookings: many(bookings),
}));

Database Configuration

Configure database options in your Quickback config:

typescript
// quickback.config.ts
export default {
  database: {
    generateId: 'uuid',           // 'uuid' | 'cuid' | 'nanoid' | 'serial' | false
    namingConvention: 'camelCase', // 'camelCase' | 'snake_case'
    usePlurals: true,              // Table names: 'users' vs 'user'
  },
  compiler: {
    features: {
      auditFields: true,           // Auto-manage audit timestamps
    }
  }
};

Next Steps

Once your schema is defined, you can:

Backend security, simplified.