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.
Related
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 });
}
});
};
Everything I've looked at regarding s3 and multer s3 looks something like this
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'bucket',
acl: 'public-read',
cacheControl: 'max-age=31536000',
contentType: multerS3.AUTO_CONTENT_TYPE,
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function (req, file, cb) {
const key = `pictures/${Date.now().toString()}`;
cb(null, key);
},
}),
});
and then the controller action looks like this
app.post('/profileImage', upload.single('profileImage'), async function (req, res) {
const { _id } = req.params;
const user = await User.findOneAndUpdate({ _id }, { image: req.file.location }, { new: true });
res.json(user);
});
My question is can I send back form data and other data in one call and upload my images to s3. I want to be able to structure my data something like this
const formData = new FormData();
formData.append("picture", this.file);
const data = {
title: this.toDoTitle,
details: this.toDoDetails,
formData,
};
axios
.post("/profileImage", data)
.then(() => {
})
.catch(err => {
console.log(err);
});
and then in my controller be able to save that form data image in s3 but also interact with the other data. I've been able to send back data in the req.params but I want to know if you can send it back in the body as well with the image and be able to upload image and get other data
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.
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
};
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'
});