Skip to main content
Version: v3.x.x

Mongoloquent: Relationships

Introduction

Database tables are often related to one another. For example, a blog post may have many comments or an order could be related to the user who placed it. Mongoloquent makes managing and working with these relationships easy, and supports a variety of common relationships:

Defining Relationships

Mongoloquent relationships are defined as methods on your Mongoloquent model classes. Since relationships also serve as powerful query builders, defining relationships as methods provides powerful method chaining and querying capabilities. For example, we may chain additional query constraints on this posts relationship:

const posts = await user.posts().where('active', true).get();

But, before diving too deep into using relationships, let's learn how to define each type of relationship supported by Mongoloquent.

One to One / Has One

A one-to-one relationship is a very basic type of database relationship. For example, a User model might be associated with one Phone model. To define this relationship, we will place a phone method on the User model. The phone method should call the hasOne method and return its result. The hasOne method is available to your model via the model's Mongoloquent Model base class:

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from "mongoloquent";
import Phone from './Models/Phone';

interface IUser extends IMongoloquentSchema, IMongoloquentTimestamps {
name: string
}

class User extends Model<IUser> {
/**
* The attributes of the model.
*
* @var IUser
*/
public static $schema: IUser

/**
* Get the phone associated with the user.
*/
public phone(){
return this.hasOne(Phone);
}
}

The first argument passed to the hasOne method is the name of the related model class. Once the relationship is defined, we may retrieve the related record using Mongoloquent's dynamic methods. Dynamic methods allow you to access relationship methods as if they were methods defined on the model:

const phone = await User.find("10ab7e3d05d58a1ad246ee87").phone().first();

Mongoloquent determines the foreign key of the relationship based on the parent model name. In this case, the Phone model is automatically assumed to have a userId foreign key. If you wish to override this convention, you may pass a second argument to the hasOne method:

return this.hasOne(Phone, 'foreign_key');

Additionally, Mongoloquent assumes that the foreign key should have a value matching the primary key column of the parent. In other words, Mongoloquent will look for the value of the user's id column in the userId column of the Phone record. If you would like the relationship to use a primary key value other than _id, you may pass a third argument to the hasOne method:

return this.hasOne(Phone, 'foreign_key', 'local_key');

Defining the Inverse of the Relationship

So, we can access the Phone model from our User model. Next, let's define a relationship on the Phone model that will let us access the user that owns the phone. We can define the inverse of a hasOne relationship using the belongsTo method:

import { ObjectID } from 'mongodb';
import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from "mongoloquent";
import User from './Models/User';

interface IPhone extends IMongoloquentSchema, IMongoloquentTimestamps {
number: string
userId: ObjectID
}

class Phone extends Model<IPhone> {
/**
* The attributes of the model.
*
* @var IPhone
*/
public static $schema: IPhone

/**
* Get the user that owns the phone.
*/
public user(){
return this.belongsTo(User);
}
}

When invoking the user method, Mongoloquent will attempt to find a User model that has an _id which matches the userId column on the Phone model.

Mongoloquent determines the foreign key name by examining the name of the relationship method and suffixing the method name with Id. So, in this case, Mongoloquent assumes that the Phone model has a userId column. However, if the foreign key on the Phone model is not userId, you may pass a custom key name as the second argument to the belongsTo method:

/**
* Get the user that owns the phone.
*/
public user(){
return this.belongsTo(User, 'foreign_key');
}

If the parent model does not use _id as its primary key, or you wish to find the associated model using a different column, you may pass a third argument to the belongsTo method specifying the parent table's custom key:

/**
* Get the user that owns the phone.
*/
public user(){
return this.belongsTo(User, 'foreign_key', 'owner_key');
}

One to Many / Has Many

A one-to-many relationship is used to define relationships where a single model is the parent to one or more child models. For example, a blog post may have an infinite number of comments. Like all other Mongoloquent relationships, one-to-many relationships are defined by defining a method on your Mongoloquent model:

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Comment from './Models/Comment';

interface IPost extends IMongoloquentSchema, IMongoloquentTimestamps {
title: string
}

class Post extends Model<IPost> {
/**
* The attributes of the model.
*
* @var IPost
*/
public static $schema: IPost

/**
* Get the comments for the post.
*/
public comments(){
return this.hasMany(Comment);
}
}

