I've been reading some CRUD / Mongoose guides, but haven't a good explainer for conditionally updating fields.
So for example, an action called updateItem is used in one place to update item.price but in another place it updates item.color. Does anyone know a good explanation or tutorial for Mongoose CRUD APIs that shows this?
I'm getting the blow code to work fine, but I have a feeling it could be cleaner :)
Thanks!!!
router.put('/tasks/:id', (req, res) => {
Task.findByIdAndUpdate(req.params.id,
req.body.owner ? { owner: req.body.owner } : { hours: req.body.hours }, { new: true })
.then(task => {
res.status(201).json(task)
})
.catch(err => {
console.log('Our error', err)
})
});
Another approach you could take is to first retrieve the object, and then only update the value if it is passed into the put request. An example of that could be something like this:
router.put('/tasks/:id', (req, res) => {
let price = req.body.price;
let color = req.body.color;
Task.findById(req.params.id, function (err, task) {
if (err) return handleError(err);
task.color = color || task.color;
task.price = price || task.price;
task.save(function(err, updatedTask) {
if err return handleError(err);
return res.send(updatedTask);
});
});
});
Here's another cleaner approach using async-await functions:
// Import promisify from utils
const promisify = require('utils').promisify;
// Wrap findByIdAndUpdate into a promise
const updateOwnerPromise = promisify(Task.findByIdAndUpdate);
// Write an async handler now
updateOwnerPromiseAsync = async (req, res) => {
const replacementObject = req.body.owner ? { owner: req.body.owner } : { hours: req.body.hours };
try {
await updateOwnerPromise(replacementObject, { new:true} );
return res.status(200).send({ message: 'Owner updated successfully!' });
} catch(err) {
// TODO: handle error here
console.log('Our error', err)
return res.status(500).send({ message: 'Failed to update owner, because of some issue at the server!' });
}
}
// Modify the express route with the handler
router.put('/tasks/:id', updateOwnerPromiseAsync);
Related
I'm trying to store Redis key value to a variable in nodejs, something like
let gPost;
redis.get("posts", async function (err, post) {
if (err) console.log(err);
if (post) gPost = post;
}
but this approach is giving me undefined. Is there any way by which I can store value to Redis? I've already searched for it and a few posts suggested using callbacks. But what I basically want is something like this:
router.post("/:id/likes", async (req, res) => {
try {
redis.get(`posts.${req.params.id}.likes`, function (err, likeCount) {
if (err) console.error(err.message);
redis.get(`posts.${req.params.id}`, async function (err, post) {
if (err) console.log(err);
if (post) {
await customCallback(likeCount, post, req, res);
const retPost = JSON.parse(post);
return res.send({ retPost, redis: true });
} else {
try {
const reqPost = await Post.findById(req.params.id).lean().exec();
redis.set(`posts.${req.params.id}`, JSON.stringify(reqPost));
await customCallback(likeCount, reqPost, req, res);
const retPost = JSON.parse(post);
return res.send({ retPost, redis: false });
} catch (err) {
console.log(err);
}
}
});
console.log(upPost);
});
} catch (err) {
return res.status(500).send({ message: err.message });
}
});
So, here I want to increase my likes count on a post. But I don't want to hit any unnecessary requests to the database. Here first I'm getting posts.id.likes and inside it, I'm trying to fetch that post. If a post is found I'll increase my likes there only. Else, I'll make an API call to the database to fetch that post. Can you where I'm getting it wrong, or any other efficient approach I can use? Thanks.
If you're using a recent version of node-redis, you can just use promises.
Your code seems to simplify to something like
/**
* Get a post from Redis or the database.
*
* If found in the database, caches it in Redis.
*
* Returns a pair: post object and whether it was from Redis.
* #param id Post ID.
*/
async function getPost(id) {
const redisKey = `posts.${id}`;
const postData = await redis.get(redisKey);
if (postData) {
return [JSON.parse(postData), true];
}
const postObj = await Post.findById(id).lean().exec();
await redis.set(redisKey, JSON.stringify(post));
return [postObj, false];
}
router.post("/:id/likes", async (req, res) => {
try {
const { id } = req.params;
const [postObj, fromRedis] = await getPost(id);
const likeCount = await redis.get(`posts.${id}.likes`);
await customCallback(likeCount, postObj, req, res);
return res.send({ postObj, redis: fromRedis });
} catch (err) {
return res.status(500).send({ message: err.message });
}
});
I am trying to update my post on my MongoDB database, but it shows: Cannot read properties of null (reading 'updateOne')
const router = require("express").Router();
const Post = require("../moduls/Post");
router.post("/", async (req, res) => {
const newpost = Post(req.body);
try {
const savedPost = await newpost.save();
res.status(200).json(savedPost);
} catch (error) {
res.status(500).json(error)
}
});
Here I try to write a code for updating my post. But it doesn't work.
//Update Post
router.put("/:id", async (req, res) => {
// try {
const post = await Post.findById(req.params.id);
if (post.userId === req.body.userId) {
await post.updateOne({ $set: req.body })
}
else {
res.status(403).json("You can't update it")
}
// } catch (error) {
// res.status(500).json("Internal Error")
// }
})
module.exports = router;
Based on your question, there are a few things that are wrong in your code:
Add always a check that the operation has succeeded before moving on.
Use Post instead of post to perform operations on.(Post Mongoose model instead of an instance of a Post)
In your case you can use findOneAndUpdate no need to find the corresponding Post first and then update after.
router.put("/:id", async (req, res) => {
try {
const postUpdated = await Post.findOneAndUpdate(
{
_id: mongoose.Types.ObjectId(req.params.id),
userId: mongoose.Types.ObjectId(req.body.userId) // assuming it is saved as a mongo id
},
req.body,
{ new: true }
);
if (!postUpdated) {
throw new Error('could not update Post');
}
res.json(postUpdated);
} catch (e) {
res.sendStatus(500);
}
});
As an addition:
Your commented error handling is actually needed, due to the fact that Express does not handle the returned promise for you.(This is what makes you get a UnhandledPromiseRejectionWarning)
Your code also does not provide any form of validation of the incoming request, you might want to consider checking first what data you are receiving from the client before inserting it into the database.
I am trying to be better at reformatting my code, and hoping someone can please let me know if I have done so correctly.
Here is my original copy
app.get('/lookup/:url', function (req, res) {
country.init().then(function() {
console.log(country)
country.open(req.params.url).then(function(site) {
site.analyze().then(function(results) {
res.json(results)
})
})
})
})
Here is what I formatted it to:
app.get('/:url', async function (req, res) {
try {
await country.init();
const site = await country.open(decodeURIComponent(req.params.url));
const data = await site.analyze();
return res.status(200).json(data);
} catch (ex) {
console.log(ex);
return res.status(500).json({ message : "Oops." });
}
Can anyone provide advice if I have done so correctly?
On the following post method, I'm having some issues due to moongose async. res.send(suggestions) is executed first then Expense.findOne.exec
app.post('/suggestions', async function(req, res) {
const suggestions = await req.body.map((description) => {
Expense.findOne({ description: new RegExp(description, 'i') }).exec((err, result) => {
if (result) {
console.log(result.newDescription);
return {
description,
newDescription: result.newDescription,
category: result.category,
subcategory: result.subcategory
};
}
});
});
res.send(suggestions);
});
The result is a array of null values. How can I executed a query for each item, then execute res.send(suggestion)?
Found solution with the following code:
app.post('/suggestions', async function(req, res) {
try {
if (req.body.length > 0) {
const suggestions = req.body.map((description) =>
Expense.findOne({ description: new RegExp(description, 'i') })
);
const results = await Promise.all(suggestions);
return res.send(results);
}
} catch (e) {
console.log('error', e);
}
});
I am attempting to check if a user owns a document before updating it or deleting it, and would like to keep this as DRY as possible. Ideally, I would not have to make two calls to the database where I would first findById().then(doc => {check if user owns document and then -> doc.findByIdAndUpdate() }) but rather keep this as one call to the DB.
I am constantly having to execute this check on express routes and have thought about implementing this layer of logic on the mongoose .pre('update') middleware. but am unaware how to pass the incoming userid from the req object to my middleware validation function?
Are there any better layers to implement this checking functionality? or am I going to have to make the two requests to the database every time I want to check if a user owns a document and write this out in every express route?
My current implementation is:
const addDocToDoc = (req, res, next) => {
let doc1id = req.params.id;
let doc2id = req.params.doc2id;
Doc1.findById(doc1id)
.then(doc1 => {
if(userCanAlter(doc1, req.user, res)) {
doc1.doc2s.push(doc2id)
return doc1.save().then(updatedDoc1 => res.send(updatedDoc1))
}
}).catch(next)
}
Where userCanAlter() looks like this:
function userCanAlter(instance, user, res) {
if (!instance) { res.status(404).send("Document does not exist."); return false}
if (instance.user != user) { res.status(401).send("User unauthorized"); return false}
else return true;
}
Obviously, this is a very simple update but the more complex updates would require more configuration before saving.
Current implementation in question found to be the best & DRY’est implementation.
You can simply wrap your user in find query and use findOne(), Something like:
const addDocToDoc = (req, res, next) => {
const {
user = ''
} = req;
const {
id = '', doc2id = ''
} = req.params;
Doc1.findOne({
_id: id,
user
})
.then(doc => {
if (!doc) {
return res.status(400).json({
message: 'User Not Found!!'
});
}
doc.doc2s.push(doc2id);
doc.save()
.then(updatedDoc1 => res.status(200).json(updatedDoc1))
.catch(err => res.status(500).json({
message: 'Error While Updating!!',
error: err
}));
})
.catch(err => res.status(500).json({
message: 'Error While Fetching!!',
error: err
}));
}
Also I'd suggest if you work a bit on naming things, as this may mess up things a few times.
In case if you wanna throw specific error for unauthorized user, you can stick to your way of implementation, just don't need a separate method to check ownership. I've simplified it with async/await and the code is:
const addDocToDoc = async (req, res, next) => {
try {
const {
user = ''
} = req;
const {
id = '', doc2id = ''
} = req.params;
const doc = await Doc1.findById(id);
if (!doc || !doc.user || doc.user !== user) {
return res.status(401).json({
message: 'Unauthorized User!!'
});
}
doc.doc2s.push(doc2id);
const updatedDoc1 = await doc.save();
return res.status(200).json(updatedDoc1);
} catch (err) {
res.status(500).json({
message: 'Error While Updating Record!!',
error: err
});
}
}
Ps: You may need some modification as i couldn't get a chance to run it.
Hope this helps :)