Imagine you're organizing a large library. Each book can have multiple authors, and each author can write multiple books. How do we track these connections? We need a special list (a join table) that keeps track of which authors wrote which books. In our case, we're tracking which insects have been spotted near which trees.
Think of it like a guest book at different locations. Each location (tree) can have multiple visitors (insects), and each visitor can visit multiple locations. The guest book (join table) records all these visits.
Our join table needs two main pieces of information:
InsectTrees Table Structure:
-------------------------
insectId (FK) - Which insect was seen
treeId (FK) - Which tree it was seen near
The table also needs some special rules (constraints):
We'll use Sequelize CLI to create our join table. Here's how:
npx sequelize-cli model:generate --name InsectTree \
--attributes "insectId:integer,treeId:integer"
File Location: server/db/migrations/XXXXXX-create-insect-tree.js
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('InsectTrees', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
insectId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Insects',
key: 'id'
},
onDelete: 'CASCADE'
},
treeId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Trees',
key: 'id'
},
onDelete: 'CASCADE'
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
});
// Add a unique constraint to prevent duplicate associations
await queryInterface.addConstraint('InsectTrees', {
fields: ['insectId', 'treeId'],
type: 'unique',
name: 'unique_insect_tree'
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('InsectTrees');
}
};
File Location: server/db/models/insect.js
// In the Insect model
static associate(models) {
Insect.belongsToMany(models.Tree, {
through: 'InsectTrees',
foreignKey: 'insectId',
otherKey: 'treeId'
});
}
File Location: server/db/models/tree.js
// In the Tree model
static associate(models) {
Tree.belongsToMany(models.Insect, {
through: 'InsectTrees',
foreignKey: 'treeId',
otherKey: 'insectId'
});
}
Let's examine each part of our migration:
Foreign Key Setup:
references: {
model: 'Insects',
key: 'id'
},
onDelete: 'CASCADE'
This tells the database:
"This column points to the id in the Insects table. If that insect gets deleted, also delete this connection (CASCADE)."
Timestamp Handling:
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
This automatically sets the timestamps when records are created or updated.
The belongsToMany relationship is like telling the database:
"Each insect can be found near many trees, and each tree can have many insects nearby. To keep track of these connections, use the InsectTrees table."
Many-to-many relationships are common in real applications:
To verify your setup:
npx sequelize-cli db:migrate
.schema InsectTrees
npm test test/phase-05-spec.jsConsider these scenarios to deepen your understanding:
Watch out for these common issues:
Try these exercises to reinforce your understanding: