DEV Community

Cover image for Sequelize ORM with MySQL Setup: Complete Guide for Node.js
Md. Maruf Rahman
Md. Maruf Rahman

Posted on • Originally published at marufrahman.live

Sequelize ORM with MySQL Setup: Complete Guide for Node.js

When I first started building Node.js applications, I was writing raw SQL queries everywhere. It worked, but maintaining those queries as the application grew became a nightmare. Then I discovered Sequelize, and it completely changed how I interact with databases. Instead of writing complex JOIN statements, I could use simple JavaScript methods that felt natural.

Sequelize is an ORM (Object-Relational Mapping) library that acts as a bridge between your Node.js code and your MySQL database. It handles all the SQL generation for you, provides type safety, and makes database operations much more maintainable. But setting it up correctlyβ€”especially for productionβ€”requires understanding connection pooling, the singleton pattern, and proper error handling.

πŸ“– Want the complete guide with more examples and advanced patterns? Check out the full article on my blog for an in-depth tutorial with additional code examples, troubleshooting tips, and real-world use cases.

What is Sequelize ORM?

Sequelize is a popular ORM (Object-Relational Mapping) for Node.js that provides:

  • Type-safe database operations - JavaScript methods instead of raw SQL
  • Connection pooling - Efficient database connection management
  • Migrations - Version control for your database schema
  • Associations - Easy relationship handling (hasMany, belongsTo, etc.)
  • Transactions - Support for complex operations
  • Multi-database support - MySQL, PostgreSQL, SQLite, and more

Installation

First, let's install Sequelize and MySQL driver:

npm install sequelize mysql2
npm install dotenv
Enter fullscreen mode Exit fullscreen mode

Database Connection with Singleton Pattern

Creating a singleton database connection ensures you only have one database connection instance, which is crucial for avoiding connection leaks and managing resources efficiently.

const { Sequelize } = require("sequelize");
require("dotenv").config();

class Database {
  constructor() {
    if (Database.instance) {
      return Database.instance;
    }

    this.sequelize = new Sequelize(
      process.env.DB_NAME || "inventory_management",
      process.env.DB_USER || "root",
      process.env.DB_PASSWORD || "",
      {
        host: process.env.DB_HOST || "localhost",
        port: process.env.DB_PORT || 3306,
        dialect: "mysql",

        // Connection Pool Configuration
        pool: {
          max: 10,              // Maximum connections
          min: 0,               // Minimum connections
          acquire: 30000,       // Max time to get connection
          idle: 10000,          // Max time connection can be idle
        },

        // Logging
        logging: process.env.NODE_ENV === "production" ? false : console.log,

        // Retry Configuration
        retry: {
          max: 3,
          match: [
            Sequelize.ConnectionError,
            Sequelize.ConnectionTimedOutError,
          ],
        },

        // Query timeout
        dialectOptions: {
          connectTimeout: 60000,
          decimalNumbers: true,
        },

        // Timezone
        timezone: "+00:00",

        // Model defaults
        define: {
          timestamps: true,
          underscored: false,
          freezeTableName: true,
          charset: "utf8mb4",
          collate: "utf8mb4_unicode_ci",
        },
      }
    );

    Database.instance = this;
  }

  async testConnection() {
    try {
      await this.sequelize.authenticate();
      console.log("βœ… MySQL Database connected successfully");
      return true;
    } catch (error) {
      console.error("❌ Database connection failed:", error.message);
      return false;
    }
  }

  async closeConnection() {
    try {
      await this.sequelize.close();
      console.log("βœ… Database connection closed");
      return true;
    } catch (error) {
      console.error("❌ Error closing connection:", error.message);
      return false;
    }
  }

  getSequelize() {
    return this.sequelize;
  }
}

const database = new Database();
module.exports = database;
Enter fullscreen mode Exit fullscreen mode

Why Singleton Pattern?

The singleton pattern ensures:

  • Single connection instance - Prevents multiple database connections
  • Resource efficiency - Reuses the same connection pool
  • Consistent state - All parts of your app use the same connection
  • Easier testing - Mock the singleton instance for tests

Creating Models

Defining models with Sequelize is straightforward. Here's an example Product model:

