UPDATE Using Sequelize Queries

Understanding the Problem

Imagine you're maintaining a library catalog where book details occasionally need to be corrected or updated. Similarly, in our tree database, we sometimes need to update information about existing trees. This is like editing a document rather than creating a new one or throwing it away.

When updating a tree, we need to handle several scenarios:

Expected Input Example:


PUT /trees/3
{
    "id": 3,
    "name": "President-edit",
    "location": "Sequoia National Park-edit",
    "height": 240.91,
    "size": 93.1
}
    

Expected Success Output:


{
    "status": "success",
    "message": "Successfully updated tree",
    "data": {
        "id": 3,
        "tree": "President-edit",
        "location": "Sequoia National Park-edit",
        "heightFt": 240.91,
        "groundCircumferenceFt": 93.1,
        "updatedAt": "2025-02-17T..."
    }
}
    

Planning the Solution

Let's break this down into manageable steps:

  1. Verify that URL ID matches body ID
  2. Find the existing tree
  3. Update tree properties if found
  4. Save changes to database
  5. Return appropriate response

Basic Solution

File Location: server/routes/trees.js


router.put('/:id', async (req, res, next) => {
    try {
        // Extract data from request
        const treeId = parseInt(req.params.id);
        const { id, name, location, height, size } = req.body;

        // Verify IDs match
        if (id !== treeId) {
            throw new Error(`${treeId} does not match ${id}`);
        }

        // Find the tree
        const tree = await Tree.findByPk(treeId);
        
        // Handle tree not found
        if (!tree) {
            const err = new Error(`Could not update tree ${treeId}`);
            err.status = "not-found";
            err.details = "Tree not found";
            throw err;
        }

        // Update tree properties
        tree.tree = name;                      // Map 'name' to 'tree'
        tree.location = location;
        tree.heightFt = height;                // Map 'height' to 'heightFt'
        tree.groundCircumferenceFt = size;     // Map 'size' to 'groundCircumferenceFt'

        // Save changes
        await tree.save();

        // Return success response
        res.json({
            status: "success",
            message: "Successfully updated tree",
            data: tree
        });

    } catch (err) {
        next(err);
    }
});
    

Understanding Key Concepts

The Update Process

Updating a record in Sequelize typically follows a pattern we can think of as "Find, Modify, Save":

  1. Find: First locate the existing record
  2. Modify: Change the values we want to update
  3. Save: Persist those changes back to the database

Field Mapping

Just like in our create operation, we need to map between the API fields and database fields:

API FieldDatabase Field
nametree
heightheightFt
sizegroundCircumferenceFt

Advanced Implementation

Here's a more robust version that includes additional error checking and partial updates:


router.put('/:id', async (req, res, next) => {
    try {
        const treeId = parseInt(req.params.id);
        const { id, name, location, height, size } = req.body;

        // Input validation
        if (isNaN(treeId)) {
            throw new Error('Invalid ID format');
        }

        // ID matching check
        if (id && id !== treeId) {
            throw new Error(`${treeId} does not match ${id}`);
        }

        // Use a transaction for safety
        const result = await sequelize.transaction(async (t) => {
            const tree = await Tree.findByPk(treeId, { transaction: t });
            
            if (!tree) {
                const err = new Error(`Could not update tree ${treeId}`);
                err.status = "not-found";
                err.details = "Tree not found";
                throw err;
            }

            // Only update provided fields (partial update)
            if (name !== undefined) tree.tree = name;
            if (location !== undefined) tree.location = location;
            if (height !== undefined) tree.heightFt = height;
            if (size !== undefined) tree.groundCircumferenceFt = size;

            // Validate before saving
            await tree.validate();
            
            // Save changes
            await tree.save({ transaction: t });
            return tree;
        });

        res.json({
            status: "success",
            message: "Successfully updated tree",
            data: result
        });

    } catch (err) {
        next({
            status: err.status || "error",
            message: err.message || "Could not update tree",
            details: err.details || err.errors?.map(e => e.message).join(', ')
        });
    }
});
    

Real World Applications

Update operations are essential in many real-world scenarios:

Testing Strategies

To thoroughly test your update route, try these scenarios:

  1. Update all fields of an existing record
  2. Update only some fields (partial update)
  3. Try to update a non-existent record
  4. Submit mismatched IDs
  5. Submit invalid data types
  6. Submit empty or null values

Common Pitfalls

Be careful to avoid these common issues:

Practice Exercises

Try implementing these enhancements to deepen your understanding:

  1. Add validation to ensure updated tree names remain unique
  2. Implement batch updates for multiple trees
  3. Add an update history tracking system
  4. Create a route to restore previous versions

Thinking Questions

Consider these questions to deepen your understanding:

  1. Why might you want to use transactions for updates?
  2. What are the trade-offs between allowing partial updates versus requiring all fields?
  3. How would you handle concurrent updates to the same record?
  4. What security considerations should you keep in mind for update operations?