I'm a beginner in the MERN stack, but I'm trying to create a social network application with the help of various tutorials on YouTube when I get stuck. I need a little more experienced thinking on how best to upload an image to the cloudinary. When I study, I want to learn in the right direction how people work in companies and how they work in the real world.
I found two types of code on youtube that works the same (uploads an image to cloudinary). I wonder which is better. A new API is created in one tutorial, something like this:
cloudinary.config({
cloud_name: '',
api_key: '',
api_secret: '',
});
router.post('/upload', authenticateUser, (req, res) => {
try {
if (!req.files || Object.keys(req.files).length === 0)
return res.status(400).json({ msg: 'No files were uploaded.' });
const file = req.files.file;
if (file.size > 1024 * 1024) {
removeTmp(file.tempFilePath);
return res.status(400).json({ msg: 'Size too large' });
}
if (file.mimetype !== 'image/jpeg' && file.mimetype !== 'image/png') {
removeTmp(file.tempFilePath);
return res.status(400).json({ msg: 'File format is incorrect.' });
}
cloudinary.v2.uploader.upload(
file.tempFilePath,
{ folder: 'test' },
async (err, result) => {
if (err) throw err;
removeTmp(file.tempFilePath);
res.json({ public_id: result.public_id, url: result.secure_url });
}
);
} catch (err) {
return res.status(500).json({ msg: err.message });
}
});
router.post('/destroy', authenticateUser, (req, res) => {
try {
const { public_id } = req.body;
if (!public_id) return res.status(400).json({ msg: 'No images Selected' });
cloudinary.v2.uploader.destroy(public_id, async (err, result) => {
if (err) throw err;
res.json({ msg: 'Deleted Image' });
});
} catch (err) {
return res.status(500).json({ msg: err.message });
}
});
const removeTmp = path => {
fs.unlink(path, err => {
if (err) throw err;
});
};
export default router;
And the other shot does it all the same from the controller. For example if I want to make a new post my controller looks like this:
export const createPost = async (req, res) => {
const myCloud = await cloudinary.v2.uploader.upload(req.body.image, {
folder: 'posts',
});
const { description, image } = req.body;
if (!image) {
throw new BadRequestError('Image is required');
}
const user = await User.findById(req.user.userId);
const newPost = new Post({
description,
image: {
public_id: myCloud.public_id,
url: myCloud.secure_url,
},
username: user.username,
avatar: user.avatar,
user: req.user.userId,
});
const post = await newPost.save();
res.status(StatusCodes.CREATED).json({ post });
};
I create an image in the controller and upload it when I submit the form in react. They both work the same, I tried, but I'm interested in whether it's necessary to make a new endpoint or it's enough to upload an image from the controller, delete an image from the controller, etc.
You can use multer module for uploading content in the directory and if you want to upload in S3 bucket then you have to use module accordingly.
For multer -> https://www.npmjs.com/package/multer
For multer-S3 -> https://www.npmjs.com/package/multer-s3
Related
I have a database in which the user is a parent and it has some child documents child document has image data too and those images are stored in the AWS s3 bucket. I used MongoDB middleware remove to perform cascade delete. If I delete parents then the data from the child table is also deleted but the image data remains in the s3 bucket. How can I implement the logic that image data should also be deleted from the server on deleting the parent? I also wrote AWS SDK delete APIs but how can I connect them to the parent document?
// This is the parent delete API
function user_delete(req, res, next) {
User.findOne({ _id: req.params.id })
.then(user => {
if (!user) {
return next('The user you requested could not be found.')
}
Child.remove({ userId: user._id }).exec(); //using middleware to delete child when parent deleted
user.remove();
return res.status(200).send('User deleted');
}).catch(err => {
console.log(err)
if (err.kind === 'ObjectId') {
return next(res.status(404).send({
success: false,
message: "User not found with id "
}));
}
return next(res.status(500).send({
success: false,
message: "Error retrieving User with id "
}));
});
};
router.delete('/delete/:id', user_delete);
// Delete function for aws SDK delete a file from s3
function deleteFileStream(fileKey, next) {
const deleteParams = {
Key: fileKey,
Bucket: bucket_name,
}
s3.deleteObject(deleteParams, (error, data) => {
next(error, data)
})
}
exports.deleteFileStream = deleteFileStream;
// Child delete document API
function delete_child(req, res, next) {
Child.findById(req.params.id)
.then(child => {
if (!child) {
return next(res.status(404).send({
success: false,
message: "child not found with id " + req.params.id
}));
}
// deleting the images of questions also if it has image
if(question.file !== '') {
const url_parts = url.parse(question.file, true);
const datas = url_parts.pathname.split('getImage/')
const filekey = datas.pop();
console.log(filekey);
deleteFileStream(filekey); // calling the delete function
}
child.remove()
return res.send({
success: true,
message: "child successfully deleted!"
});
}).catch(err => {
if (err.kind === 'ObjectId' || err.name === 'NotFound') {
return res.status(404).send({
success: false,
message: "child not found with id " + req.params.id
});
}
return res.status(500).send({
success: false,
message: "Could not delete question with id " + req.params.id
});
});
}
router.delete('/delete/:id', delete_child);
If I call the child API the image is also deleted from the server as I am deleting it but if I delete the parent the child is deleted but not the image. Can anybody tell me, please? I am struggling with this use case.
You can collect all image paths from the child before deleting Parent. After deleting the parent, you have to call S3 delete API and provide those paths to delete.
const deleteS3Files = async (imageKeys: []) => {
const bucketParams = {
Bucket: "BUCKET_NAME",
Delete: {
Objects: imageKeys
}
};
try {
const data = await s3Client.send(new DeleteObjectsCommand(bucketParams));
return data; // For unit tests.
console.log("Success. Object deleted.");
} catch (err) {
console.log("Error", err);
}
};
async function user_delete(req, res, next) {
try {
const user = await User.findOne({ _id: req.params.id });
if (!user) {
return next('The user you requested could not be found.')
}
//Collect all the child image paths from the collection.
//create an array of key objects like imageKeys.
//const imagesKeys : [{Key: imageKey1}, {Key: imageKey2}....];
const imageKeys = [];
await Child.remove({ userId: user._id }).exec();
await user.remove().exec();
//After deleting data, you can remove the images
await deleteS3Files(imageKeys)
return res.status(200).send('User deleted');
} catch (error) {
console.log(err)
if (err.kind === 'ObjectId') {
return next(res.status(404).send({
success: false,
message: "User not found with id "
}));
}
return next(res.status(500).send({
success: false,
message: "Error retrieving User with id "
}));
}
router.delete('/delete/:id', user_delete);
I'm trying to delete a file by its id using gridfs but I get this error when calling the delete API.
Controller :
let gfs;
connect.once("open", () => {
gfs = Grid(connect.db, mongoose.mongo);
gfs.collection("uploads");
});
exports.deleteFile = (req, res) => {
try {
gfs.remove(
{ _id: req.params.id, root: "uploads" },
(err, gridStore) => {
if (err) {
return res.status(404).send({ message: err });
} else {
return res.send({ message: "File deleted successfuly" });
}
}
);
} catch (error) {
return res.status(500).send({
message: error.message,
});
}
};
exports.deleteFileByFilename = async (req, res, next) => {
const file = await gfs.files.findOne({ filename: req.params.filename });
const gsfb = new mongoose.mongo.GridFSBucket(conn.db, { bucketName: 'uploads' });
gsfb.delete(file._id, function (err, gridStore) {
if (err) return next(err);
res.status(200).end();
});
};
// #route DELETE /files/:filename
// #desc Delete file
app.delete('/files/:filename', async (req, res) => {
const file = await gfs.files.findOne({ filename: req.params.filename });
const gsfb = new mongoose.mongo.GridFSBucket(conn.db, { bucketName: 'uploads' });
gsfb.delete(file._id, function (err, gridStore) {
if (err) {
res.status(404).send('no file found')
}
res.status(200).send('deleted successfully')
});
});
On client side:
const delImage = async (fileName) => {
await axios.delete(`http://localhost:5000/files/${fileName}`);
console.log('fileDeleted');
getData(); // reminder to REFETCH the data so that database with deleted file is refreshed
}
I also made a video on this - full file uploads, multiupload, display and delete using MERN stack and NPM Multer thing, in here: https://youtu.be/4WT5nvfXcbs
Docs for the video with full code: https://docs.google.com/document/d/1MxvNNc9WdJT54TpanpFBT6o7WE_7hPAmDhdRNWE8A9k/edit?usp=sharing
I am creating URL Shortener Microservice application.I have a mongodb cluster that i save my all url links. I am successfully connect to database.I am making post request to save my posted url. Related code is here
app.post('/api/shorturl', (req, res) => {
const bodyUrl = req.body.url;
const something = dns.lookup(
urlParser.parse(bodyUrl).hostname,
(error, address) => {
if (!address) {
res.json({ error: 'Invalid URL' });
} else {
const url = new Url({ url: bodyUrl });
url.save((err, data) => {
res.json({
original_url: data.url,
short_url: data.id,
});
});
}
}
);
});
So, I can save my new url in database succesfully.Here also related cluster after post request
But my problem is with get request. I dont know why i cant find the url links by id. Here also my get request
app.get('/api/shorturl/:id', (req, res) => {
// const id = req.body.id;
Url.findById({ _id: req.body.id }, (err, data) => {
if (!data) {
res.json({ error: 'Invalid URL' });
} else {
res.redirect(data.url);
}
});
});
You need to either use:
Url.findOne({ _id: req.params.id }, (err, data) => {
if (!data) {
res.json({ error: 'Invalid URL' });
} else {
res.redirect(data.url);
}
});
or:
Url.findById(req.params.id, (err, data) => {
if (!data) {
res.json({ error: 'Invalid URL' });
} else {
res.redirect(data.url);
}
});
findOne takes an object as the argument (like you have).
findById just takes the ID as the argument.
You seem to be combining the two options.
Edit: I found another issue with your code, you are trying to pull the id from req.body.id, but in this case, you need to use req.params.id. The code in my post has been updated.
Upload and set default controller functions are working perfectly. However, we are trying to implement delete image from Cloudinary as well. How can it be done?
In the documentation it was confusing. Here is the code:
const cloudinary = require('cloudinary');
const HttpStatus = require('http-status-codes');
const User = require('../models/userModels');
cloudinary.config({
cloud_name: 'name',
api_key: 'key',
api_secret: 'secret'
});
module.exports = {
UploadImage(req, res) {
cloudinary.uploader.upload(req.body.image, async result => {
await User.update(
{
_id: req.user._id
},
{
$push: {
images: {
imgId: result.public_id,
imgVersion: result.version
}
}
}
)
.then(() =>
res
.status(HttpStatus.OK)
.json({ message: 'Image uploaded successfully' })
)
.catch(err =>
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error uploading image' })
);
});
},
DeleteImage(req, res) {
cloudinary.uploader.destroy(req.params.image, async result => {
await User.update(
{
_id: req.user._id
},
{
$pull: {
images: {
imgId: result.public_id,
imgVersion: result.version
}
}
}
)
.then(() =>
res
.status(HttpStatus.OK)
.json({ message: 'Image deleted successfully' })
)
.catch(err =>
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error deleting image' })
);
});
},
async SetDefaultImage(req, res) {
const { imgId, imgVersion } = req.params;
await User.update(
{
_id: req.user._id
},
{
picId: imgId,
picVersion: imgVersion
}
)
.then(() =>
res.status(HttpStatus.OK).json({ message: 'Default image set' })
)
.catch(err =>
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error occured' })
);
}
};
We are using Node.js Express with Mongoose. How can we include extra function here that will remove images?
There are two options to delete an image from cloudinary:
By using the admin API. For example in Node:
cloudinary.v2.api.delete_resources(['image1', 'image2'],
function(error, result){console.log(result);});
Using our upload API:
cloudinary.v2.uploader.destroy('sample', function(error,result) {
console.log(result, error) });
Please note that using our admin API is rate limited and you might want to use the second option.
it just because your req.params.image is like https:https://res.cloudinary.com/your/image/upload/v1663358932/Asset_2_bdxdsl.png
instead write your delete request like so :
cloudinary.v2.uploader.destroy('Asset_2_bdxdsl', function(error,result) {
console.log(result, error) })
ps: Asset_2_bdxdsl is your image name without prefix .png !!
I am using multer package
I have two ways of uploading images to my server one is with Array and the other is using fields.
The only thing that works is the uploadArray for the /status route.. when i'm uploading to /update it gives me this error SyntaxError: Unexpected token < in JSON at position 0.. The controller for the /update is just the same as the postController.js the only difference it that i update fields instead of save new one.
/utils/lib/account.js
const storage = multer.memoryStorage();
// These two image might be available in the req.files depending on what was sent
const upload = multer({storage}).fields([{ name: 'photo', maxCount: 1 }, { name: 'cover', maxCount: 1 }]);
const uploadArray = multer({storage}).array('image', 12);
exports.upload = (req, res, next) => {
upload(req, res, function (err) {
if (err) {
console.log(err);
}
next();
});
};
exports.uploadArray = (req, res, next) => {
uploadArray(req, res, function (err) {
if(err){
console.log(err);
}
next();
});
};
/routes.js
router.route('/status')
.all(helper.verifyToken)
.all(helper.uploadArray)
.get(status.get)
.post(status.new) // file uploads
.delete(status.delete);
router.route('/update')
.all(helper.verifyToken)
.all(helper.upload)
.post(account.update_profile) // file uploads
The only thing that works here is the uploadArray
/postController.js
new:
(req, res) => {
const uri = new DataUri();
const promises = [];
const images = [];
//Get buffer from files
for(let key in req.files){
const obj = req.files[key];
uri.format('.png', obj.buffer);
let uriContent = uri.content;
promises.push(uploadAsync(uriContent)); //upload each image
}
//Init upload
function uploadAsync(buffer){
return new Promise((resolve, reject) => {
cloudinary.v2.uploader.upload(buffer, function(error, result) {
if(error){
reject(error);
}
if(result.url){
images.push(result.url);
resolve(images);
}
});
});
}
Promise.all(promises)
.then(results => {
// Init post model
console.log('test1')
const post = new Post({
post_img: images,
post_description: req.body.description,
post_by: req.body.id,
photoURL: req.body.id,
post_comments: []
});
// Save data
post.save(function(err) {
if(err) {
res.send(err);
}
var leanObject = post.toObject(); // Transform instance to plain JS Object for modification
// Modifications
leanObject['post_by'] = {
_id: leanObject['post_by'],
display_name: req.body.user, // Add current user display name
photo_url: req.body.user_photo
};
res.json({message: 'Success', type: 'success', code: 200, data: leanObject});
});
})
.catch(err => {
console.log(err);
});
},