Skip to main content
Version: v7 - alpha

The HasMany Association

The HasMany association is used to create a One-To-Many relationship between two models.

In a One-To-Many relationship, a row of one table is associated with zero, one or more rows of another table.

For instance, a post can have zero or more comments, but a comment can only belong to one post.

Defining the Association

Here is how you would define the Post and Comment models in Sequelize:

import {
Model,
DataTypes,
InferAttributes,
InferCreationAttributes,
CreationOptional,
NonAttribute,
} from '@sequelize/core';
import {
PrimaryKey,
Attribute,
AutoIncrement,
NotNull,
HasMany,
BelongsTo,
} from '@sequelize/core/decorators-legacy';

class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@Attribute(DataTypes.INTEGER)
@AutoIncrement
@PrimaryKey
declare id: CreationOptional<number>;

@HasMany(() => Comment, /* foreign key */ 'postId')
declare comments?: NonAttribute<Comment[]>;
}

class Comment extends Model<InferAttributes<Comment>, InferCreationAttributes<Comment>> {
@Attribute(DataTypes.INTEGER)
@AutoIncrement
@PrimaryKey
declare id: CreationOptional<number>;

// This is the foreign key
@Attribute(DataTypes.INTEGER)
@NotNull
declare postId: number;
}

Note that in the example above, the Comment model has a foreign key to the Post model. HasMany adds the foreign key on the model the association targets.

Inverse association

The HasMany association automatically creates an inverse association on the target model. The inverse association is a BelongsTo association.

You can configure that inverse association by using the inverse option:

import {
Model,
DataTypes,
InferAttributes,
InferCreationAttributes,
CreationOptional,
NonAttribute,
} from '@sequelize/core';
import {
PrimaryKey,
Attribute,
AutoIncrement,
NotNull,
HasMany,
BelongsTo,
} from '@sequelize/core/decorators-legacy';

class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@Attribute(DataTypes.INTEGER)
@AutoIncrement
@PrimaryKey
declare id: CreationOptional<number>;

@HasMany(() => Comment, {
foreignKey: 'postId',
inverse: {
as: 'post',
},
})
declare comments?: NonAttribute<Comment[]>;
}

class Comment extends Model<InferAttributes<Comment>, InferCreationAttributes<Comment>> {
@Attribute(DataTypes.INTEGER)
@AutoIncrement
@PrimaryKey
declare id: CreationOptional<number>;

/** Defined by {@link Post.comments} */
declare post?: NonAttribute<Post>;

// This is the foreign key
@Attribute(DataTypes.INTEGER)
@NotNull
declare postId: number;
}

Association Methods

All associations add methods to the source model1. These methods can be used to fetch, create, and delete associated models.

If you use TypeScript, you will need to declare these methods on your model class.

Association Getter (getX)

The association getter is used to fetch the associated models. It is always named get<AssociationName>:

import { HasManyGetAssociationsMixin } from '@sequelize/core';

class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@HasMany(() => Comment, 'postId')
declare comments?: NonAttribute<Comment[]>;

declare getComments: HasManyGetAssociationsMixin<Comment>;
}

// ...

const post = await Post.findByPk(1);

const comments: Comment[] = await post.getComments();

Association Setter (setX)

The association setter is used to set the associated models. It is always named set<AssociationName>.

If the model is already associated to one or more models, the old associations are removed before the new ones are added.

import { HasManySetAssociationsMixin } from '@sequelize/core';

class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@HasMany(() => Comment, 'postId')
declare comments?: NonAttribute<Comment[]>;

declare setComments: HasManySetAssociationsMixin<
Comment,
/* this is the type of the primary key of the target */
Comment['id']
>;
}

// ...

const post = await Post.findByPk(1);
const [comment1, comment2, comment3] = await Comment.findAll({ limit: 3 });

// Remove all previous associations and set the new ones
await post.setComments([comment1, comment2, comment3]);

// You can also use the primary key of the newly associated model as a way to identify it
// without having to fetch it first.
await post.setComments([1, 2, 3]);
caution

If the foreign key is not nullable, calling this method will delete the previously associated models (if any), as setting their foreign key to null would result in a validation error.

If the foreign key is nullable, it will by default set it to null on all previously associated models. You can use the destroyPrevious option to delete the previously associated models instead:

// this will delete all previously associated models
await post.setComments([], { destroyPrevious: true });

Association Adder (addX)

The association adder is used to add one or more new associated models without removing existing ones. There are two versions of this method:

  • add<SingularAssociationName>: Associates a single new model.
  • add<PluralAssociationName>: Associates multiple new models.
import { HasManyAddAssociationMixin, HasManyAddAssociationsMixin } from '@sequelize/core';

class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@HasMany(() => Comment, 'postId')
declare comments?: NonAttribute<Comment[]>;

declare addComment: HasManyAddAssociationMixin<
Comment,
/* this is the type of the primary key of the target */
Comment['id']
>;