Remember, Mongoloquent will automatically determine the proper foreign key column for the Comment model. By convention, Mongoloquent will take the "camel case" name of the parent model and suffix it with Id. So, in this example, Mongoloquent will assume the foreign key column on the Comment model is postId.

Once the relationship method has been defined, we can access the collection of related comments by accessing the comments property. Remember, since Mongoloquent provides "dynamic relationship methods", we can access relationship methods as if they were defined as methods on the model:

import Post from './Models/Post';

const post = await Post.find("10ab7e3d05d58a1ad246ee87")
const comments = await post.comments().get();

comments.forEach(comment => {
console.log(comment.body);
});

Since all relationships also serve as query builders, you may add further constraints to the relationship query by calling the comments method and continuing to chain conditions onto the query:

const post = await Post.find("10ab7e3d05d58a1ad246ee87");

const comment = await post.comments()
.where('title', 'foo')
.first();

Like the hasOne method, you may also override the foreign and local keys by passing additional arguments to the hasMany method:

return this.hasMany(Comment, 'foreign_key');

return this.hasMany(Comment, 'foreign_key', 'local_key');

One to Many (Inverse) / Belongs To

Now that we can access all of a post's comments, let's define a relationship to allow a comment to access its parent post. To define the inverse of a hasMany relationship, define a relationship method on the child model which calls the belongsTo method:

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Post from './Models/Post';

interface IComment extends IMongoloquentSchema, IMongoloquentTimestamps {
body: string
postId: ObjectID
}

class Comment extends Model<IComment> {
/**
* The attributes of the model.
*
* @var IComment
*/
public static $schema: IComment

/**
* Get the post that owns the comment.
*/
public post(){
return this.belongsTo(Post);
}
}

Once the relationship has been defined, we can retrieve a comment's parent post by accessing the post "dynamic relationship method":

import Comment from './Models/Comment';

const comment = await Comment.find("10ab7e3d05d58a1ad246ee87");

const post = await comment.post().first();

In the example above, Mongoloquent will attempt to find a Post model that has an _id which matches the postId column on the Comment model.

Mongoloquent determines the default foreign key name by examining the name of the relationship method and suffixing the method name followed by the name of the parent model's primary key column. So, in this example, Mongoloquent will assume the Post model's foreign key on the comments table is postId.

However, if the foreign key for your relationship does not follow these conventions, you may pass a custom foreign key name as the second argument to the belongsTo method:

/**
* Get the post that owns the comment.
*/
public post(){
return this.belongsTo(Post, 'foreign_key');
}

If your parent model does not use _id as its primary key, or you wish to find the associated model using a different column, you may pass a third argument to the belongsTo method specifying your parent table's custom key:

/**
* Get the post that owns the comment.
*/
public post(){
return this.belongsTo(Post, 'foreign_key', 'owner_key');
}

Has Many Through

The "has-many-through" relationship provides a convenient way to access distant relations via an intermediate relation. For example, let's assume we are building a deployment platform. An Application model might access many Deployment models through an intermediate Environment model. Using this example, you could easily gather all deployments for a given application. Let's look at the tables required to define this relationship:

applications
_id - ObjectID
name - string

environments
_id - ObjectID
applicationId - ObjectID
name - string

deployments
_id - ObjectID
environmentId - ObjectID
commit_hash - string

Now that we have examined the table structure for the relationship, let's define the relationship on the Application model:

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Deployment from './Models/Deployment';
import Environment from './Models/Environment';

interface IApplication extends IMongoloquentSchema, IMongoloquentTimestamps {
name: string
}

class Application extends Model<IApplication> {
/**
* Get all of the deployments for the application.
*/
public deployments(){
return this.hasManyThrough(Deployment, Environment);
}
}

The first argument passed to the hasManyThrough method is the name of the final model we wish to access, while the second argument is the name of the intermediate model.

Key Conventions

Typical Mongoloquent foreign key conventions will be used when performing the relationship's queries. If you would like to customize the keys of the relationship, you may pass them as the third and fourth arguments to the hasManyThrough method. The third argument is the name of the foreign key on the intermediate model. The fourth argument is the name of the foreign key on the final model. The fifth argument is the local key, while the sixth argument is the local key of the intermediate model:

