Mongoose - Best way to update a record - node.js

I want to update a record with mongoose. I've seen other answers on SO. but they are very old like 4 - 5 years ago.
What is the best way to update a record with mongoose ? both for PUT and PATCH requests.
Currently I'm doing it like so. but I don't think this is a good way for models with many fields.
export const updateTestimonial = asyncHandler(async (req, res) => {
const values = await testimonialSchema.validateAsync(req.body);
const testimonial = await Testimonial.findById(req.params.id);
if (!testimonial) {
return res.status(404).json({ error: 'Testimonial not found' });
}
testimonial.name = values.name;
testimonial.text = values.text;
const updatedTestimonial = await testimonial.save();
res.status(200).json(updatedTestimonial);
});

You can use findByIdAndUpdate
export const updateTestimonial = asyncHandler(async (req, res) => {
const values = await testimonialSchema.validateAsync(req.body);
const testimonial = await Testimonial.findByIdAndUpdate(req.params.id, values, {new : true});
if (!testimonial) {
return res.status(404).json({ error: 'Testimonial not found' });
}
res.status(200).json(testimonial);
});

Related

How can I update my images with multer nodeJS(The images are stored in mongodb)

I am creating the backend server for a ecommerce website. I handle file uploads with multer and store them in gridfs. Now, when updating a new product, not all images may be submitted to be updated. I need a way to identify what field was updated, so I can delete the current file and upload the new file to mongodb. I've reached the point where I think it's impossible, but I'm still trying. Are there any alternative approach I could take. All responses are greatly appreciated.
Here is the code for the router and the 3 middleware functions I call when a request is made to said route.
```
router
.route("/:id")
.put(
productController.readPhotos,
productController.appendPhotoOnRequestBody,
productController.updateProduct
)
exports.readPhotos = upload.fields([
{ name: "coverPhoto", maxCount: 1 },
{ name: "colorPhoto", maxCount: 4 },
]);
exports.appendPhotoOnRequestBody = asyncHandler(async (req, res, next) => {
req.addedFiles = [];
if (
req.originalUrl === "/api/products" &&
req.method === "POST" &&
!req.files
) {
// Error message to send to user if the above condition is true.
const message = `To create a new product, a coverPhoto and at least one color with a colorPhoto must be specified`;
return next(new AppError(400, message));
} else if (!req.files) next();
if (req.files?.coverPhoto) {
// Extract coverPhoto
const [coverPhoto] = req.files.coverPhoto;
// Process images
const coverPhotoBuffer = await processImage(coverPhoto.buffer, [300, 300]);
// Pushing the coverPhoto to the db
const coverPhotoStream = Readable.from(coverPhotoBuffer);
const coverPhotoValue = await pushToDbFromStream(
coverPhotoStream,
req,
coverPhoto
);
req.body.coverPhoto = coverPhotoValue.filename;
req.addedFiles.push(req.body.coverPhoto);
}
if (req.files?.colorPhoto) {
// Extract colorPhotos
const colorPhotos = [...req.files.colorPhoto];
const colorPhotoBuffers = await Promise.all(
colorPhotos.map((photo) => processImage(photo.buffer, [50, 50]))
);
// Pushing the colorPhotos to the db
const colorPhotosStreams = colorPhotoBuffers.map((buff) =>
Readable.from(buff)
);
const colorPhotoValues = await Promise.all(
colorPhotosStreams.map((stream, index) =>
pushToDbFromStream(stream, req, colorPhotos[index])
)
);
req.body.colors = JSON.parse(req.body.colors);
colorPhotoValues.forEach((value, index) => {
req.body.colors[index].colorPhoto = value.filename;
});
req.body.colors.forEach((color) => req.addedFiles.push(color.colorPhoto));
}
next();
});
exports.updateProduct = asyncHandler(async (req, _, next) => {
req.product = await Product.findByIdAndUpdate(req.params.id, req.body, {
runValidators: true,
new: true,
});
next();
});
exports.updateProduct = asyncHandler(async (req, _, next) => {
req.product = await Product.findByIdAndUpdate(req.params.id, req.body, {
runValidators: true,
new: true,
});
next();
});
```
What I have done is upload new images, then merge these images with the images that are in MongoDB, and update the document with the images merged.
In this case, you are not querying the product first to obtain the images previously added.
I consider it necessary because MongoDB will replace the images previously added if you only send the new images.
There is a reference to the images in a color property of the product Schema. That uses the embedded document style. Instead, I could use the referenced way instead, and create new Color documents and update them individually.

