The database is rejecting your INSERT or UPDATE operation because you’re trying to put data into a column that has a unique constraint, and the value you’re trying to insert already exists.

Common Causes and Fixes

1. Duplicate Data in the Database

This is the most straightforward cause: the record you’re trying to insert (or update to) already exists in the database, violating the unique constraint.

  • Diagnosis: Before the failing Sequelize operation, run a query to check for the existence of the record with the conflicting value. For example, if you’re trying to insert a user with email: 'test@example.com' and email is unique:

    SELECT COUNT(*) FROM users WHERE email = 'test@example.com';
    

    If the count is 1 or more, you’ve found your duplicate.

  • Fix: You have a few options:

    • Prevent the insert: In your application logic, before attempting to save, query the database. If a record with that unique value already exists, don’t proceed with the save. Instead, return an appropriate error to the user or handle it gracefully.
    • Update the existing record: If the intention is to update, use Model.update() or Model.upsert() in Sequelize. upsert is particularly useful as it will insert if the record doesn’t exist or update it if it does, based on a specified unique key.
    • Remove the duplicate: If the duplicate is an error and should not exist, manually delete the conflicting record from the database using SQL or a database tool.
    // Example using upsert
    const [user, created] = await User.upsert({
      email: 'test@example.com',
      username: 'testuser',
      // other fields
    }, {
      where: { email: 'test@example.com' }, // Specify the unique key for lookup
      returning: true, // For PostgreSQL, MySQL
    });
    
    if (!created) {
      console.log('User already existed and was updated.');
    } else {
      console.log('User was created.');
    }
    
  • Why it works: This directly addresses the root cause by either preventing the invalid insert or by handling the existing data appropriately. upsert leverages the database’s ability to efficiently check for existence and perform the correct operation (insert or update) in a single atomic step.

2. Case Sensitivity Differences (Database vs. Application)

Your database might be configured to be case-sensitive for string comparisons, while your application or Sequelize might be treating values case-insensitively by default.

  • Diagnosis: Manually check the database for similar entries with different casing. For instance, if your app tries to create a user with username: 'Admin', but 'admin' already exists, and the database is case-sensitive, it will fail.

    SELECT username FROM users WHERE LOWER(username) = 'admin';
    

    If this query returns 'admin' and your app tried to insert 'Admin', this is the issue.

  • Fix:

    • Normalize data on insert/update: Ensure that when you’re saving data that is subject to a unique constraint, you normalize it to a consistent case (e.g., lowercase) in your application before passing it to Sequelize.
    const usernameToSave = 'Admin'.toLowerCase(); // Normalize to 'admin'
    await User.create({
      username: usernameToSave,
      // ... other fields
    });
    
    • Database collation: For a more permanent solution, alter the column’s collation to be case-insensitive (e.g., utf8mb4_unicode_ci for MySQL, CIL for PostgreSQL). This changes the database’s default comparison behavior.
    -- Example for MySQL
    ALTER TABLE users MODIFY COLUMN username VARCHAR(255) COLLATE utf8mb4_unicode_ci;
    
  • Why it works: Normalizing data in the application ensures that the value being sent to the database conforms to the expected case. Changing the database collation makes the database itself perform case-insensitive comparisons for that column, aligning its behavior with common application expectations.

3. Data Transformation or Formatting Issues

Sometimes, strings might look identical but contain subtle differences like trailing whitespace, non-breaking spaces, or different Unicode representations of characters.

  • Diagnosis: Use string manipulation functions in SQL to reveal hidden differences.

    -- Check for trailing whitespace
    SELECT username FROM users WHERE username LIKE '% ';
    
    -- Check for length differences that might indicate different Unicode chars
    SELECT username, LENGTH(username) FROM users WHERE LOWER(username) = 'somevalue';
    

    Compare the lengths and content of existing records with the data you’re trying to insert.

  • Fix: Trim whitespace and normalize Unicode characters in your application before saving.

    const cleanedUsername = username.trim().normalize('NFC'); // NFC is a common Unicode normalization form
    await User.create({
      username: cleanedUsername,
      // ... other fields
    });
    
  • Why it works: trim() removes leading/trailing whitespace. normalize('NFC') converts characters that might have multiple Unicode representations into a single, standard form, ensuring that visually identical strings are byte-for-byte identical.

