Skip to main content

Overview

Models in Esix are TypeScript classes that represent your MongoDB collections. Each model extends BaseModel and automatically inherits powerful querying, CRUD operations, and aggregation methods.

Defining Models

Create a model by extending BaseModel and defining properties with default values:
import { BaseModel } from 'esix'

class User extends BaseModel {
  public name = ''
  public email = ''
  public age = 0
  public isActive = true
}

class BlogPost extends BaseModel {
  public title = ''
  public content = ''
  public authorId = ''
  public tags: string[] = []
  public publishedAt: Date | null = null
}
Default values are required for all properties. These values define both the property’s type and its default state when creating new instances.

Built-in Properties

Every model automatically includes these timestamp properties:
PropertyTypeDescription
idstringUnique identifier (MongoDB’s _id converted to string)
createdAtnumberTimestamp when the document was created
updatedAtnumber | nullTimestamp when the document was last updated
const user = await User.create({ name: 'John Smith', email: 'john@example.com' })

console.log(user.id)        // '507f1f77bcf86cd799439011'
console.log(user.createdAt) // 1672574400000
console.log(user.updatedAt) // null

Collection Names

Esix automatically determines the MongoDB collection name from your model class name using kebab-case with pluralization:
Model ClassCollection Name
Userusers
BlogPostblog-posts
Productproducts

Static Query Methods

All models inherit powerful static methods for querying and aggregation:

Basic Retrieval

// Get all documents
const users = await User.all()

// Find by ID
const user = await User.find('507f1f77bcf86cd799439011')

// Find by any field
const john = await User.findBy('email', 'john@example.com')

Query Building

// Filter with where clause
const activeUsers = await User.where('isActive', true).get()

// Comparison operators
const adults = await User.where('age', '>=', 18).get()
const youngUsers = await User.where('age', '<', 30).get()

// Multiple conditions
const youngActiveUsers = await User
  .where('isActive', true)
  .where('age', '<', 25)
  .get()

// Array queries
const specificUsers = await User.whereIn('id', ['id1', 'id2', 'id3']).get()
const nonAdmins = await User.whereNotIn('role', ['admin', 'moderator']).get()

Sorting and Pagination

// Order results
const orderedUsers = await User.orderBy('createdAt', 'desc').get()

// Limit results
const firstTen = await User.limit(10).get()

// Skip and limit for pagination
const page2 = await User.skip(10).limit(10).get()

Aggregations

// Count documents
const userCount = await User.count()

// Sum values
const totalAmount = await Order.sum('amount')

// Calculate average
const avgAge = await User.average('age')

// Min and max
const lowestPrice = await Product.min('price')
const highestScore = await Test.max('score')

// Percentiles
const p95 = await ResponseTime.percentile('value', 95)

// Extract field values
const titles = await BlogPost.pluck('title')

Custom Aggregations

Access MongoDB’s aggregation pipeline directly:
const results = await User.aggregate([
  { $group: { _id: '$department', count: { $sum: 1 } } },
  { $sort: { count: -1 } }
])

Instance Methods

Model instances provide methods for persistence and deletion:

Creating and Saving

// Create using static method
const user = await User.create({
  name: 'John Smith',
  email: 'john@example.com',
  age: 30
})

// Create instance and save
const post = new BlogPost()
post.title = 'My First Post'
post.content = 'Hello world!'
await post.save()

Updating

const user = await User.find(userId)
if (user) {
  user.age = 31
  await user.save() // Updates updatedAt timestamp automatically
}

Deleting

const user = await User.find(userId)
if (user) {
  await user.delete()
}

Relationships

Define relationships using the hasMany method:
class Author extends BaseModel {
  public name = ''

  // Define relationship
  blogPosts() {
    return this.hasMany(BlogPost, 'authorId')
  }
}

class BlogPost extends BaseModel {
  public title = ''
  public authorId = ''
}

// Usage
const author = await Author.find(authorId)
const posts = await author.blogPosts().get()

// Chain with queries
const publishedPosts = await author
  .blogPosts()
  .where('publishedAt', '!=', null)
  .orderBy('publishedAt', 'desc')
  .get()
The hasMany method accepts:
  • Model class: The related model type
  • Foreign key (optional): Defaults to camelCase(ModelName)Id
  • Local key (optional): Defaults to 'id'

First or Create

Find an existing document or create a new one atomically:
// Find by filter, create with additional attributes if not found
const user = await User.firstOrCreate(
  { email: 'john@example.com' },
  { name: 'John Smith', age: 30 }
)

// Use filter as attributes when not found
const settings = await Settings.firstOrCreate({
  userId: 'user123',
  theme: 'dark'
})

Type Safety

Esix leverages TypeScript’s type system to provide compile-time safety:
class User extends BaseModel {
  public name = ''
  public age = 0
}

// ✅ Type-safe queries
const users = await User.where('age', '>', 18).get() // User[]
const names = await User.pluck('name') // string[]

// ❌ Compile errors for invalid fields
const invalid = await User.where('invalidField', 'value') // TypeScript error

Best Practices

Use Meaningful Names

Choose descriptive model names that reflect your domain. Esix automatically converts them to appropriate collection names.

Set Appropriate Defaults

Default values should represent the most common initial state for new documents.

Leverage TypeScript

Define explicit types for complex properties like arrays and nullable fields.

Keep Models Focused

Each model should represent a single, well-defined entity in your domain.

Next Steps