Best way to upload file with text inputs - node.js

I am trying to create an item where users will be able to upload a file image, that uploads the file to amazon s3, and returns a string for the image address that should be passed into mongoDB as a single document along with text inputs such as name of item, description, dates etc.
So far I have created a simple mongoDB model with a name and an image to test uploading a file with one text input, but when I try to test it in postman using form-urlencoded, there is no option to select a file, and if i try to test it in form-data, I get an empty string for the name input, but my code does read the file and return a string for the file, just not both together.
My code is:
setup file for amazon s3
const multer = require("multer");
const multerS3 = require("multer-s3");
const { secret_key, access_key, bucket_name } = require("../config/config");
aws.config.update({
secretAccessKey: secret_key,
accessKeyId: access_key,
region: "us-east-2"
});
const s3 = new aws.S3();
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
cb(null, true);
} else {
cb(new Error("Invalid Mime Type, only JPEG or PNG"), false);
}
};
const upload = multer({
fileFilter: fileFilter,
storage: multerS3({
s3: s3,
bucket: bucket_name,
acl: "public-read",
metadata: function(req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function(req, file, cb) {
cb(null, Date.now().toString());
}
})
});
module.exports = upload;
controller file for the upload at the target route
const upload = require("../services/file-upload");
const singleUpload = upload.single("img");
let uploadArtefact = (req, res) => {
const { name } = req.body;
singleUpload(req, res, err => {
if (err) {
return res.status(422).send({
errors: [{ title: "File Upload Error", detail: err.message }]
});
}
let artefact = new Artefact({
name: name,
img: req.file.location
});
if (!name) {
return res.json({
success: false,
error: "Invalid Inputs"
});
}
artefact.save(err => {
if (err) {
return res.json({ sucess: false, error: err });
}
return res.json({ success: true });
});
});
};
module.exports = uploadArtefact;
So I was wondering what would be the best approach to this? Is there a way to submit files with text in one request? I would preferably want to be able to find a way to send them both together.

Nevermind, I realised that multer can parse the text but i just needed to put the const { name } = req.body; inside the singleUpload{} section.

Related

how to use sharp to resize image while uploading to s3 nodejs

I use multer to upload the images to s3. The user clicks on upload button in the frontend, and uploads the image, which is then uploaded to s3 with the helper multer backend.
But the images uploaded aren't optimised i.e if the user uploads a 4mb image, the image is uploaded with any compression. That indeed led to slow website frontend.
How do I optimise or compress the image using sharp ?
Code to upload to s3 (nodejs):
const s3 = new aws.S3({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECERT_KEY,
});
//S3
const uploadS3 = multer({
storage: multerS3({
s3: s3,
bucket: "testbucket",
acl: "public-read",
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function (req, file, cb) {
cb(null, shortid.generate() + "-" + file.originalname);
},
}),
});
router.post("/poster/create", uploadS3.array("posterPictures"), createPoster);
CreatePoster is a function that returns the path of the image in s3:
exports.createPoster = (req, res) => {
let posterPictures = [];
if (req.files.length > 0) {
posterPictures = req.files.map((file) => {
return { img: file.location };
});
}
const { name, id } = req.body;
const poster = new Poster({
name: name,
slug: slugify(name),
id,
posterPictures,
});
poster.save((error, poster) => {
if (error) return res.status(400).json(error);
if (poster) {
return res.status(201).json({ poster, files: req.files });
}
});
};

Reference S3 file upload name in new mongoDB object

I'm uploading images from a form to my S3 bucket. When the form is submitted, it creates a new mongoDB object. I'm using key: function (req, file, cb) {cb(null, Date.now().toString())},
to give the uploads unique names. My issue is, when I'm saving the new mongoDB object, I'm not sure how to reference the unique S3 key.
I admit this might be a dumb question, but I'm not very experienced with mongoDB/mongoose and this is my first time ever working with S3.
I would greatly appreciate any advice or recommendations
// productController.js
// if errors on form
else {
let product = new Product({
business: req.body.businessName,
productName: req.body.productName,
category: req.body.productCategory,
price: req.body.productPrice,
description: req.body.productDescription,
website: req.body.website,
imageURL: "https://mybucket.s3-us-west-2.amazonaws.com/" + '???'
});
product.save(function (err) {
if (err) {
console.log(err)
return next(err);
}
res.redirect("/list-product");
});
}
to upload my images im using this:
// uploadController.js
const aws = require("aws-sdk");
const multer = require("multer");
const multerS3 = require("multer-s3");
require("dotenv").config();
aws.config.update({
secretAccessKey: process.env.SECRETACCESSKEY,
accessKeyId: process.env.ACCESSKEYID,
region: "us-west-2",
});
const s3 = new aws.S3();
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
cb(null, true);
} else {
cb(new Error("Invalid format, only JPG and PNG"), false);
}
};
const upload = multer({
fileFilter: fileFilter,
storage: multerS3({
s3: s3,
bucket: "mybucket",
acl: "public-read",
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function (req, file, cb) {
console.log(file);
cb(null, Date.now().toString());
},
}),
});
module.exports = upload;
and my routes:
// routes.js
let upload = require('../controllers/uploadController')
const singleUpload = upload.single("image");
// post list product page
router.post('/list-product', singleUpload, listController.list__post)
I'm not a pro on this but i can think in one solution:
I see you handle the request first in the upload, so there you can save on the request the value of the Key, and not use directly the Date.now() on the name of the image when you create the file
So you should do:
req.imageName = new Date.now().toString()
And in the name change that creation of the name with req.imageName
Then in the next controller handling the request, you have access to the name on the object "req" provided to the function.
NOTE: new Date.now() IS NOT a good unique key if you have concurrent request for different clients, you have a high probability of getting the same number if you have too much requests at the same time.
NOTE 2: I can think on uuid for better unique keys, i don't know too much about others librarys that can solve that problem.

