Esix provides a simple way to define relationships between models using the hasMany() method. This allows you to express one-to-many relationships and easily query related records.
The hasMany() method defines a one-to-many relationship where the parent model has multiple related child models. It returns a QueryBuilder that you can chain with other query methods.
class User extends BaseModel { public name = '' public email = ''}class Post extends BaseModel { public title = '' public content = '' public userId = '' // Foreign key}// Get all posts for a userconst user = await User.find('user-id-123')const posts = await user.hasMany(Post).get()
You can specify a custom foreign key if your relationship doesn’t follow the default naming convention:
class Author extends BaseModel { public name = ''}class Book extends BaseModel { public title = '' public authorId = '' // Custom foreign key}const author = await Author.find('author-123')const books = await author.hasMany(Book, 'authorId').get()
You can also specify a custom local key if you want to join on a field other than id:
class Department extends BaseModel { public code = '' public name = ''}class Employee extends BaseModel { public name = '' public departmentCode = ''}const department = await Department.findBy('code', 'ENG')const employees = await department.hasMany(Employee, 'departmentCode', 'code').get()
Since hasMany() returns a QueryBuilder, you can chain additional query methods to filter, sort, or limit the results:
const user = await User.find('user-123')// Get only published postsconst publishedPosts = await user .hasMany(Post) .where('status', 'published') .get()// Get the latest 5 postsconst latestPosts = await user .hasMany(Post) .orderBy('createdAt', 'desc') .limit(5) .get()// Count total postsconst postCount = await user.hasMany(Post).count()// Get post titles onlyconst titles = await user.hasMany(Post).pluck('title')
class User extends BaseModel { public username = '' public email = ''}class Post extends BaseModel { public title = '' public content = '' public userId = '' public status = 'draft' // 'draft' | 'published'}class Comment extends BaseModel { public content = '' public postId = '' public userId = ''}// Get all posts by a userconst user = await User.findBy('username', 'john')const posts = await user.hasMany(Post).get()// Get published posts onlyconst publishedPosts = await user .hasMany(Post) .where('status', 'published') .orderBy('createdAt', 'desc') .get()// Get comments for a postconst post = await Post.find('post-123')const comments = await post.hasMany(Comment).get()
class Customer extends BaseModel { public name = '' public email = ''}class Order extends BaseModel { public customerId = '' public status = 'pending' public total = 0}class OrderItem extends BaseModel { public orderId = '' public productName = '' public quantity = 0 public price = 0}// Get all orders for a customerconst customer = await Customer.findBy('email', 'customer@example.com')const orders = await customer.hasMany(Order).get()// Get completed orders onlyconst completedOrders = await customer .hasMany(Order) .where('status', 'completed') .orderBy('createdAt', 'desc') .get()// Calculate total spendingconst totalSpent = await customer.hasMany(Order).sum('total')// Get items for an orderconst order = await Order.find('order-456')const items = await order.hasMany(OrderItem).get()
The hasMany() method is implemented in the BaseModel class (base-model.ts:429-440). It:
Creates a new QueryBuilder for the related model
Automatically determines or uses the provided foreign key
Automatically determines or uses the provided local key
Adds a where clause matching the foreign key to the local key value
Returns the QueryBuilder for further chaining
Behind the scenes, hasMany() uses the QueryBuilder’s where() method to filter related records. All sanitization and security measures apply automatically.