class Application extends Model{

public deployments(){
return this.hasManyThrough(
Deployment,
Environment,
'applicationId', // Foreign key on the environments table...
'environmentId', // Foreign key on the deployments table...
'_id', // Local key on the applications table...
'_id' // Local key on the environments table...
);
}

}

Scoped Relationships

It's common to add additional methods to models that constrain relationships. For example, you might add a featuredPosts method to a User model which constrains the broader posts relationship with an additional where constraint:

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Post from './Models/Post';

interface IUser extends IMongoloquentSchema, IMongoloquentTimestamps {
name: string
}

class User extends Model<IUser> {
/**
* The attributes of the model.
*
* @var IUser
*/
public static $schema: IUser

/**
* Get all of the user's posts.
*/
public posts(){
return this.hasMany(Post, 'foreign_key');
}

/**
* Get all of the user's featured posts.
*/
public featuredPosts(){
return this.posts().where('featured', true);
}
}

Many to Many Relationships

Many-to-many relations are slightly more complicated than hasOne and hasMany relationships. An example of a many-to-many relationship is a user that has many roles and those roles are also shared by other users in the application. For example, a user may be assigned the role of "Author" and "Editor"; however, those roles may also be assigned to other users as well. So, a user has many roles and a role has many users.

Table Structure

To define this relationship, three database tables are needed: users, roles, and role_user. The role_user table is derived from the alphabetical order of the related model names and contains userId and roleId columns. This table is used as an intermediate table linking the users and roles.

Remember, since a role can belong to many users, we cannot simply place a userId column on the roles table. This would mean that a role could only belong to a single user. In order to provide support for roles being assigned to multiple users, the role_user table is needed. We can summarize the relationship's table structure like so:

users
_id - ObjectID
name - string

roles
_id - ObjectID
name - string

role_user
userId - ObjectID
roleId - ObjectID

Model Structure

Many-to-many relationships are defined by writing a method that returns the result of the belongsToMany method. The belongsToMany method is provided by the Model base class that is used by all of your application's Mongoloquent models. For example, let's define a roles method on our User model. The first argument passed to this method is the name of the related model class:

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Role from './Models/Role';

interface IUser extends IMongoloquentSchema, IMongoloquentTimestamps {
name: string
}

class User extends Model<IUser> {
/**
* The roles that belong to the user.
*/
public roles(){
return this.belongsToMany(Role);
}
}

Once the relationship is defined, you may access the user's roles using the roles dynamic relationship property:

import User from './Models/User';

const user = await User.find("10ab7e3d05d58a1ad246ee87");

const roles = await user.roles().get();

roles.forEach(role => {
console.log(role.name);
});

Since all relationships also serve as query builders, you may add further constraints to the relationship query by calling the roles method and continuing to chain conditions onto the query:

const user = await User.find("10ab7e3d05d58a1ad246ee87");

const roles = await user.roles().orderBy('name').get();

To determine the table name of the relationship's intermediate table, Mongoloquent will join the two related model names in alphabetical order. However, you are free to override this convention. You may do so by passing a second argument to the belongsToMany method:

return this.belongsToMany(Role, "role_user");

In addition to customizing the name of the intermediate table, you may also customize the column names of the keys on the table by passing additional arguments to the belongsToMany method. The third argument is the foreign key name of the model on which you are defining the relationship, while the fourth argument is the foreign key name of the model that you are joining to:

return this.belongsToMany(Role, "role_user", "userId", "roleId");

Defining the Inverse of the Relationship

To define the "inverse" of a many-to-many relationship, you should define a method on the related model which also returns the result of the belongsToMany method. To complete our user / role example, let's define the users method on the Role model:

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import User from './Models/User';

interface IRole extends IMongoloquentSchema, IMongoloquentTimestamps {
name: string
}

class Role extends Model<IRole> {
/**
* The attributes of the model.
*
* @var IRole
*/
public static $schema: IRole

/**
* The users that belong to the role.
*/
public users(){
return this.belongsToMany(User);
}
}

As you can see, the relationship is defined exactly the same as its User model counterpart with the exception of referencing the Models/User model. Since we're reusing the belongsToMany method, all of the usual table and key customization options are available when defining the "inverse" of many-to-many relationships.

Polymorphic Relationships

A polymorphic relationship allows the child model to belong to more than one type of model using a single association. For example, imagine you are building an application that allows users to share blog posts and videos. In such an application, a Comment model might belong to both the Post and Video models.

One to One (Polymorphic)