How to rename a file with multer before uploading it to s3 bucket?

I want to rename every file before it gets uploaded to aws s3 bucket using multer, node.js, and express.js
This is how my current implementation looks
Font End
const file = e.target.files[0];
const formData = new FormData();
formData.append('file', file);
const { data } = await axios.post('api/aws/upload-image', formData);
Backend
var storage = multer.memoryStorage();
const upload = multer({ storage }).single('file');
const s3Client = new aws.S3({
accessKeyId: config.aws.accessKey,
secretAccessKey: config.aws.secretKey,
region: config.aws.region
});
router.post('/upload-image', async (req, res) => {
upload(req, res, function(err) {
if (err instanceof multer.MulterError || err)
return res.status(500).json(err);
const uploadParams = {
Bucket: config.aws.bucket,
Key: req.file.originalname,
ACL: 'public-read',
Body: req.file.buffer
};
s3Client.upload(uploadParams, (err, data) => {
if (err) res.status(500).json({ error: 'Error -> ' + err });
return res.status(200).send(data.Location);
});
});
});
The code above works as it should. But I'm trying to rename the file before it gets uploaded.
I thought of doing this:
var storage = multer.diskStorage({
filename: function (req, file, cb) {
cb(null, Date.now() + '-' +file.originalname )
}
})
But that returns a file element, not a blob element, and therefore doesn't upload to s3 bucket.
How can I make it that when file gets sent over to node.js, I first change the file name, the I upload the file?
The upload method uses PutObjectRequest, PutObjectRequest constructor the key parameter is actually the name of the uploaded file.
Just change Key value to your new name.
const uploadParams = {
Bucket: config.aws.bucket,
Key: "NEW_NAME_WHAT_YOU_WANT", // req.file.originalname,
ACL: 'public-read',
Body: req.file.buffer
};

upload image to s3 from node js

I'm trying to upload a file to s3 but it not happening what I expected.
I create a file-helper.js in middleware, that is looks like below
const aws = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');
aws.config.update({
accessKeyID:'XXXXXXXXXXXXXX',
SecretAccessKey:'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
region:'ap-south-1'
});
const s3 = new aws.S3();
const fileFilter = (req, file, cb) => {
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
cb(null, true);
} else {
cb(new Error('Invalid file type, only JPEG and PNG is allowed!'), false);
}
}
const upload = multer({
fileFilter,
storage: multerS3({
s3,
bucket: 'demo.moveies.com',
acl: 'public-read',
metadata: function (req, file, cb) {
cb(null, {fieldName: file.fieldname});
},
key: function (req, file, cb) {
cb(null, Date.now().toString())
}
})
})
module.exports = upload;
and my controller file like below
const upload = require('../middleware/file-helper');
const imageUpload = upload.single('image');
exports.fileUpload = async(req,res)=>{
imageUpload(req, res, function(err, some) {
if (err) {
return res.status(422).send({errors: [{title: 'Image Upload Error', detail: err.message}] });
}
return res.json({'imageUrl': req.file.location});
});
}
when hit the API end point it is giving error
{
"errors": [
{
"title": "Image Upload Error",
"detail": "Missing credentials in config"
}
]
}
I'm not able to figure out where I went in wrong in my code. can one help me in this situation
There are typos in your config details. It should be accessKeyId not accessKeyID and secretAccessKey and not SecretAccessKey.
You have used the wrong key SecretAccessKey and accessKeyID, try changing it to secretAccessKey and accessKeyId.
aws.config.update({
accessKeyId:'XXXXXXXXXXXXXX',
secretAccessKey:'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
region:'ap-south-1'
});

