Models

Learn how to define data models and their relationships in Query-2jz.

What are Models?

Models in Query-2jz define the structure of your data. They automatically generate database tables, API endpoints, and TypeScript types. No need to write resolvers or database migrations manually.

Key Benefits

Auto-generated CRUD operations
Automatic database schema creation
TypeScript type generation
Relationship handling
Validation rules
Index optimization

Basic Model Definition

Here's how to define a simple model in your query-2jz.config.js:

{
  name: 'User',
  fields: {
    id: { type: 'id' },
    name: { type: 'string', required: true },
    email: { type: 'string', required: true, unique: true },
    age: { type: 'number' },
    isActive: { type: 'boolean', default: true },
    createdAt: { type: 'date' },
    updatedAt: { type: 'date' }
  },
  indexes: ['email', 'isActive']
}

Field Types

Query-2jz supports various field types with built-in validation and database mapping.

Basic Types

stringText data
numberNumeric data
booleanTrue/false values
dateDate and time
jsonJSON objects

Special Types

idUnique identifier
uuidUUID string
emailEmail validation
urlURL validation
enumPredefined values

Field Options

Configure field behavior with various options.

Validation Options

{
  name: { 
    type: 'string', 
    required: true,           // Field is required
    minLength: 2,            // Minimum length
    maxLength: 50,           // Maximum length
    pattern: '^[a-zA-Z ]+$'  // Regex pattern
  },
  email: { 
    type: 'email', 
    unique: true,            // Must be unique
    required: true 
  },
  age: { 
    type: 'number', 
    min: 0,                  // Minimum value
    max: 120                 // Maximum value
  }
}

Default Values

{
  isActive: { 
    type: 'boolean', 
    default: true 
  },
  status: { 
    type: 'enum', 
    values: ['pending', 'active', 'inactive'],
    default: 'pending'
  },
  createdAt: { 
    type: 'date', 
    default: 'now'           // Current timestamp
  }
}

Relationships

Define relationships between models for automatic join handling.

One-to-Many Relationship

A user can have many posts

// User model
{
  name: 'User',
  fields: {
    id: { type: 'id' },
    name: { type: 'string', required: true },
    posts: { 
      type: 'relation', 
      model: 'Post', 
      many: true,
      foreignKey: 'authorId'
    }
  }
}

// Post model
{
  name: 'Post',
  fields: {
    id: { type: 'id' },
    title: { type: 'string', required: true },
    authorId: { type: 'string', required: true },
    author: { 
      type: 'relation', 
      model: 'User',
      foreignKey: 'authorId'
    }
  }
}

Many-to-Many Relationship

Users can have many roles, roles can have many users

// User model
{
  name: 'User',
  fields: {
    id: { type: 'id' },
    name: { type: 'string', required: true },
    roles: { 
      type: 'relation', 
      model: 'Role', 
      many: true,
      through: 'UserRole'    // Junction table
    }
  }
}

// Role model
{
  name: 'Role',
  fields: {
    id: { type: 'id' },
    name: { type: 'string', required: true },
    users: { 
      type: 'relation', 
      model: 'User', 
      many: true,
      through: 'UserRole'
    }
  }
}

Indexes

Define database indexes for optimal query performance.

Single Field Indexes

{
  name: 'User',
  fields: {
    email: { type: 'string', unique: true },
    status: { type: 'string' }
  },
  indexes: ['email', 'status']  // Single field indexes
}

Composite Indexes

{
  name: 'Post',
  fields: {
    authorId: { type: 'string' },
    status: { type: 'string' },
    publishedAt: { type: 'date' }
  },
  indexes: [
    'authorId',
    'status',
    { fields: ['authorId', 'status'] },           // Composite index
    { fields: ['status', 'publishedAt'], unique: true }  // Unique composite
  ]
}

Model Hooks

Add custom logic that runs before or after model operations.

{
  name: 'User',
  fields: {
    id: { type: 'id' },
    name: { type: 'string', required: true },
    email: { type: 'email', required: true }
  },
  hooks: {
    beforeCreate: async (data) => {
      // Hash password before creating user
      if (data.password) {
        data.password = await hashPassword(data.password)
      }
      return data
    },
    afterCreate: async (user) => {
      // Send welcome email
      await sendWelcomeEmail(user.email)
      return user
    },
    beforeUpdate: async (data, oldData) => {
      // Validate business rules
      if (data.status === 'inactive' && oldData.status === 'active') {
        // Check if user has active posts
        const activePosts = await Post.count({ authorId: oldData.id, status: 'active' })
        if (activePosts > 0) {
          throw new Error('Cannot deactivate user with active posts')
        }
      }
      return data
    }
  }
}

Best Practices

Do

  • • Use descriptive field names
  • • Add indexes for frequently queried fields
  • • Use appropriate field types
  • • Define relationships explicitly
  • • Add validation rules
  • • Use hooks for business logic
  • • Keep models focused and cohesive

Don't

  • • Create overly complex models
  • • Skip validation rules
  • • Forget to add indexes
  • • Use generic field names
  • • Mix unrelated data in one model
  • • Ignore relationship constraints
  • • Hardcode business logic in models