Table Structure

A one-to-one polymorphic relation is similar to a typical one-to-one relation; however, the child model can belong to more than one type of model using a single association. For example, a blog Post and a User may share a polymorphic relation to an Image model. Using a one-to-one polymorphic relation allows you to have a single table of unique images that may be associated with posts and users. First, let's examine the table structure:

posts
_id - ObjectID
name - string

users
_id - ObjectID
name - string

images
_id - ObjectID
url - string
imageableId - ObjectID
imageableType - string

Note the imageableId and imageableType columns on the images collection. The imageableId column will contain the ID value of the post or user, while the imageableType column will contain the class name of the parent model. The imageableType column is used by Mongoloquent to determine which "type" of parent model to return when accessing the imageable relation. In this case, the column would contain either Post or User.

Model Structure

Next, let's examine the model definitions needed to build this relationship:

// Image.ts
import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';

interface IImage, IMongoloquentSchema, IMongoloquentTimestamps {
url: string
imageableId: ObjectID
imageableType: string
}

class Image extends Model<IImage> {
/**
* The attributes of the model.
*
* @var IImage
*/
public static $schema: IImage

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'images';
}


// Post.ts
import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Image from './Models/Image';

interface IPost extends IMongoloquentSchema, IMongoloquentTimestamps {
title: string
}

class Post extends Model<IPost> {
/**
* The attributes of the model.
*
* @var IPost
*/
public static $schema: IPost

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'posts';

/**
* Get the post's image.
*/
public image(){
return this.morphTo(Image, 'imageable');
}
}


// User.ts
import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Image from './Models/Image';

interface IUser extends IMongoloquentSchema, IMongoloquentTimestamps {
name: string
}

class User extends Model<IUser> {
/**
* The attributes of the model.
*
* @var IUser
*/
public static $schema: IUser

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'users';

/**
* Get the user's image.
*/
public image(){
return this.morphTo(Image, 'imageable');
}
}

Retrieving the Relationship

Once your database table and models are defined, you may access the relationships via your models. For example, to retrieve the image for a post, we can access the image dynamic relationship property:

import Post from './Models/Post';

const post = await Post.find("10ab7e3d05d58a1ad246ee87");

const image = await post.image().first();

One to Many (Polymorphic)

A one-to-many polymorphic relation is similar to a typical one-to-many relation; however, the child model can belong to more than one type of model using a single association. For example, imagine users of your application can "comment" on posts and videos. Using polymorphic relationships, you may use a single comments table to contain comments for both posts and videos. First, let's examine the table structure required to build this relationship:

posts
_id - ObjectID
title - string
body - text

videos
_id - ObjectID
title - string
url - string

comments
_id - ObjectID
body - text
commentableId - ObjectID
commentableType - string
// Comment.ts
import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import { ObjectID } from "mongodb"

interface IComment extends IMongoloquentSchema, IMongoloquentTimestamps {
body: string
commentableId: ObjectID
commentableType: string
}

class Comment extends Model<IComment> {
/**
* The attributes of the model.
*
* @var IComment
*/
public static $schema: IComment

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'comments';
}


// Post.ts
import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Comment from './Models/Comment';

interface IPost extends IMongoloquentSchema, IMongoloquentTimestamps {
title: string
body: string
}

class Post extends Model<IPost> {
/**
* The attributes of the model.
*
* @var IPost
*/
public static $schema: IPost

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'posts';

/**
* Get all of the post's comments.
*/
public comments(){
return this.morphMany(Comment, 'commentable');
}
}


// Video.ts
import { Model } from 'mongoloquent';
import Comment from './Models/Comment';

interface IVideo extends IMongoloquentSchema, IMongoloquentTimestamps {
title: string
url: string
}

class Video extends Model<IVideo> {
/**
* The attributes of the model.
*
* @var IVideo
*/
public static $schema: IVideo

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'videos';

/**
* Get all of the video's comments.
*/
public comments(){
return this.morphMany(Comment, 'commentable');
}
}

Retrieving the Relationship

Once your database table and models are defined, you may access the relationships via your model's dynamic relationship properties. For example, to access all of the comments for a post, we can use the comments dynamic property:

import Post from './Models/Post';

const post = await Post.find("10ab7e3d05d58a1ad246ee87");

const comments = await post.comments().get();

comments.forEach(comment => {
console.log(comment.body);
});

