I'm trying to do the following in Node.js using express router, Multer-S3, Multer, AWS and Mongodb.
I want to:
1: Check if filetype is image, price is number etc (some kind of quality check)
2: If above true, upload image to S3 to get Image url
3: If Image Url was generated, upload to Mongodb, including the generated image url..
Trying with below code but can only get one of these to work at same time..
const express = require("express");
const router = express.Router();
const shopController = require("../controllers/shop");
router.post(
"/shop/create/:shopId",
shopController.creatingShop,
shopController.createShopItem
);
const ShopItem = require("../models/shopitem"); //Mongoose Schema
const multer = require("multer");
const fileview = multer().single("file1"); //Trying to use this to view file before uploading to S3
const uploader = require("../services/file-upload");
const singleUpload = uploader.single("file1"); //Using this to upload to S3
exports.createShopItem = (req, res, next) => {
fileview(req, res, function (err) {
const file = req.file;
const title = req.body.title;
const price = req.body.price;
const description = req.body.description;
const location = req.body.location;
const user = "OrreSnorre";
if (
file.mimetype != "image/jpeg" &&
file.mimetype != "image/jpg" &&
file.mimetype != "image/png"
) {
return next(new Error("invalid file type"));
}
if (file.size > 2500000) {
return next(new Error("Your image is to big. Maximum 2.5mb"));
}
next();
console.log(
"Here I want to add upload text to mongoDb... including URL from S3 after it is generated"
);
});
exports.creatingShop = (req, res, next) => {
singleUpload(req, res, function (err) {
console.log(req.file);
// res.json({ "image-url": req.file.location });
});
next();
};
Anyone got ideas? Or examples that work?
Best regards,
Oscar
There are 2 ways to do this, either you can use only multer or multer-s3.
For simplicity, I will show you the way using only multer.
Flow of processing as follow:
Multer process and save to local
You read from local, and upload to s3 using s3 SDK (You should explore how to remove the file after upload as well, but I wont clutter you with this logic here)
If upload is successful, you retrieve the URL and pass it to your MongoDB.
// Make "temp" directory as multer.diskStorage wont create folder
fs.mkdir('./temp', { recursive: true }, (err) => {
if (err) throw err;
});
const PORT = parseInt(process.argv[2]) || parseInt(process.env.PORT) || 3000;
// Multer
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './temp');
},
filename: function (req, file, cb) {
let extArray = file.mimetype.split('/');
let extension = extArray[extArray.length - 1];
cb(null, new Date().getTime() + '.' + extension);
},
});
const upload = multer({ storage: storage });
const endpoint = new AWS.Endpoint(AWS_S3_HOSTNAME);
const s3 = new AWS.S3({
endpoint,
accessKeyId: AWS_S3_ACCESSKEY_ID,
secretAccessKey: AWS_S3_SECRET_ACCESSKEY,
});
// Get the uploaded file in local here
const readFile = (path) =>
new Promise((resolve, reject) =>
fs.readFile(path, (err, buff) => {
if (null != err) reject(err);
else resolve(buff);
})
// Upload to AWS S3 here
const putObject = (file, buff, s3) =>
new Promise((resolve, reject) => {
const params = {
Bucket: AWS_S3_BUCKET_NAME,
Key: file.filename,
Body: buff,
ACL: 'public-read',
ContentType: file.mimetype,
ContentLength: file.size,
};
s3.putObject(params, (err, result) => {
if (null != err) reject(err);
else resolve(file.filename);
});
});
);
const mongoClient = new MongoClient(MONGO_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
app.post('/api/post', upload.single('imageFile'), async (req, res) => {
readFile(req.file.path)
.then((buff) =>
// Insert Image to S3 upon succesful read
putObject(req.file, buff, s3)
)
.then((results) => {
// build url of the resource upon successful insertion
const resourceURL = `https://${AWS_S3_BUCKET_NAME}.${AWS_S3_HOSTNAME}/${results}`;
const doc = {
comments,
title,
ts: new Date(),
image: resourceURL, // Your URL reference to image here
};
// Insert to your mongoDB
mongoClient
.db(MONGO_DB)
.collection(MONGO_COLLECTION)
.insertOne(doc)
.then((results) => {
// delete the temp file when no error from MONGO & AWS S3
fs.unlink(req.file.path, () => {});
// return the inserted object
res.status(200).json(results.ops[0]);
})
.catch((error) => {
console.error('Mongo insert error: ', error);
res.status(500);
res.json({ error });
});
})
.catch((error) => {
console.error('insert error: ', error);
res.status(500);
res.json({ error });
});
}
Related
I am trying to upload an image from my front end to the backend but it it doesn't send the image in the request
It says that the formdata is empty and it says that there's no image found, where is the problem and how can I fix this error?
Here is the code from the Frontend made in react:
const [userInfo, setuserInfo] = useState({
file:[],
filepreview:null,
});
const handleInputChange = (event) => {
setuserInfo({
...userInfo,
file:event.target.files[0],
filepreview:URL.createObjectURL(event.target.files[0]),
});
}
const [isSucces, setSuccess] = useState(null);
const submit = async () =>{
const formdata = new FormData();
formdata.append('avatar', userInfo.file);
console.log(formdata)
Axios.post("http://localhost:4000/imageupload", formdata,{
headers: { "Content-Type": "multipart/form-data" }
})
.then(res => { // then print response status
console.warn(res);
if(res.data.success === 1){
setSuccess("Image upload successfully");
}
})
}
The code of the Backend made in NodeJS:
const storage = multer.diskStorage({
destination: path.join(__dirname, './temp', 'uploads'),
filename: function (req, file, cb) {
// null as first argument means no error
cb(null, Date.now() + '-' + file.originalname )
}
})
app.post('/imageupload', async (req, res) => {
try {
// 'avatar' is the name of our file input field in the HTML form
let upload = multer({ storage: storage}).single('avatar');
upload(req, res, function(err) {
// req.file contains information of uploaded file
// req.body contains information of text fields
if (!req.file) {
return res.send('Please select an image to upload');
}
else if (err instanceof multer.MulterError) {
return res.send(err);
}
else if (err) {
return res.send(err);
}
const classifiedsadd = {
image: req.file.filename
};
res.send("ok")
});
}catch (err) {console.log(err)}
})
Edit:
Multer is essentially a nodejs router,i.e. a function that can be pipelined between your HTTP request and HTTP response.
I think that you should first make multer analyze your HTTP content and to actually populate the req.file before actually evaluate express parsers do their job.
const storage = multer.diskStorage({
destination: path.join(__dirname, './temp', 'uploads'),
filename: function (req, file, cb) {
// null as first argument means no error
cb(null, Date.now() + '-' + file.originalname )
}
})
let upload = multer({ storage: storage});
app.post('/imageupload', upload.single('avatar'), async (req, res) => {
try {
// 'avatar' is the name of our file input field in the HTML form
// req.file contains information of uploaded file
// req.body contains information of text fields
if (!req.file) {
return res.send('Please select an image to upload');
}
else if (err instanceof multer.MulterError) {
return res.send(err);
}
else if (err) {
return res.send(err);
}
const classifiedsadd = {
image: req.file.filename
};
res.send("ok")
}catch (err) {console.log(err)}
})
I am assuming that your upload code is working. Have you tried to read the HTTP request from your browser to see that the image has been correctly attached to the request?
Because probably the issue lies in the fact that you are not actually parsing the image.
const file = new File(userInfo.file, "avatar.png", {
type: 'image/png' // choose the appropriate
});
const formdata = new FormData();
formdata.append('avatar', file);
console.log(formdata)
I want to upload files from the form data in react which is being posted by axios like this.
const addNewProduct = () => {
const newProduct = {
name: name,
cost: cost,
size: size,
color: color,
material: material,
discount: discount,
description: description,
category: category
};
const nulls = Object.values(newProduct).filter(p => p === null);
if(nulls.length === 0 && images.imageFiles) {
let productFormData = new FormData();
productFormData.append('productInfo', JSON.stringify(newProduct));
productFormData.append('productImages', images.imageFiles);
const addUrl = "http://localhost:8080/cpnl/addproduct";
axios({
method: "POST",
url: addUrl,
data: productFormData,
headers: { "Content-Type": "multipart/form-data" }
})
.then((response) => {
console.log(response.data.msg);
})
.catch((response) => {
console.error(response);
});
}else {
Notiflix.Notify.Warning("Check your inputs!");
console.log(nulls);
console.log("product: \n" + JSON.stringify(newProduct));
}
};
then I want to upload images with multer to images folder. this is my code:
const storage = multer.diskStorage({
destination: "./public/images",
filename: (req, file, cb) => {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage,
limits:{fileSize: 1000000},
fileFilter: function(req, file, cb){
checkFileType(file, cb);
}
}).array("productImages", 5);
function checkFileType(file, cb) {
// Allowed ext
const filetypes = /jpeg|jpg|png/;
// Check ext
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
// Check mime
const mimetype = filetypes.test(file.mimetype);
if(mimetype && extname){
return cb(null,true);
} else {
cb('Error: Images Only!');
}
}
//receive form data from front-end and add new product to database
router.post('/addproduct', async (req, res) => {
upload(req, res, (err) => {
if(err) {
res.status(400).json({
msg: err
});
} else {
if(req.files == undefined) {
res.status(400).json({
msg: "Error: No file selected! please contact the developer."
});
} else {
data = req.body.productInfo;
res.status(200).json({
msg: "Files uploaded!"
});
console.log( "images: " + req.files);
console.log("data" + data);
}
}
});
});
first problem: I'm getting image files inside req.body.productImages and not inside req.files
second problem: when I send the request node js throws me this error:
TypeError: upload is not a function
why everything is messed up!?
Edit: I restarted the server and now I'm getting data but the files are not being uploaded. no error is shown.
UPDATE: second problem fixed
First Problem : You have used .array("productImages", 5); in your upload function. use .array("files", 5); to get the file in req.files.
Second Problem : I guess there is some typo error in your code upload(req, res, (err)... there is one extra bracket it should be only upload(req,res,err )...
I built a web app and want to resize my images to be smaller for better quality to my profile pictures. I am using "multer" for upload picture and sharp package for resizing.
For some reason i get this error:
"[0] [Error: D:\DevConnectors\public\resized\5f4f4e0bb295ba36042536bf.jpg: unable to open for write
[0] windows error: The storage control block address is invalid."
My code:
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, DIR)
},
filename: (req, file, cb) => {
var typeFile = file.originalname.split(".").pop()
const fileName = req.user.id + "." + typeFile
cb(null, fileName)
},
})
var upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (
file.mimetype == "image/png" ||
file.mimetype == "image/jpg" ||
file.mimetype == "image/jpeg"
) {
cb(null, true)
} else {
return cb(new Error("Only .png, .jpg and .jpeg format allowed!"), false)
}
},
})
const Profile = require("../../moduls/Profile")
const User = require("../../moduls/User")
//#route GET/api/profile/me
//#desc Get current users profile
//#access Private
router.post(
"/upload",
[auth, upload.single("imageProfile")],
async (req, res) => {
try {
console.log(req.file)
const { filename: image } = req.file
await sharp(req.file.path)
.resize(150)
.jpeg({ quality: 50 })
.toFile(path.resolve(req.file.destination, "resized", image))
fs.unlinkSync(req.file.path)
const url = req.protocol + "://" + req.get("host")
let user = await User.findById(req.user.id)
const profile = await Profile.findOne({ user: req.user.id })
//Update
if (user) {
user.avatar = url + "/public/" + req.file.filename
await user.save()
return res.json(profile)
}
} catch (err) {
console.log(err)
}
}
)
This happens at this line :
path.resolve(req.file.destination,'resized',image))
What am i doing wrong? i am using sharp docs.
Try this
Configuration of multer ( change it according to your needs)
import multer from "multer";
import sharpe from "sharp";
const upload = multer({ //multer configuration
//dest: "avatars", //so that buffer is available in route handler
limits: {
fileSize: 1000000,
},
fileFilter(req, file, cb) { // object method shorthand syntax
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) { //.match for using regex b/w (//)
return cb(new Error("Please upload a IMAGE"));
}
cb(undefined, true);
},
});
handle sharp in your route handle like this.
router.post(
"path",
upload.single("avatar"),
async (req, res) => {
const buffer = await sharpe(req.file.buffer)
.png()
.resize({
width: 300,
height: 300
})
.toBuffer();
req.user.avatar = buffer;
await req.user.save();
res.send();
},
(error, req, res, next) => {
//to tell express this how mutler/s error should be handled
res.status(400).send({
error: error.message,
});
}
);
I had the same problem, and in my case the issue was that the directory (in your case "resized") didn't exist yet. So either create it manually or programmatically like this:
const targetPath = path.resolve(req.file.destination, 'resized')
if (!existsSync(targetPath)) {
mkdirSync(targetPath);
}
await sharp(...)
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 sent a request from the client with an image link in the body, in the node server I use jimp (Jimp NPM) to resize the image which I was sent, then I need to save the image to mongodb with multer but I have some confuse with this step, does anyone have any idea ? thanks !
here is my code so far :
const multer = require('multer');
const GridFsStorage = require('multer-gridfs-storage');
const mongoose = require('mongoose');
exports.resizeImage = async function (req, res) {
let storageFS = new GridFsStorage({
db: app.get("mongodb"),
file: (req, file) => {
return new Promise((resolve, reject) => {
crypto.randomBytes(16, (err, buf) => {
if (err) {
return reject(err);
}
const filename = file.originalname;
const fileInfo = {
filename: filename,
bucketName: 'images'
};
resolve(fileInfo);
});
});
}
});
var upload = multer({ storage: storageFS }).single('image');
try {
Jimp.read(req.body.imagePath)
.then(image => {
image.resize(150, 150, function (err, img) {
// How to store img to mongodb with multer here ?
});
})
.catch(err => {
throw err
});
} catch (error) {
res.send(error);
}
}