Sling Academy
Home/Node.js/Sequelize Upsert: How to insert or update a record in one query

Sequelize Upsert: How to insert or update a record in one query

Last updated: February 06, 2024

Overview

Sequelize is a popular ORM (Object-Relational Mapping) library used in Node.js applications for managing database interactions in a more structured and high-level manner. One common requirement in data manipulation is to either insert a new record into the database or update an existing one if certain conditions are met. This process is known as ‘upsert’ (a portmanteau of ‘update’ and ‘insert’). Sequelize provides a convenient upsert method that combines both operations into one query, providing a seamless and efficient way to ensure that a record is created if it does not exist, or updated if it does.

In this tutorial, we will explore the Sequelize upsert method with multiple code examples, ranging from basic to advanced usage. We will also discuss how to handle edge cases and best practices when using upsert in your Node.js applications.

Basic Usage of Sequelize Upsert

At its core, the upsert method requires two arguments: an object that contains the new or updated data for the record, and an options object that defines the criteria for checking whether to perform an insert or an update.

const { Model } = require('sequelize');

async function upsertExample() {
  const data = {
    uniqueField: 'unique_value',
    anotherField: 'some_value'
  };

  // The uniqueField is used to determine if the record exists
  const [instance, created] = await Model.upsert(data);

  if (created) {
    console.log('A new record was created: ', instance);
  } else {
    console.log('The record was updated: ', instance);
  }
}

This basic example demonstrates how to use the upsert method to either create a new record or update an existing one based on the unique constraint of uniqueField.

Defining Unique Keys

To properly use the upsert method, you must define unique constraints on your model. These constraints are used to search for existing records before deciding to insert or update.

const User = sequelize.define('user', {
  email: {
    type: Sequelize.STRING,
    unique: true
  },
  username: {
    type: Sequelize.STRING,
    unique: true
  }
});

In this User model, both the email and username fields have unique constraints, allowing upsert to search using either of these fields.

Advanced Example: Manually Upsert

In the example below, we’ll manually implements the upsert logic, as Sequelize provides a built-in .upsert method that simplifies this process, but this approach offers more control and customization over the upsert operation, including handling custom logic before or after the update/create actions.

const { User } = require('./models'); // Assume 'User' is a Sequelize model

async function upsertUser(userData) {
  // Attempt to find a user by a unique identifier (e.g., email)
  const user = await User.findOne({ where: { email: userData.email } });

  if (user) {
    // If the user exists, update their data
    await User.update(userData, { where: { email: userData.email } });
    return { user: userData, created: false }; // Return the updated user data and 'created' flag as false
  } else {
    // If the user does not exist, create a new user with the provided data
    const newUser = await User.create(userData);
    return { user: newUser, created: true }; // Return the new user data and 'created' flag as true
  }
}

// Example usage of the upsertUser function
upsertUser({
  email: '[email protected]',
  username: 'exampleUser',
  // Additional user fields here
}).then(result => {
  console.log(result.created ? 'User was created' : 'User was updated', result.user);
}).catch(error => {
  console.error('Upsert failed', error);
});

If a user with the specified unique identifier (email in this case) exists, their information is updated. If the user does not exist, a new user is created with the provided data.

Handling Edge Cases & Best Practices

Handling edge cases and following best practices when using upsert operations in Node.js applications, especially with ORMs like Sequelize, are crucial for maintaining data integrity, performance, and avoiding unexpected behaviors. Here are some strategies and best practices:

Understand upsert Behavior

  • Atomicity: Ensure that the upsert operation is atomic, meaning it’s completed entirely or not at all, to prevent partial updates or inserts that could lead to data corruption.
  • Return Value: Be aware of what the upsert method returns. In some ORMs, upsert might return a boolean indicating whether a new record was created, along with the model instance. This can be crucial for subsequent logic in your application.

Conflict Resolution

  • Unique Constraints: Define unique constraints in your database schema to prevent duplicate entries for fields that should be unique (e.g., email, username). This helps the upsert operation determine when to insert a new record versus when to update an existing one.
  • Concurrency Control: In high-concurrency environments, consider using transaction locks or versioning for rows to handle concurrent upsert operations safely.

Validating Data

  • Input Validation: Always validate and sanitize input data before performing an upsert operation to prevent SQL injection attacks and ensure that the data conforms to your application’s expectations.
  • Model Validation: Utilize model validation features provided by your ORM to enforce data integrity at the application level.

Handling Relationships

  • Foreign Keys: Be cautious when upserting data related to other tables. Ensure that foreign key constraints are respected, and related data is in a consistent state.
  • Cascading Operations: Decide how upsert operations should cascade to related records. In some cases, you may need to manually handle updates to related models.

Performance Considerations

  • Indexing: Ensure that fields used in the upsert condition (e.g., email for user records) are indexed to optimize performance.
  • Bulk Operations: When possible, use bulk upsert operations to reduce the number of database calls, which can significantly improve performance for large datasets.

Error Handling

  • Catch Exceptions: Always catch and handle exceptions from upsert operations. This includes handling unique constraint violations gracefully and informing the user if necessary.
  • Logging: Log failures or exceptions for upsert operations, especially in production environments, to aid in debugging and monitoring application health.

Example: Using try/catch with Upsert

When implementing upsert in Sequelize or any ORM, you should encapsulate the logic in a try-catch block, validate input data, and consider using transactions for operations involving multiple steps or related data:

async function upsertUserData(userData) {
  try {
    const [user, created] = await User.upsert(userData, {returning: true});
    // Additional logic based on whether the user was created or updated
  } catch (error) {
    // Handle error, possibly a unique constraint violation
    console.error('Error during upsert operation:', error);
  }
}

By adhering to these best practices and strategies, you can ensure that your upsert operations are reliable, secure, and efficient, contributing to the overall robustness of your Node.js applications (for simplicity’s sake, some code examples in this article don’t use try/catch, but you in production, this isn’t good).

Testing Edge Cases

  • Automated Testing: Write unit and integration tests covering various edge cases for your upsert logic, including scenarios where constraints are violated or data is malformed.
  • Test with Real Data: If possible, test upsert operations with data that mimics real-world scenarios to uncover potential issues not caught by standard testing.

Conclusion

Through this tutorial, we’ve learned how to use Sequelize’s upsert method to efficiently insert or update a record with a single query. By understanding the concept and exploring the various examples provided, developers can now apply this knowledge to their Node.js applications to manage database records effectively. Remember to define your unique constraints clearly in your Sequelize models and consider the edge cases and error handling to ensure robust and reliable database operations. Incorporating upsert into your toolset will make your codebase more concise and your data management processes more streamlined.

Next Article: Sequelize.js: How to insert multiple records at once

Previous Article: Sequelize.js: How to Auto-Hash Passwords

Series: Sequelize.js Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates
  • ExpressJS: How to pass variables to Pug templates