Introduction
In the last lesson, you successfully implemented the "Create" part of our CRUD API by building a POST route. You learned to handle incoming data from req.body, use Model.create() for efficient document creation, and return the correct 201 Created status code.
Now, we'll tackle the "U" in CRUD: Update. This lesson focuses on implementing a PUT route to modify an existing resource. You will learn the essential Mongoose method Model.findByIdAndUpdate(), how to use its options to get the behavior you need, and how to handle cases where the item to be updated doesn't exist by returning a 404 Not Found status.
1. The PUT Method and Route Structure
The PUT HTTP method is conventionally used to completely replace or update an existing resource. To tell the server which resource to update, we must include its unique identifier in the URL. This is done using a route parameter, just as you did for the GET by ID endpoint.
Your route will look like this: PUT /api/items/:id.
Inside the handler, you'll access the ID with req.params.id and the new data for the item from req.body.
2. Updating Documents with Model.findByIdAndUpdate()
Mongoose provides a powerful, single method for finding a document by its ID and updating it: findByIdAndUpdate. It's the most direct way to implement your PUT route.
The method signature looks like this:Model.findByIdAndUpdate(id, update, options)
id: The_idof the document you want to change.update: An object containing the new data for the document (e.g.,req.body).options: A configuration object to control the method's behavior.
There are a couple of crucial options you must know for the exam. The video below clearly explains two of the most important ones. While it uses the PATCH method, the Mongoose function and its options are identical for PUT.
#70 Update document from Express | Using MongoDB with Express| A Complete NODE JS Course
Watch this clip from the procademy channel. It explains two essential options for findByIdAndUpdate: { new: true } and { runValidators: true }. Understanding these is critical for writing correct update logic.
Watch the segment from 02:27 to 03:48. Focus on the explanation of what { new: true } does and why you need { runValidators: true } to enforce your schema rules during an update.
To summarize the key points from the video:
{ new: true }: By default,findByIdAndUpdatereturns the document as it was before the update. This is rarely what you want. Settingnew: trueensures the method returns the new, modified document, which you can then send back to the client.{ runValidators: true }: Mongoose does not run schema validators on updates by default. This means aPUTrequest could save a document that violates your schema rules (e.g., missing a required field). SettingrunValidators: trueforces Mongoose to validate the update against your schema, ensuring data integrity.
3. Handling the "Not Found" Case (404)
What happens if a client tries to PUT data to an ID that doesn't exist in the database? In this scenario, findByIdAndUpdate will find nothing to update and will return null.
Your code must check for this null response. If you receive it, you should stop execution and immediately return a 404 Not Found status with a clear error message. This is a non-negotiable requirement for a production-grade API.
The following resource provides a perfectly clean and concise code snippet showing exactly how to implement this check.
How to Build a RESTful API Using Node, Express, and MongoDB
The article from GeeksforGeeks, 'How to Build a RESTful API...', contains a direct code example for a PUT route. Pay close attention to how it handles the response from findByIdAndUpdate.
Locate the section titled 'Update a Book By ID (PUT)'. Examine the code snippet. Specifically, look at the line if (!book) and how it returns a 404 status. This is the exact pattern you will implement.
4. Your Turn: Implement the PUT Route
Now, let's put it all together. You will add the update logic to your item router file.
Action: Open src/routes/item.routes.js and add the following PUT route handler. A logical place is after the POST route you created in the last lesson.
// src/routes/item.routes.js
// ... (keep all your existing routes)
// @route PUT /api/items/:id
// @desc Update an item by ID
// @access Public
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
const { name, quantity } = req.body;
// Check if there's anything to update
if (!name && quantity === undefined) {
return res.status(400).json({ message: 'Request body must contain data to update.' });
}
const updatedItem = await Item.findByIdAndUpdate(
id,
req.body, // The new data from the request body
{ new: true, runValidators: true } // Options
);
// If the item with the given ID was not found
if (!updatedItem) {
return res.status(404).json({ message: 'Item not found.' });
}
// Successfully updated
res.status(200).json(updatedItem);
} catch (error) {
// This will catch errors like invalid ObjectId format or validation failures
res.status(400).json({ message: 'Error updating item', error: error.message });
}
});
module.exports = router;
Code Breakdown:
- The route is defined as
put('/:id', ...). - We extract the
idfromreq.params. - We call
await Item.findByIdAndUpdate(), passing theid, the entirereq.bodyas the update payload, and our essential options object{ new: true, runValidators: true }. - Crucially, we check if
updatedItemis falsy (i.e.,null). If it is, we know the ID was not found, and we return a404. - If the update is successful,
updatedItemwill contain the modified document, which we send back to the client with a200 OKstatus. - The
catchblock will handle Mongoose errors, such as a malformedObjectIdor a schema validation error (because we setrunValidators: true).
5. Testing Your Endpoint
Thorough testing is key. You need to verify all possible outcomes of your new endpoint.
-
First, get a valid
_idfor an existing item by making aGETrequest to/api/items. Copy it. -
Test the Success Case (200 OK):
- Method:
PUT - URL:
http://localhost:5001/api/items/<your_copied_id> - Body (raw, JSON): Provide the new data.
{ "name": "Gaming Mouse (Updated)", "quantity": 25 } - Expected Result: A
200 OKstatus and the response body containing the item with its updated name and quantity.
- Method:
-
Test the Not Found Case (404 Not Found):
- Method:
PUT - URL: Use a validly formatted but non-existent ID. You can copy a real ID and change the last character. For example:
http://localhost:5001/api/items/60d5f3f7e5b2f2b3e4f3a9e1 - Body: Send any valid JSON body.
- Expected Result: A
404 Not Foundstatus with the message"Item not found.".
- Method:
-
Test the Validation Error Case (400 Bad Request):
- Method:
PUT - URL:
http://localhost:5001/api/items/<your_copied_id> - Body: Send data that violates the schema (e.g.,
quantityas a string).{ "quantity": "fifty" } - Expected Result: A
400 Bad Requeststatus with a Mongoose validation error message. This confirmsrunValidators: trueis working.
- Method:
Conclusion
You have now successfully implemented the "Update" functionality in your API. You've mastered one of the most important Mongoose methods, findByIdAndUpdate, and learned to use its options to enforce best practices. Critically, you are now correctly handling not-found cases, a key requirement for robust APIs.
Key Takeaways:
- The
PUTmethod is used for updating resources, identified by a route parameter like:id. Model.findByIdAndUpdate(id, data, options)is the primary tool for updates.- The
{ new: true }option is essential for returning the updated document. - The
{ runValidators: true }option is a best practice for maintaining data integrity. - If
findByIdAndUpdatereturnsnull, it means the document was not found, and you must return a404status. - A successful update that returns the new entity should have a
200 OKstatus.
Next Up:
With Create, Read, and Update complete, the final core CRUD operation is "Delete". In the next lesson, you will implement the DELETE route to permanently remove resources from your database.