Many to Many (Polymorphic)

Table Structure

Many-to-many polymorphic relations are slightly more complicated than "morph one" and "morph many" relationships. For example, a Post model and Video model could share a polymorphic relation to a Tag model. Using a many-to-many polymorphic relation in this situation would allow your application to have a single table of unique tags that may be associated with posts or videos. First, let's examine the table structure required to build this relationship:

posts
_id - ObjectID
name - string

videos
_id - ObjectID
name - string

tags
_id - ObjectID
name - string

taggables
_id - ObjectID
tagId - ObjectID
taggableId - ObjectID
taggableType - string

Before diving into polymorphic many-to-many relationships, you may benefit from reading the documentation on typical many-to-many relationships.

Model Structure

Next, we're ready to define the relationships on the models. The Post and Video models will both contain a tags method that calls the morphToMany method provided by the base Mongoloquent model class.

The morphToMany method accepts the name of the related model as well as the "relationship name". Based on the name we assigned to our intermediate table name and the keys it contains, we will refer to the relationship as "taggable":

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Tag from './Models/Tag';

interface IPost extends IMongoloquentSchema, IMongoloquentTimestamps {
title: string
body: string
}

class Post extends Model<IPost> {
/**
* The attributes of the model.
*
* @var IPost
*/
public static $schema: IPost

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'posts';

/**
* Get all of the tags for the post.
*/
public tags(){
return this.morphToMany(Tag, 'taggable');
}
}

Defining the Inverse of the Relationship

Next, on the Tag model, you should define a method for each of its possible parent models. So, in this example, we will define a posts method and a videos method. Both of these methods should return the result of the morphedByMany method.

The morphedByMany method accepts the name of the related model as well as the "relationship name". Based on the name we assigned to our intermediate table name and the keys it contains, we will refer to the relationship as "taggable":

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import Post from './Models/Post';
import Video from './Models/Video';

interface ITag extends IMongoloquentSchema, IMongoloquentTimestamps {
name: string
}

class Tag extends Model<ITag> {
/**
* The attributes of the model.
*
* @var ITag
*/
public static $schema: ITag

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'tags';

/**
* Get all of the posts that are tagged with the tag.
*/
public posts(){
return this.morphedByMany(Post, 'taggable');
}

/**
* Get all of the videos that are tagged with the tag.
*/
public videos(){
return this.morphedByMany(Video, 'taggable');
}
}

Retrieving the Relationship

Once your database table and models are defined, you may access the relationships via your models. For example, to access all of the tags for a post, you may use the tags dynamic relationship method:

import Post from './Models/Post';

const post = await Post.find("10ab7e3d05d58a1ad246ee87");

const tags = await post.tags().get();

tags.forEach(tag => {
console.log(tag.name);
});

You may retrieve the parent of a polymorphic relation from the polymorphic child model by accessing the name of the method that performs the call to morphedByMany. In this case, that is the posts or videos methods on the Tag model:

import Tag from './Models/Tag';

const tag = await Tag.find("10ab7e3d05d58a1ad246ee87");

const posts = await tag.posts().get();

posts.forEach(post => {
console.log(post.title);
});

const videos = await tag.videos().get();

videos.forEach(video => {
console.log(video.title);
});

Querying Relationships

Since all Mongoloquent relationships are defined via methods, you may call those methods to obtain an instance of the relationship without actually executing a query to load the related models. In addition, all types of Mongoloquent relationships also serve as query builders, allowing you to continue to chain constraints onto the relationship query before finally executing the SQL query against your database.

For example, imagine a blog application in which a User model has many associated Post models:

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';

interface IUser extends IMongoloquentSchema, IMongoloquentTimestamps {
name: string
}

class User extends Model<IUser> {
/**
* The attributes of the model.
*
* @var IUser
*/
public static $schema: IUser

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'users';

/**
* Get all of the posts for the user.
*/
public posts(){
return this.hasMany(Post, 'foreign_key');
}
}

You may query the posts relationship and add additional constraints to the relationship like so:

import User from './Models/User';

const user = User.find("10ab7e3d05d58a1ad246ee87");

const posts = await user.posts().where('active', true).get();

You are able to use any of the Mongoloquent query builders methods on the relationship, so be sure to explore the query builder documentation to learn about all of the methods that are available to you.