Get all documents in collection using Cloud Firestore

I read several documentation but I don't understand why I should use an extra layer(foreach) in my code when I read all of the data inside a collection using Firebase (Cloud Firestore).
Here is the original documentation:
https://firebase.google.com/docs/firestore/query-data/get-data#get_all_documents_in_a_collection
Here is my code:
async loadUsers(): Promise<User[]> {
const users = new Array<User>();
const snapshot = await this.firestore.collection('users').get();
snapshot.forEach((collection) => {
collection.docs.forEach(doc => {
users.push(doc.data() as User);
});
});
return users;
}
As I understand it should work like this:
async loadUsers(): Promise<User[]> {
const users = new Array<User>();
const snapshot = await this.firestore.collection('users').get();
snapshot.forEach(doc => {
users.push(doc.data() as User);
});
return users;
}
Error message:
"Property 'data' does not exist on type 'QuerySnapshot'."
.collection().get() does NOT return an array; it returns a QuerySnapshot, which has a property .docs, which is an array of QueryDocumentSnapshot, each of which has a property .data, which is the data read from the document.
Documentation
https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference
In new modular firebase firestore(version 9.+) it should be like this:
import { getFirestore, collection, query, getDocs } from 'firebase/firestore/lite'
async readAll() {
const firestore = getFirestore()
const collectionRef = collection(firestore, '/users')
let q = query(collectionRef, orderBy('createTimestamp', 'desc'))
const querySnapshot = await getDocs(q)
const items = []
querySnapshot.forEach(document => {
items.push(document.data())
})
return items
}
I could not find any parameter on querySnapshot directly that is something like .docs was and included whole array before. So it is kinda like onSnapshot is and was.
Based on #LeadDreamer answer, I could manage to simplify the code
async loadUsers(): Promise<User[]> {
const users = new Array<User>();
await this.firestore.collection('users').get().subscribe(querySnapshot => {
querySnapshot.docs.forEach(doc => {
users.push(doc.data() as User);
});
});
return users;
}
There seems to be no other way but to iterate.
const q = query(collection(db, "item"));
getDocs(q).then( response => {
const result = response.docs.map(doc=>({
id: doc.id,
...doc.data(),
}))
console.log(result);
}).catch(err=>console.log(err))

TypeError: Cannot set property 'brief_title' of null - node and Mongoose

I have an app using Node/Express/Mongo and I'm running into trouble when I want to edit a document. I can add documents no problem but when I built out the Edit form I get the error in the title. I can fetch the document as well and see the information I've put in. The problem is when I try to save whatever changes I've made.
Here's my edit function: The error occurs at the brief.brief_title = updated_brief_title in the FindByIdAndUpdate method.
exports.postEditBrief = (req, res, next) => {
const briefId = req.body.briefId;
const updated_brief_title = req.body.brief_title;
const updated_country = req.body.country;
const updated_psg = req.body.psg;
const updated_one_year_withholding = req.body.one_year_withholding;
const updated_withholding_only = req.body.withholding_only;
const updated_practice_advisory = req.body.practice_advisory;
const updated_courthouse = req.body.courthouse;
const updated_pages = req.body.pages;
const updated_additional_psg = req.body.additional_psg;
const updated_gangs = req.body.gangs;
const updated_gang_name = req.body.gang_name;
const updated_link = req.body.link;
Brief.findByIdAndUpdate(briefId)
.then(brief => {
brief.brief_title = updated_brief_title;
brief.country = updated_country;
brief.psg = updated_psg;
brief.one_year_withholding = updated_one_year_withholding;
brief.withholding_only = updated_withholding_only;
brief.practice_advisory = updated_practice_advisory;
brief.courthouse = updated_courthouse;
brief.pages = updated_pages;
brief.additional_psg = updated_additional_psg;
brief.gangs = updated_gangs;
brief.gang_name = updated_gang_name;
brief.link = updated_link;
return brief.save();
})
.then(result => {
console.log(Brief);
res.redirect('/');
})
.catch(err => console.log(err));
};
I've played around with the various "find and update" methods in Mongoose but the results are the same.
You need to create an object of the data you want to update and pass it as a second argument in the function, when you do .then() it gets you the result of the operation you are doing and you don't need to call .save() as it does that internally
Correct version should look something like this
exports.postEditBrief = (req, res, next) => {
const briefId = req.body.briefId;
let update = {
brief_title: req.body.brief_title,
country: req.body.country,
psg: req.body.psg,
one_year_withholding: req.body.one_year_withholding,
withholding_only: req.body.withholding_only,
practice_advisory: req.body.practice_advisory,
courthouse: req.body.courthouse,
pages: req.body.pages,
additional_psg: req.body.additional_psg,
gangs: req.body.gangs,
gang_name: req.body.gang_name,
link: req.body.link
}
Brief.findByIdAndUpdate(briefId, update)
.then(result => {
console.log(result);
res.redirect('/');
})
.catch(err => console.log(err));
};
You can simplify it even further using object destructuring.
Update:
You need to use $set with findOneandUpdate if you don't want to overwrite your document
exports.postEditBrief = (req, res, next) => {
const briefId = req.body.briefId;
let update = {
brief_title: req.body.brief_title,
country: req.body.country,
psg: req.body.psg,
one_year_withholding: req.body.one_year_withholding,
withholding_only: req.body.withholding_only,
practice_advisory: req.body.practice_advisory,
courthouse: req.body.courthouse,
pages: req.body.pages,
additional_psg: req.body.additional_psg,
gangs: req.body.gangs,
gang_name: req.body.gang_name,
link: req.body.link
}
Brief.findOneAndUpdate(briefId, {$set:update})
.then(result => {
console.log(result);
res.redirect('/');
})
.catch(err => console.log(err));
};

