Skip to content
Sutando
Esc
↑↓navigate↵open⌘Jpreview

Model Relationships in Sutando: hasMany, belongsTo, and Beyond

Relationships are the heart of any ORM. Sutando brings Laravel Eloquent's relationship system to Node.js, making it easy to define and query complex data…


Relationships are the heart of any ORM. Sutando brings Laravel Eloquent’s relationship system to Node.js, making it easy to define and query complex data relationships. This guide covers the supported relationship types with practical examples.

Defining Relationships

In Sutando, relationships are defined as methods on your model with a relation prefix:

class User extends Model {
  table = 'users';
  
  relationPosts() {
    return this.hasMany(Post, 'user_id');
  }
}

One-to-Many: hasMany

A user has many posts:

class User extends Model {
  table = 'users';
  relationPosts() {
    return this.hasMany(Post, 'user_id');
  }
}

class Post extends Model {
  table = 'posts';
  relationUser() {
    return this.belongsTo(User, 'user_id');
  }
}

Querying

// Eager load
const user = await User.query().with('posts').find(1);
console.log(user.posts); // Post[]

// Query the relationship
const user = await User.query().find(1);
const publishedPosts = await user.related('posts').where('published', true).get();

// Create related
const post = await user.related('posts').create({
  title: 'New Post',
  content: 'Hello!',
});

// Save related
const post = new Post();
post.title = 'New Post';
await user.related('posts').save(post);

One-to-One: hasOne

A user has one profile:

class User extends Model {
  table = 'users';
  relationProfile() {
    return this.hasOne(Profile, 'user_id');
  }
}

class Profile extends Model {
  table = 'profiles';
  relationUser() {
    return this.belongsTo(User, 'user_id');
  }
}
const user = await User.query().with('profile').find(1);
console.log(user.profile); // Profile

Many-to-Many: belongsToMany

A user belongs to many roles through a pivot table:

class User extends Model {
  table = 'users';
  relationRoles() {
    return this.belongsToMany(Role, 'user_roles', 'user_id', 'role_id');
  }
}

class Role extends Model {
  table = 'roles';
  relationUsers() {
    return this.belongsToMany(User, 'user_roles', 'role_id', 'user_id');
  }
}
// Get user with roles
const user = await User.query().with('roles').find(1);
console.log(user.roles); // Role[]

// Attach / detach
await user.related('roles').attach([1, 2, 3]); // attach role IDs
await user.related('roles').detach([2]); // detach role ID 2
await user.related('roles').sync([1, 3]); // sync: detach all others, attach these

// With pivot data
await user.related('roles').attach(1, { expires_at: '2026-12-31' });

Nested Eager Loading

Load relationships across multiple levels:

// Load user → posts → comments → user
const users = await User.query()
  .with('posts.comments.user')
  .get();

// Load specific relationships at each level
const users = await User.query()
  .with({
    posts: query => query.with('comments.user').where('published', true)
  })
  .get();

Preventing N+1 Queries

The N+1 problem is the most common ORM performance issue. Sutando solves it with eager loading:

// BAD: N+1 (1 + N queries)
const users = await User.query().get();
for (const user of users) {
  console.log(await user.getRelated('posts')); // 1 query per user
}

// GOOD: 2 queries total
const users = await User.query().with('posts').get();
for (const user of users) {
  console.log(user.posts); // already loaded
}

Relationship Methods Summary

Method Use Case Example
hasMany One-to-Many User → Posts
belongsTo Inverse One-to-Many Post → User
hasOne One-to-One User → Profile
belongsToMany Many-to-Many User ↔ Roles

Conclusion

Sutando’s relationship system mirrors Eloquent’s, making complex data relationships simple to define and query. With eager loading, you avoid N+1 problems while keeping your code clean and readable.

Learn more in the Sutando documentation.