4. Concurrent Operations and Race Conditions

In high-traffic applications, two separate requests might try to create a record with the same unique value almost simultaneously. The first request might pass a check, but before it commits, the second request also passes the same check. When the first request commits, the second one fails.

  • Diagnosis: This is harder to diagnose directly with a SQL query as it’s a timing issue. Look for patterns of errors occurring seemingly randomly, especially under load. Check your application logs for sequences of operations that are very close in time.

  • Fix:

    • Database-level locking: Use Sequelize’s transaction capabilities with appropriate isolation levels. SERIALIZABLE is the strongest, but can impact performance. REPEATABLE READ or READ COMMITTED might suffice depending on your database.
    const t = await sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE });
    try {
      // Perform your checks and creates within the transaction
      const existing = await User.findOne({ where: { email: 'test@example.com' }, transaction: t });
      if (existing) {
        throw new Error('User already exists');
      }
      await User.create({ email: 'test@example.com', ... }, { transaction: t });
      await t.commit();
    } catch (error) {
      await t.rollback();
      // Handle error, potentially retry or inform user
      throw error;
    }
    
    • UPSERT: As mentioned before, UPSERT is often a good way to handle this as it’s an atomic operation at the database level.
  • Why it works: Transactions with strong isolation levels ensure that no other concurrent transaction can interfere with your read-modify-write operation. If another transaction attempts to insert conflicting data while yours is in progress, it will be blocked or rolled back, preventing the unique constraint violation. UPSERT is a more specialized atomic operation that handles the check-and-insert/update in one go.

5. Incorrect Unique Constraint Definition

It’s possible the unique constraint in your database isn’t defined as you expect, or you’re trying to enforce uniqueness on a column that doesn’t have one.

  • Diagnosis: Inspect your database schema.

    -- For PostgreSQL
    \d your_table_name
    
    -- For MySQL
    SHOW CREATE TABLE your_table_name;
    
    -- Or query the information schema
    SELECT
        CONSTRAINT_NAME,
        COLUMN_NAME
    FROM
        INFORMATION_SCHEMA.KEY_COLUMN_USAGE
    WHERE
        TABLE_NAME = 'your_table_name' AND CONSTRAINT_NAME = 'your_unique_constraint_name'; -- If you know the constraint name
    

    Verify that the column you believe is unique actually has a UNIQUE index or constraint associated with it.

  • Fix: If the constraint is missing or incorrect, add or alter it in your database.

    -- Example for PostgreSQL: Add a unique constraint
    ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);
    
    -- Example for MySQL: Add a unique index (which implies a unique constraint)
    ALTER TABLE users ADD UNIQUE INDEX users_email_unique (email);
    

    If you’re using Sequelize migrations, define the constraint there.

  • Why it works: This ensures that the database itself is configured to enforce the uniqueness rule you intend. Without a proper database-level constraint, your application logic might incorrectly assume uniqueness is being enforced, leading to unexpected errors or data integrity issues.

6. Constraints on Composite Keys

You might have a unique constraint defined across multiple columns (a composite unique key). The error occurs if the combination of values across these columns already exists.

  • Diagnosis: Check your schema for constraints involving more than one column.

    -- Example for PostgreSQL: \d your_table_name will show multiple columns in a UNIQUE constraint
    -- Example for MySQL: SHOW CREATE TABLE your_table_name will show UNIQUE KEY (col1, col2)
    

    You’ll need to check for the specific combination of values that are together unique.

  • Fix: Ensure the combination of values you are trying to insert or update does not already exist for that set of columns.

    // If (userId, productId) must be unique
    const existing = await OrderItem.findOne({
      where: {
        userId: 123,
        productId: 456
      }
    });
    if (existing) {
      // Handle error: this combination already exists
    } else {
      await OrderItem.create({ userId: 123, productId: 456, ... });
    }
    
  • Why it works: This focuses the uniqueness check on the entire set of columns involved in the composite constraint, rather than individual columns.

After fixing the UniqueConstraintError, the next error you’re likely to encounter is a ForeignKeyConstraintError if your data also violates referential integrity.

Want structured learning?

Take the full Nodejs course →