Chaining orWhere Clauses After Relationships

As demonstrated in the example above, you are free to add additional constraints to relationships when querying them. However, use caution when chaining orWhere clauses onto a relationship, as the orWhere clauses will be logically grouped at the same level as the relationship constraint:

const posts = await user.posts()
.where('active', true)
.orWhere('votes', '>=', 100)
.get();

Eager Loading

Mongoloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the "N + 1" query problem.

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import { ObjectID } from "mongodb"
import Author from './Models/Author';

interface IBook extends IMongoloquentSchema, IMongoloquentTimestamps {
title: string
authorId: ObjectID
}

class Book extends Model<IBook> {
/**
* The attributes of the model.
*
* @var IBook
*/
public static $schema: IBook

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'books';

/**
* Get the author that wrote the book.
*/
public author(){
return this.belongsTo(Author, 'foreign_key');
}
}

We can use eager loading to reduce this operation to just two queries. When building a query, you may specify which relationships should be eager loaded using the with method:

import Book from './Models/Book';

const books = await Book.with('author').get();

books.forEach(book => {
console.log(book.author.name);
});

Eager Loading Multiple Relationships

Sometimes you may need to eager load several different relationships.

import Book from './Models/Book';

const books = await Book.with('author').with('publisher').get();

Nested Eager Loading

To eager load a relationship's relationships, you may use "dot" syntax. For example, let's eager load all of the book's authors and all of the author's personal contacts:

const books = await Book.with('author.contacts').get();

Alternatively, you may specify nested eager loaded relationships by providing an object to the with method, which can be convenient when eager loading multiple nested relationships:

const books = await Book.with({
"author": ["contacts", "publisher"]
}).get();

Eager Loading Specific Columns

You may not always need every column from the relationships you are retrieving. For this reason, Mongoloquent allows you to specify which columns of the relationship you would like to retrieve:

const books = await Book.with('author', {
select: ["_id", "name"]
}).get();

When using this feature, you should always include the _id column and any relevant foreign key columns in the list of columns you wish to retrieve.

You also can excluded columns from the eager loaded relationships by passing an array of columns to the exclude option:

const books = await Book.with('author', {
exclude: ["name"]
}).get();

Eager Loading with another options

You may also pass any other options that are available to the with method, such as select, exclude, sort, skip and limit:

const books = await Book.with('author', {
sort: ["name", "desc"],
skip: 5,
limit: 10
}).get();

Eager Loading and Nested Queries

Sometimes you may need to querying data by relation data.

const books = await Book.with('author').where("author.name", "Ajat").get();

You can use . (chaining) for nested queries

Eager Loading by Default

Sometimes you might want to always load some relationships when retrieving a model. To accomplish this, you may define a $with property on the model:

import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from 'mongoloquent';
import { ObjectID } from "mongodb"
import Author from './Models/Author';
import Genre from './Models/Genre';

interface IBook extends IMongoloquentSchema, IMongoloquentTimestamps {
title: string
authorId: ObjectID
genreId: ObjectID
}

class Book extends Model<IBook> {
/**
* The attributes of the model.
*
* @var IBook
*/
public static $schema: IBook

/**
* The collection name.
*
* @var string
*/
protected $collection: string = 'books';

/**
* The relationships that should always be loaded.
*
* @var array
*/
protected $with: string[] = ['author'];

/**
* Get the author that wrote the book.
*/
public author(){
return this.belongsTo(Author);
}

/**
* Get the genre of the book.
*/
public genre(){
return this.belongsTo(Genre);
}
}

If you would like to remove an item from the $with property for a single query, you may use the without method:

const books = await Book.without('author').get();

If you would like to override all items within the $with property for a single query, you may use the withOnly method:

const books = await Book.withOnly('genre').get();

The save Method

Mongoloquent provides convenient methods for adding new models to relationships. For example, perhaps you need to add a new comment to a post. Instead of manually setting the postId attribute on the Comment model you may insert the comment using the relationship's save method:

import Post from './Models/Post';

const post = await Post.find("10ab7e3d05d58a1ad246ee87");

const comment = await post.comments().save({ message: 'A new comment.' });

Note that we did not access the comments relationship as a dynamic property. Instead, we called the comments method to obtain an instance of the relationship. The save method will automatically add the appropriate postId value to the new Comment model.

If you need to save multiple related models, you may use the saveMany method:

import Post from './Models/Post';

const post = await Post.find("10ab7e3d05d58a1ad246ee87");

const comment = await post.comments().saveMany([
{ message: 'A new comment.' },
{ message: 'Another new comment.' }
]);

The create Method

In addition to the save and saveMany methods, you may also use the create method, which accepts an array of attributes, creates a model, and inserts it into the database. The difference between save and create is that save accepts a full Mongoloquent model instance while create accepts a plain Javascript object. The newly created model will be returned by the create method:

import Post from './Models/Post';

const post = await Post.find("10ab7e3d05d58a1ad246ee87");

const comment = await post.comments().create({ message: 'A new comment.' });

You may use the createMany method to create multiple related models:

import Post from './Models/Post';

const post = await Post.find("10ab7e3d05d58a1ad246ee87");

const comment = await post.comments().createMany([
{ message: 'A new comment.' },
{ message: 'Another new comment.' }
]);

Belongs To Relationships

If you would like to assign a child model to a new parent model, you may use the associate method. In this example, the User model defines a belongsTo relationship to the Account model. This associate method will set the foreign key on the child model:

import Account from './Models/Account';

const account = await Account.find("10ab7e3d05d58a1ad246ee87");

await user.account().associate(account);

To remove a parent model from a child model, you may use the dissociate method. This method will set the relationship's foreign key to null:

await user.account().dissociate();

Many to Many Relationships

Attaching / Detaching

Mongoloquent also provides methods to make working with many-to-many relationships more convenient. For example, let's imagine a user can have many roles and a role can have many users. You may use the attach method to attach a role to a user by inserting a record in the relationship's intermediate collection:

import User from './Models/User';

const user = await User.find("10ab7e3d05d58a1ad246ee87");

await user.roles().attach(roleId);

When attaching a relationship to a model, you may also pass an object of additional data to be inserted into the intermediate table:

await user.roles().attach(roleId, { "expires": expires });

Sometimes it may be necessary to remove a role from a user. To remove a many-to-many relationship record, use the detach method. The detach method will delete the appropriate record out of the intermediate table; however, both models will remain in the database:

// Detach a single role from the user...
await user.roles().detach(roleId);

// Detach all roles from the user...
await user.roles().detach();

For convenience, attach and detach also accept arrays of IDs as input:

const user = await User.find("10ab7e3d05d58a1ad246ee87");

await user.roles().detach([
"10ab7e3d05d58a1ad246ee87",
"10ab7e3d05d58a1ad246ee88"
]);

await user.roles().attach([
"10ab7e3d05d58a1ad246ee87",
"10ab7e3d05d58a1ad246ee88"
], { "expires": expires });

Syncing Associations

You may also use the sync method to construct many-to-many associations. The sync method accepts an array of IDs to place on the intermediate collection. Any IDs that are not in the given array will be removed from the intermediate collection. So, after this operation is complete, only the IDs in the given array will exist in the intermediate collection:

await user.roles().sync([
"10ab7e3d05d58a1ad246ee87",
"10ab7e3d05d58a1ad246ee88"
]);

You may also pass additional intermediate table values with the IDs:

await user.roles().sync([
"10ab7e3d05d58a1ad246ee87",
"10ab7e3d05d58a1ad246ee88"
], { "expires": expires });

If you would like to insert the same intermediate table values with each of the synced model IDs, you may use the syncWithPivotValues method:

await user.roles().syncWithPivotValues([
"10ab7e3d05d58a1ad246ee87",
"10ab7e3d05d58a1ad246ee88"
], { "expires": expires });

If you do not want to detach existing IDs that are missing from the given array, you may use the syncWithoutDetaching method:

await user.roles().syncWithoutDetaching([
"10ab7e3d05d58a1ad246ee87",
"10ab7e3d05d58a1ad246ee88"
]);

Toggling Associations

The many-to-many relationship also provides a toggle method which "toggles" the attachment status of the given related model IDs. If the given ID is currently attached, it will be detached. Likewise, if it is currently detached, it will be attached:

await user.roles().toggle([
"10ab7e3d05d58a1ad246ee87",
"10ab7e3d05d58a1ad246ee88"
]);

Support us

Mongoloquent is an MIT-licensed open source project. It can grow thanks to the support by these awesome people. If you'd like to join them, please read more here.

Sponsors

_

Partners