How to use a variable as the field key for a query in mongodb?

how to use the title variable from the req.params, as the field key for a query in mongodb?
i would like to use const { title } = req.params in the query field like this{title:1}
i tried this below but it is not working
router.get('/books/:title', async (req, res) => {
const { title } = req.params
const book = await books.find({ title:1, _id:0 });
res.send(book);
});
You can just use [] for dynamic variable as follows:
If you want to use it query you can use it straight forward as follows
router.get('/books/:title', async (req, res) => {
const { title } = req.params
const book = await books.find({ [title]:"Some Title"});
res.send(book);
});
Try this query
const book = await books.find({},{title:1});

How to Lint code if I need additional mongodb request

Today I have this code. I learning NodeJS and all the time I have same question: I have a route. This route doing something, thats why I need some mongodb requests. In different ways I can need additional options or not. For example please look on my code:
const express = require('express');
const router = express.Router();
const debug = require('debug')('hackit:posts');
const boom = require('boom');
const config = require('../../config');
const db = require('monk')(config.mdbConnect);
const DBpgs = db.get('pgs');
const DBposts = db.get('posts');
const DBcategories = db.get('categories');
router.route( "/edit-:id" )
.get((req,res,next) => {
const ops ={
h1: "Edit post",
title: "Admin edit post",
description: "Admin page",
specialscript: "/javascripts/editpost.js"
};
DBposts.findOne({_id: req.params.id}, (err, saved) => {
if(saved.h1.length) debug('Edit «'+saved.h1+'»')
else debug('Create new post')
if(err) next(boom.badImplementation(err))
DBcategories.find({}, (err, categories) => {
if(err) next(boom.badImplementation(err))
let group = {}
if(saved.parent&&saved.parent.length){
DBpgs.findOne({parent: saved.parent}, (err, group) => {
if(err) next(boom.badImplementation(err))
res.render('admin/post', {ops, saved, categories, group})
})
}else{
res.render('admin/post', {ops, saved, categories, group})
}
})
})
})
module.exports = router;
There is only one route. The problem is if I got saved.parent I need to do additional request to db, to pick group options. I solved it just by if and ... its looks rude. Next two lines i type res.render('admin/post', {ops, saved, categories, group}) witch is the same line of code. I want to be a good programmer. Lint my code and say how I must fill my code please. Thanks!
The promise-based implementation would look something like this (I just modified your code directly in the answer so it probably has bugs, but it gives you the gist). Note that you get out of duplicating both the response as well as the error conditions.
router.route( "/edit-:id" )
.get((req,res,next) => {
const ops ={
h1: "Edit post",
title: "Admin edit post",
description: "Admin page",
specialscript: "/javascripts/editpost.js"
};
DBposts.findOne({_id: req.params.id})
.then(saved => {
if (saved.h1.length) debug('Edit «'+saved.h1+'»');
else debug('Create new post');
return DBcategories.find({})
.then(categories => {
if (saved.parent && saved.parent.length) {
return DBpgs.findOne({parent: saved.parent});
}
return {};
})
.then(group => {
res.render('admin/post', {ops, saved, categories, group})
})
.catch(error => {
next(boom.badImplementation(err));
});
});
});

Resources