Amazon S3 bucket file upload response is not available

I'm using multer ,aws-sdk and multer-s3 packages along with express.
when users edit profile user may change profile picture /avatar or not.
I've passed multer object
multer({storage:
multer.memoryStorage()}).single('profileHeroImageEdit')
if a file with current request then I will upload the file to s3 bucket but I am not getting any response from upload_replace where req.file.location will provide the url of S3 bucket (file's location).
And Inside upload_replace I can get the file I am trying to upload(req.file) but I want the location of uploaded file to S3 bucket .
What I'm missing ? Help will be appreciated
router.put("/:id",multer({ storage:
multer.memoryStorage()}).single('profileHeroImageEdit'),
middleware.checkProfileOwnership,function(req, res){
if(isFileExists(req)==false){
delete req.body.profileHeroImage
}
else{
console.log('file has')
var upload_replace = multer({
limits:{
fileSize:MAX_FILE_SIZE,
files:1
},
storage: multerS3({
s3: photoBucket,
bucket: BucketName,
acl: 'public-read',
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function (req, file, cb) {
cb(null,Date.now().toString())
}
})
}).single('profileHeroImageEdit')
upload_replace(req, res, function (err,log) {
console.log('request log')
console.log(req.file.location)
console.log()
});
}
Profile.findByIdAndUpdate(req.params.id, req.body.profile, function(err, updatedProfile){
if (err){
res.redirect("/profiles");
} else {
res.redirect("/profiles/" + req.params.id);
}
});
});
function isFileExists(request){
if(request.file)
{
return true
}
else{
return false
}
}
I have whole code using multer and aws-sdk
include this files and npm install all
//aws s3 packages
const aws = require("aws-sdk");
const multerS3 = require("multer-s3");
const multer = require("multer");
const path = require("path");
then
//profile image upload start
const s3 = new aws.S3({
accessKeyId: "***",
secretAccessKey: "***",
Bucket: "***"
});
//Singe profile image upload
const profileImgUpload = multer({
storage: multerS3({
s3: s3,
bucket: "***",
acl: "public-read",
key: function(req, file, cb) {
cb(
null,
path.basename(file.originalname, path.extname(file.originalname)) +
"-" +
Date.now() +
path.extname(file.originalname)
);
}
}),
limits: { fileSize: 2000000 }, // In bytes: 2000000 bytes = 2 MB
fileFilter: function(req, file, cb) {
checkFileType(file, cb);
}
}).single("profileImage");
// getExtension of file by this function
function getExtension(filename) {
var parts = filename.split(".");
return parts[parts.length - 1];
}
//checkfile type of input function
function checkFileType(file, cb) {
const ext = getExtension(file.originalname);
switch (ext.toLowerCase()) {
case "jpeg":
case "jpg":
case "png":
case "gif":
return cb(null, true);
}
cb("Error: Images Only!");
}
router.post(
"/image",
passport.authenticate("jwt", { session: false }),
(req, res) => {
profileImgUpload(req, res, error => {
if (error) {
res.json({ error: error });
} else {
//here we can get req.body
const userDp = {};
//end of getting values
// If File not found then dont store anything
if (req.file !== undefined) userDp.dpUrl = req.file.location;
// Save the file name into database into profile model
User.findOne({ email: req.user.email }).then(user => {
if (user) {
// Update
User.findOneAndUpdate(
{ email: req.user.email },
{ $set: userDp },
{ new: true }
).then(user => res.json(user));
} else {
res.json({ msg: "not able not update data" });
}
});
}
});
}
);
3.need to send data from react frontend by using
const data = new Formdata();
data.append()
and by including headers also
Add a listener for when the OutgoingMessage to upload the photo to S3 is completed. The on-finished library is handy for this.
const onFinished = require('on-finished');
const print = process._rawDebug;
uploadReplace(req, null, function (err) {
onFinished(req, function () {
if (err) print(err);
print(req.file.location);
// Other things can be done here
})
});

Resources