declare addComments: HasManyAddAssociationsMixin<
Comment,
/* this is the type of the primary key of the target */
Comment['id']
>;
}

// ...

const post = await Post.findByPk(1);
const [comment1, comment2, comment3] = await Comment.findAll({ limit: 3 });

// Add a single comment, without removing existing ones
await post.addComment(comment1);

// Add multiple comments, without removing existing ones
await post.addComments([comment1, comment2]);

// You can also use the primary key of the newly associated model as a way to identify it
// without having to fetch it first.
await post.addComment(1);
await post.addComments([1, 2, 3]);

Association Remover (removeX)

The association remover is used to remove one or more associated models.

There are two versions of this method:

  • remove<SingularAssociationName>: Removes a single associated model.
  • remove<PluralAssociationName>: Removes multiple associated models.
import { HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin } from '@sequelize/core';

class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@HasMany(() => Comment, 'postId')
declare comments?: NonAttribute<Comment[]>;

declare removeComment: HasManyRemoveAssociationMixin<
Comment,
/* this is the type of the primary key of the target */
Comment['id']
>;

declare removeComments: HasManyRemoveAssociationsMixin<
Comment,
/* this is the type of the primary key of the target */
Comment['id']
>;
}

// ...

const post = await Post.findByPk(1);
const [comment1, comment2, comment3] = await Comment.findAll({ limit: 3 });

// Remove a single comment, without removing existing ones
await post.removeComment(comment1);

// Remove multiple comments, without removing existing ones
await post.removeComments([comment1, comment2]);

// You can also use the primary key of the newly associated model as a way to identify it
// without having to fetch it first.
await post.removeComment(1);
await post.removeComments([1, 2, 3]);
caution

If the foreign key is not nullable, calling this method will delete the specified models, as setting their foreign key to null would result in a validation error.

If the foreign key is nullable, it will by default set it to null on all specified models. You can use the destroy option to delete the previously associated models instead:

// this will delete comments with PKs 1, 2 and 3
await post.removeComments([1, 2, 3], { destroy: true });

Association Creator (createX)

The association creator is used to create a new associated model and associate it with the source model. It is always named create<AssociationName>.

import { HasManyCreateAssociationMixin } from '@sequelize/core';

class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@HasMany(() => Comment, 'postId')
declare comments?: NonAttribute<Comment[]>;

declare createComment: HasManyCreateAssociationMixin<Comment, 'postId'>;
}

// ...

const post = await Post.findByPk(1);

const comment = await post.createComment({
content: 'This is a comment',
});
Omitting the foreign key

In the example above, we did not need to specify the postId attribute. This is because Sequelize will automatically add it to the creation attributes.

If you use TypeScript, you need to let TypeScript know that the foreign key is not required. You can do so using the second generic argument of the HasManyCreateAssociationMixin type.

HasManyCreateAssociationMixin<Comment, 'postId'> ^ Here;

Association Checker (hasX)

The association checker is used to check if a model is associated with another model. It has two versions:

  • has<SingularAssociationName>: Checks if a single model is associated.
  • has<PluralAssociationName>: Checks whether all the specified models are associated.
import { HasManyHasAssociationMixin, HasManyHasAssociationsMixin } from '@sequelize/core';

class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@HasMany(() => Comment, 'postId')
declare comments?: NonAttribute<Comment[]>;

declare hasComment: HasManyHasAssociationMixin<
Comment,
/* this is the type of the primary key of the target */
Comment['id']
>;

declare hasComments: HasManyHasAssociationsMixin<
Comment,
/* this is the type of the primary key of the target */
Comment['id']
>;
}

// ...

const post = await Post.findByPk(1);

// Returns true if the post has a comment with id 1
const isAssociated = await post.hasComment(comment1);

// Returns true if the post is associated to all specified comments
const isAssociated = await post.hasComments([comment1, comment2, comment3]);

// Like other association methods, you can also use the primary key of the associated model as a way to identify it
const isAssociated = await post.hasComments([1, 2, 3]);

Association Counter (countX)

The association counter is used to count the number of associated models. It is always named count<AssociationName>.

import { HasManyCountAssociationsMixin } from '@sequelize/core';

class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@HasMany(() => Comment, 'postId')
declare comments?: NonAttribute<Comment[]>;

declare countComments: HasManyCountAssociationsMixin<Comment>;
}

// ...

const post = await Post.findByPk(1);

// Returns the number of associated comments
const count = await post.countComments();

Foreign Key targets (sourceKey)

By default, Sequelize will use the primary key of the source model as the attribute the foreign key references. You can customize this by using the sourceKey option.

class Post extends Model {
declare id: CreationOptional<number>;

@HasMany(() => Comment, {
foreignKey: 'postId',
// The foreign key will reference the `id` attribute of the `Post` model
sourceKey: 'id',
})
declare comments?: NonAttribute<Comment[]>;
}

Footnotes

  1. The source model is the model that defines the association.