const { DataTypes } = require("sequelize");
const database = require("./database");
const { Category } = require("./index");

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",
    },
  },
  sku: {
    type: DataTypes.STRING,
    allowNull: true,
  },
  description: {
    type: DataTypes.TEXT,
    allowNull: true,
  },
  price: {
    type: DataTypes.DECIMAL(10, 2),
    allowNull: false,
    defaultValue: 0,
  },
  cost: {
    type: DataTypes.DECIMAL(10, 2),
    allowNull: false,
    defaultValue: 0,
  },
  stock: {
    type: DataTypes.INTEGER,
    allowNull: false,
    defaultValue: 0,
  },
  minStock: {
    type: DataTypes.INTEGER,
    allowNull: false,
    defaultValue: 0,
  },
  unit: {
    type: DataTypes.STRING,
    allowNull: false,
    defaultValue: "pcs",
  },
  barcode: {
    type: DataTypes.STRING,
    allowNull: true,
  },
}, {
  tableName: "products",
  timestamps: true,
});

// Associations
Product.belongsTo(Category, { foreignKey: "categoryId", as: "category" });
Category.hasMany(Product, { foreignKey: "categoryId", as: "products" });

module.exports = Product;
Enter fullscreen mode Exit fullscreen mode

Model Class for Clean Interface

Creating a model class that wraps Sequelize operations provides a clean, maintainable interface:

const { Product, Category } = require("./index");

class ProductModel {
  async getAll() {
    try {
      const products = await Product.findAll({
        include: [{
          model: Category,
          as: "category",
          attributes: ["id", "name"],
        }],
        order: [["createdAt", "DESC"]],
      });

      return products.map(product => {
        const data = product.toJSON();
        return {
          ...data,
          categoryName: data.category ? data.category.name : "Unknown",
        };
      });
    } catch (error) {
      console.error("Error getting all products:", error);
      return [];
    }
  }

  async getById(id) {
    try {
      const product = await Product.findByPk(id, {
        include: [{
          model: Category,
          as: "category",
          attributes: ["id", "name"],
        }],
      });

      if (!product) {
        return { success: false, message: "Product not found" };
      }

      const data = product.toJSON();
      return {
        ...data,
        categoryName: data.category ? data.category.name : "Unknown",
      };
    } catch (error) {
      console.error("Error getting product by ID:", error);
      return { success: false, message: "Error reading product" };
    }
  }

  async create(productData) {
    try {
      return await Product.create(productData);
    } catch (error) {
      console.error("Error creating product:", error);
      return { success: false, message: "Error creating product", error: error.message };
    }
  }

  async update(id, productData) {
    try {
      const product = await Product.findByPk(id);
      if (!product) {
        return { success: false, message: "Product not found" };
      }
      await product.update(productData);
      return product;
    } catch (error) {
      console.error("Error updating product:", error);
      return { success: false, message: "Error updating product" };
    }
  }

  async delete(id) {
    try {
      const product = await Product.findByPk(id);
      if (!product) {
        return { success: false, message: "Product not found" };
      }
      const productData = product.toJSON();
      await product.destroy();
      return productData;
    } catch (error) {
      console.error("Error deleting product:", error);
      return { success: false, message: "Error deleting product" };
    }
  }
}

module.exports = new ProductModel();
Enter fullscreen mode Exit fullscreen mode

Environment Variables

Configure your .env file with database credentials:

DB_HOST=localhost
DB_PORT=3306
DB_NAME=inventory_management
DB_USER=root
DB_PASSWORD=your_mysql_password
NODE_ENV=development
Enter fullscreen mode Exit fullscreen mode

Important: Never commit your .env file to version control. Add it to .gitignore.

Testing the Connection

Test your database connection before starting your server:

const database = require("./config/database");

async function startServer() {
  const dbConnected = await database.testConnection();

  if (!dbConnected) {
    console.error("Failed to connect to database");
    process.exit(1);
  }

  // Start your Express server
  app.listen(3000, () => {
    console.log("Server running on port 3000");
  });
}

startServer();
Enter fullscreen mode Exit fullscreen mode

Connection Pooling Explained

Connection pooling is crucial for production applications:

  • max: 10 - Maximum number of connections in the pool
  • min: 0 - Minimum number of connections to maintain
  • acquire: 30000 - Maximum time (ms) to wait for a connection
  • idle: 10000 - Maximum time (ms) a connection can be idle before being released

Why Connection Pooling?

  • Performance - Reuses existing connections instead of creating new ones
  • Resource management - Limits the number of database connections
  • Scalability - Handles concurrent requests efficiently
  • Stability - Prevents connection exhaustion

