Constraints & Circularities

Adding constraints between tables means that tables must be created in the database in a certain order, when using sequelize.sync. If Task has a reference to User, the User table must be created before the Task table can be created. This can sometimes lead to circular references, where Sequelize cannot find an order in which to sync. Imagine a scenario of documents and versions. A document can have multiple versions, and for convenience, a document has a reference to its current version.

  1. const { Sequelize, Model, DataTypes } = require("sequelize");
  2. class Document extends Model {}
  3. Document.init({
  4. author: DataTypes.STRING
  5. }, { sequelize, modelName: 'document' });
  6. class Version extends Model {}
  7. Version.init({
  8. timestamp: DataTypes.DATE
  9. }, { sequelize, modelName: 'version' });
  10. Document.hasMany(Version); // This adds documentId attribute to version
  11. Document.belongsTo(Version, {
  12. as: 'Current',
  13. foreignKey: 'currentVersionId'
  14. }); // This adds currentVersionId attribute to document

However, unfortunately the code above will result in the following error:

  1. Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents

In order to alleviate that, we can pass constraints: false to one of the associations:

  1. Document.hasMany(Version);
  2. Document.belongsTo(Version, {
  3. as: 'Current',
  4. foreignKey: 'currentVersionId',
  5. constraints: false
  6. });

Which will allow us to sync the tables correctly:

  1. CREATE TABLE IF NOT EXISTS "documents" (
  2. "id" SERIAL,
  3. "author" VARCHAR(255),
  4. "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  5. "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  6. "currentVersionId" INTEGER,
  7. PRIMARY KEY ("id")
  8. );
  9. CREATE TABLE IF NOT EXISTS "versions" (
  10. "id" SERIAL,
  11. "timestamp" TIMESTAMP WITH TIME ZONE,
  12. "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  13. "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  14. "documentId" INTEGER REFERENCES "documents" ("id") ON DELETE
  15. SET
  16. NULL ON UPDATE CASCADE,
  17. PRIMARY KEY ("id")
  18. );

Enforcing a foreign key reference without constraints

Sometimes you may want to reference another table, without adding any constraints, or associations. In that case you can manually add the reference attributes to your schema definition, and mark the relations between them.

  1. class Trainer extends Model {}
  2. Trainer.init({
  3. firstName: Sequelize.STRING,
  4. lastName: Sequelize.STRING
  5. }, { sequelize, modelName: 'trainer' });
  6. // Series will have a trainerId = Trainer.id foreign reference key
  7. // after we call Trainer.hasMany(series)
  8. class Series extends Model {}
  9. Series.init({
  10. title: Sequelize.STRING,
  11. subTitle: Sequelize.STRING,
  12. description: Sequelize.TEXT,
  13. // Set FK relationship (hasMany) with `Trainer`
  14. trainerId: {
  15. type: DataTypes.INTEGER,
  16. references: {
  17. model: Trainer,
  18. key: 'id'
  19. }
  20. }
  21. }, { sequelize, modelName: 'series' });
  22. // Video will have seriesId = Series.id foreign reference key
  23. // after we call Series.hasOne(Video)
  24. class Video extends Model {}
  25. Video.init({
  26. title: Sequelize.STRING,
  27. sequence: Sequelize.INTEGER,
  28. description: Sequelize.TEXT,
  29. // set relationship (hasOne) with `Series`
  30. seriesId: {
  31. type: DataTypes.INTEGER,
  32. references: {
  33. model: Series, // Can be both a string representing the table name or a Sequelize model
  34. key: 'id'
  35. }
  36. }
  37. }, { sequelize, modelName: 'video' });
  38. Series.hasOne(Video);
  39. Trainer.hasMany(Series);