Openhouse Build Update Functionality
Learning objective: By the end of this lesson, students will be able to create a controller function to update a resource in a MEN stack application.
User Story
Now that we are rendering an edit view, we can address the next component of this user story:
As a user who owns a listing, I want to edit details of my listings, to ensure the information stays up-to-date.
As with our delete functionality, we’ll want some additional security here. The edit link is only visible to the owner of the listing, but an unauthorized user could still find a way to navigate to the edit page. To handle that scenario, we’ll make sure the controller action is protected. Should a user attempt to update a listing they don’t own, they will receive a message: ‘You don’t have permission to do that.’
📚 The term protected controller action refers a controller action that is secured through an authentication or authorization check. This means that only users who meet certain criteria can access the functionality of that action.
Conceptualizing the route
Our route should listen for PUT requests on /listings/:listingId.
| Action | What It Does | HTTP Verb | Route |
|---|---|---|---|
| Update | Update a single listing | PUT |
/listings/:listingId |
Now that we know our route, let’s update our form’s action and method attributes:
<!-- views/listings/edit.ejs -->
<!-- Update the form -->
<form action="/listings/<%= listing._id %>?_method=PUT" method="POST"></form>
Scaffolding the function
Next, let’s build the scaffolding for our controller function.
For now, we’ll just be confirming the following:
- The
listingIdis correctly passed throughreq.params. - The
userobject is accessible viareq.session.
Update controllers/listings.js with the code below:
// controllers/listings.js
router.put('/:listingId', async (req, res) => {
try {
console.log('listingId:', req.params.listingId);
console.log('user:', req.session.user);
res.send(`A PUT request was issued for ${req.params.listingId}`);
} catch (error) {
console.log(error);
res.redirect('/');
}
});
Test that everything is working as expected by submitting the edit form.
Writing the controller action
Now that we have confirmed access to req.params.listingId and req.session.user._id, let’s complete the controller action.
The controller action should accomplish the following:
- Retrieve a specific listing using its
ObjectId. - Verify permissions by comparing the
ownerproperty of the listing withreq.session.user._id. If the user making the request is not the owner, they should receive the message: “You don’t have permission to do that.” - If the user is the owner, the listing should be updated using the
updateOne()method.
Let’s get started:
Retrieving the listing
First, let’s remove the console logs and retrieve the listing using the findById() method:
// controllers/listings.js
router.put('/:listingId', async (req, res) => {
try {
const currentListing = await Listing.findById(req.params.listingId);
res.send(`A PUT request was issued for ${req.params.listingId}`);
} catch (error) {
console.log(error);
res.redirect('/');
}
});
Implementing a permission check
Next, let’s address the permission check. We’ll use the equals() method to check equality between the current listing’s owner and the user._id on req.session.
Update the function with the following if...else block:
// controllers/listings.js
router.put('/:listingId', async (req, res) => {
try {
const currentListing = await Listing.findById(req.params.listingId);
if (currentListing.owner.equals(req.session.user._id)) {
console.log('Permission granted');
} else {
console.log('Permission denied');
}
res.send(`A PUT request was issued for ${req.params.listingId}`);
} catch (error) {
console.log(error);
res.redirect('/');
}
});
Updating the listing
Let’s take a moment to explore the updateOne() method.
When updateOne() is called on a document instance, it updates the document with the provided data, like so:
const docInstance = await Model.findById(req.params.modelId);
docInstance.updateOne({ name: 'Alex' });
🧠 The
updateOne()method can also be called on the model. In this context, it requires an object specifying the criteria to identify the document for update. We’re not using this approach because we need to verify user permission before deletion.
Update the function as shown below:
// controllers/listings.js
router.put('/:listingId', async (req, res) => {
try {
const currentListing = await Listing.findById(req.params.listingId);
if (currentListing.owner.equals(req.session.user._id)) {
await currentListing.updateOne(req.body);
res.redirect('/listings');
} else {
res.send("You don't have permission to do that.");
}
} catch (error) {
console.log(error);
res.redirect('/');
}
});
Test it out by updating a listing!