Sequelize Associations and Relationships – Complete Guide
Learn how to define and use Sequelize associations (hasMany, belongsTo, hasOne) in Node.js. Complete guide with examples for product-category and order-item relationships.
When I first started using Sequelize, I thought associations were just a fancy way to write JOIN queries. Then I tried to fetch a product with its category, and I realized how much easier associations make working with related data. Instead of writing complex JOIN queries, I could just use `include` and Sequelize would handle everything for me.
Sequelize associations define relationships between your database models, which mirrors the relationships in your actual database schema. They enable you to work with related data efficiently—fetching products with their categories, orders with their items, users with their profiles. But understanding when to use hasMany vs belongsTo, how to set up foreign keys correctly, and how to query associated data can be confusing at first.
In this guide, I'll walk you through defining and using Sequelize associations the way I do in production applications. We'll cover one-to-many relationships (like products belonging to categories), many-to-many relationships (like products and tags), one-to-one relationships, eager loading (fetching related data in one query), and lazy loading (fetching related data on demand). I'll also share some performance tips for working with associations efficiently.
Defining Models
Setting up Category and Product models:
const { DataTypes } = require("sequelize");
const database = require("./database");
// Category Model
const Category = database.getSequelize().define("Category", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.TEXT,
allowNull: true,
},
}, {
tableName: "categories",
timestamps: true,
});
// Product Model
const Product = database.getSequelize().define("Product", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
categoryId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Category,
key: "id",
},
},
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
},
}, {
tableName: "products",
timestamps: true,
});Defining Associations
Setting up associations between models:
// One-to-Many: Category has many Products
Category.hasMany(Product, {
foreignKey: "categoryId",
as: "products", // Alias for accessing
});
// Many-to-One: Product belongs to Category
Product.belongsTo(Category, {
foreignKey: "categoryId",
as: "category", // Alias for accessing
});
// Export models
module.exports = { Category, Product };Using Associations in Queries
Querying with includes (eager loading):
// Get all products with their categories
const products = await Product.findAll({
include: [{
model: Category,
as: "category",
attributes: ["id", "name"], // Only get specific fields
}],
});
// Transform to include categoryName at root level
const transformedProducts = products.map(product => {
const data = product.toJSON();
return {
...data,
categoryName: data.category ? data.category.name : "Unknown",
};
});
// Get category with all products
const category = await Category.findByPk(categoryId, {
include: [{
model: Product,
as: "products",
attributes: ["id", "name", "price"],
}],
});
// Access related data
console.log(category.products); // Array of productsMany-to-Many Relationships
Setting up many-to-many relationships:
// Order and Product (many-to-many through OrderItem)
const Order = database.getSequelize().define("Order", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
orderNumber: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
total: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
},
});
const OrderItem = database.getSequelize().define("OrderItem", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
orderId: {
type: DataTypes.INTEGER,
references: { model: Order, key: "id" },
},
productId: {
type: DataTypes.INTEGER,
references: { model: Product, key: "id" },
},
quantity: {
type: DataTypes.INTEGER,
allowNull: false,
},
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
},
});
// Associations
Order.hasMany(OrderItem, { foreignKey: "orderId", as: "items" });
OrderItem.belongsTo(Order, { foreignKey: "orderId", as: "order" });
OrderItem.belongsTo(Product, { foreignKey: "productId", as: "product" });
Product.hasMany(OrderItem, { foreignKey: "productId", as: "orderItems" });Querying with Nested Includes
// Get order with items and products
const order = await Order.findByPk(orderId, {
include: [{
model: OrderItem,
as: "items",
include: [{
model: Product,
as: "product",
attributes: ["id", "name", "price"],
}],
}],
});
// Access nested data
order.items.forEach(item => {
console.log(item.product.name); // Product name
console.log(item.quantity); // Quantity
console.log(item.price); // Item price
});Best Practices
- Always define associations in both directions (hasMany and belongsTo)
- Use aliases for clearer code (as: "category", as: "products")
- Specify attributes in includes to avoid fetching unnecessary data
- Use eager loading (includes) to avoid N+1 queries
- Define foreign keys explicitly in models
- Use proper cascade options for deletions
Conclusion
Sequelize associations provide a powerful way to work with related data in Node.js applications. With proper associations, you can efficiently query related data, avoid N+1 problems, and maintain referential integrity. This is essential for inventory management systems with complex relationships between products, categories, orders, and items.
Related Articles
Sequelize ORM with MySQL Setup: Complete Guide for Node.js
Complete guide with connection pooling, migrations, and best practices for database setup.
Express.js REST API Setup: Complete Guide with Error Handling
Learn how to set up a production-ready Express.js REST API with CORS and error handling.
JWT Authentication in Express.js and Node.js: Complete Guide
Learn how to implement JWT authentication with bcrypt password hashing and protected routes.
Multer File Upload in Express.js: Complete Guide with Examples
Learn how to implement file uploads in Express.js using Multer with validation.