Back to Blog
Database15 min read

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 products

Many-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.