Common Operations

Creating Records

// Create a single record
const product = await Product.create({
  name: "Laptop",
  categoryId: 1,
  price: 999.99,
  stock: 10,
});

// Create multiple records
const products = await Product.bulkCreate([
  { name: "Laptop", price: 999.99, stock: 10 },
  { name: "Mouse", price: 29.99, stock: 50 },
]);
Enter fullscreen mode Exit fullscreen mode

Querying Records

// Find all records
const products = await Product.findAll();

// Find with conditions
const products = await Product.findAll({
  where: {
    stock: {
      [Op.gt]: 0  // Greater than 0
    }
  },
  order: [["price", "DESC"]],
  limit: 10,
});

// Find one record
const product = await Product.findOne({
  where: { id: 1 },
  include: [{ model: Category, as: "category" }],
});

// Find by primary key
const product = await Product.findByPk(1);
Enter fullscreen mode Exit fullscreen mode

Updating Records

// Update a single record
const product = await Product.findByPk(1);
await product.update({ price: 899.99 });

// Update multiple records
await Product.update(
  { stock: 0 },
  { where: { price: { [Op.lt]: 10 } } }
);
Enter fullscreen mode Exit fullscreen mode

Deleting Records

// Delete a single record
const product = await Product.findByPk(1);
await product.destroy();

// Delete multiple records
await Product.destroy({
  where: { stock: 0 }
});
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Use singleton pattern - Ensure only one database connection instance
  2. Configure connection pooling - Set appropriate pool sizes for your workload
  3. Always use environment variables - Never hardcode database credentials
  4. Implement proper error handling - Catch and handle database errors gracefully
  5. Use transactions - For operations that must succeed or fail together
  6. Set appropriate timeouts - Prevent hanging connections
  7. Close connections gracefully - On application shutdown
  8. Use migrations - Version control your database schema
  9. Index frequently queried fields - Improve query performance
  10. Monitor connection pool - Track connection usage in production

Error Handling Example

async function safeDatabaseOperation() {
  try {
    const result = await Product.findAll();
    return { success: true, data: result };
  } catch (error) {
    console.error("Database error:", error);

    // Handle specific errors
    if (error.name === "SequelizeConnectionError") {
      return { success: false, message: "Database connection failed" };
    }

    return { success: false, message: "Database operation failed" };
  }
}
Enter fullscreen mode Exit fullscreen mode

Production Considerations

Connection Pool Sizing

For production, adjust pool settings based on your workload:

pool: {
  max: 20,              // Increase for high-traffic apps
  min: 5,               // Keep minimum connections alive
  acquire: 30000,
  idle: 10000,
}
Enter fullscreen mode Exit fullscreen mode

Logging

Disable SQL logging in production for better performance:

logging: process.env.NODE_ENV === "production" ? false : console.log,
Enter fullscreen mode Exit fullscreen mode

Retry Logic

Implement retry logic for transient failures:

retry: {
  max: 3,
  match: [
    Sequelize.ConnectionError,
    Sequelize.ConnectionTimedOutError,
  ],
}
Enter fullscreen mode Exit fullscreen mode

Resources and Further Reading

Conclusion

Sequelize ORM provides a powerful, type-safe interface for MySQL operations in Node.js. Using a singleton pattern ensures efficient connection management, while connection pooling improves performance. This setup is production-ready and scalable for inventory management systems and other data-heavy applications.

Key Takeaways:

  • Sequelize simplifies database operations with JavaScript methods
  • Singleton pattern prevents connection leaks and resource issues
  • Connection pooling improves performance and scalability
  • Model classes provide clean, maintainable interfaces
  • Proper error handling ensures robust applications
  • Environment variables keep credentials secure
  • Production configuration requires careful pool sizing and logging

Whether you're building a simple CRUD app or a complex system with multiple relationships, Sequelize handles it all elegantly. Start with this setup, then gradually explore associations, migrations, and advanced features as your application grows.


What's your experience with Sequelize? Share your tips and tricks in the comments below! πŸš€


πŸ’‘ Looking for more details? This is a condensed version of my comprehensive guide. Read the full article on my blog for additional examples, advanced patterns, troubleshooting tips, and more in-depth explanations.

If you found this guide helpful, consider checking out my other articles on Node.js development and database best practices.

Top comments (0)