uploading images in mlab - node.js

currently i am storing images which are uploaded by node rest server in local directory "/uploads" . this is continuously increasing my repo size .
to avoid this , i want to store image files in mongoDB atlas or mlab just like service.
const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const multer = require('multer');
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, './uploads/');
},
filename: function(req, file, cb) {
cb(null, new Date().toISOString() + file.originalname);
}
});
const fileFilter = (req, file, cb) => {
// reject a file
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png')
{
cb(null, true);
} else {
cb(null, false);
}
};
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5
},
fileFilter: fileFilter
});
Please do help me in this. Thanks in advance.

You can achieve this by using a mongoose Schema and the fs core module to encode the image and unlink the file from /uploads.
I would start by creating a Mongoose Schema to set the model of all information you want to store pertaining to your uploaded file.
I'm going to use base64 encoding for this example.
uploadModel.js
const mongoose = require('mongoose');
const fs = require('fs');
const Schema = mongoose.Schema;
mongoose.set('useCreateIndex', true);
let uploadSchema = new Schema({
name: {
type: String,
},
mimetype: {
type: String,
},
size: {
type: Number,
},
base64: {
type: String,
}
})
module.exports = mongoose.model('upload',uploadSchema);
After setting up a model create a function to base64 encode and module.exports that as well.
To encode your file, use fs.readFileSync(path_to_file, encode_type). After the file has been encoded and saved in a variable you can use fs.unlink(path_to_file) to delete the file out of your /uploads folder.
uploadModel.js
module.exports.base64_encode = function(file) {
return new Promise((resolve, reject) => {
if(file == undefined){
reject('no file found');
} else {
let encodedData = fs.readFileSync(file, 'base64');
fs.unlink(file);
resolve(encodedData.toString('base64'));
}
})
}
Now inside your route file require your model.
route.js
const Upload = require('path_to_uploadModel');
router.post('/path_to_upload', upload.single('form_name_of_file'), (req, res) => {
let img = req.file;
let model = new Upload({
name: img.originalname,
size: img.size,
mimetype: img.mimetype,
})
Upload.base64_encode(img.path)
.then((base64) => {
model['base64'] = base64;
model.save((err)=> {
if(err) throw err;
});
}
})
Hope this helps

Related

Expressjs multer middleware save file in dynamic destination

I am using multer with expressjs and I am trying to change the DIR to save images depending on the request to a dynamic destination, my code is working fine but always save images inside the post directory, I'm using this middleware with multi requests.
1- How can I make the directory dynamic! example: to save to ./public/products if req product & save to ./public/posts if req post
2- How to make sure that the file is uploaded to the directory with no errors in the controller! then save the path to the database!
3- Is this the best practice to use multer ! in middleware level!
multer middleware fileUpload.js
const multer = require("multer");
const mkdirp = require("mkdirp");
const fs = require("fs");
const getDirImage = () => {
// const DIR = './public/uploads/products';
return `./public/posts`;
};
let storage = multer.diskStorage({
destination: (req, file, cb) => {
console.log(req.params,'&&&&&&&&&&&&',file);
let DIR = getDirImage();
if (!fs.existsSync(DIR)) {
fs.mkdirSync(DIR, { recursive: true });
}
cb(null, DIR);
},
filename: (req, file, cb) => {
const fileName = "overDress" + Date.now() + "" +
file.originalname.toLowerCase().split(' ').join('-');
cb(null, fileName)
},
});
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 10 //upto 2 megabytes per file.
},
fileFilter: (req, file, cb) => {
if (file.mimetype == "image/png" || file.mimetype == "image/jpg" ||
file.mimetype == "image/jpeg") {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('File types allowed .jpeg, .jpg and .png!'));
}
}
});
module.exports = upload;
product.js route
const controller = require('../controllers/product.controller');
import { Router } from 'express'; //import from esm
import upload from '../middleware/fileUpload'
const router = Router();
router
.get('/', controller.list)
.post('/', upload.single('image'), controller.create)
.get('/:id', controller.getOne)
export default router;
and my create controller:
exports.create = async (req, res, next) => {
const { name, title, description,subtitle} = req.body;
if (!name || !title) {
return res.status(400).send({
message: 'Please provide a title and a name to create a product!',
});
}
try {
if (req.file) {
req.body.image = req.file.destination + '/' + req.file.filename;
}
const PRODUCT_MODEL = {
name: req.body.name,
title: req.body.title,
description: req.body.description,
image: req.body.image,
};
try {
const product = await Product.create(PRODUCT_MODEL);
console.log('product crerated');
return res.status(201).json(product);
} catch (error) {
console.log(error);
return res.status(500).send({
message: 'Something went wrong: ' + error,
});
}
} catch (error) {
return res.status(500).json(error);
}
};

How to use Multer middleware to upload array of images

Im trying to use Multer to upload an array of images. At the client side i have a FormData called pictures.
pictures array, from react-native-image-picker:
const [pictures, setPictures] = useState([]);
const imagePickerCallBack = data => {
const picturesData = [...pictures];
const index = picturesData.length;
const image = {
image: data.uri,
fileName: data.fileName,
type: data.type,
index: index,
};
picturesData.push(image);
setPictures(picturesData);
setLoad(false);
};
Step 1 - Create formData with all images:
const data = new FormData();
pictures.forEach(pic => {
data.append('pictures', {
fileName: pic.fileName,
uri: pic.image,
type: pic.type,
});
});
const headers = {
'Content-Type': 'multipart/form-data',
'x-access-token': token,
};
const diaryUpdatePost = await post(`diary/uploadPictures/${diary}`, body, {
headers,
});
Step 2 - Get the request at server side. Im setting up multer and routers:
const router = express.Router();
const multer = require('multer');
const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, 'uploads/');
},
filename(req, file, cb) {
cb(null, `${file.fieldname}-${Date.now()}`);
},
});
const upload = multer({ storage, limits: { fieldSize: 25 * 1024 * 1024 } });
// Multer with the same FormData (client)
router.post('/uploadPictures/:name', upload.array('pictures'), diaryController.uploadDiaryPictures);
And finally my diaryController, where i need to get all files:
exports.uploadDiaryPictures = async (req, res) => {
// Logging []. I cant access files from here
console.log(`files ${req.files}...`);
};
I already tried to use express-fileupload, but req.files return undefined. Some ideia to help? Thx.
You need to give a count of files you expect to upload:
upload.array('pictures', <number_of_pictures>)
Or if it is allowed to be any number:
upload.any('pictures')
You should also add the file itself to your form data
data.append('pictures', {
name: pic.fileName,
file: pic.image,
type: pic.type,
});
None of the answers here helped. The solution for me was to iteratively append EACH file object from the files array to the same field name given in Multer, instead of appending the files array itself to the field name given in Multer.
So from this:
export const addFiles= createAsyncThunk(
"addFiles",
async (payload: any, thunkApi) => {
const formData = new FormData();
// Here was the problem -- I was appending the array itself
// to the "files" field
formData.append("files", payload.files);
formData.append("data", JSON.stringify(payload?.data || {}));
const response = await axios.post('/user/products/files', formData);
if(response){
return response;
}
return thunkApi.rejectWithValue("");
}
);
I did this:
export const addFiles= createAsyncThunk(
"addFiles",
async (payload: any, thunkApi) => {
const formData = new FormData();
// The following loop was the solution
for (const file of payload.files) {
formData.append("files", file);
}
formData.append("data", JSON.stringify(payload?.data || {}));
const response = await axios.post('/user/products/files', formData);
if(response){
return response;
}
return thunkApi.rejectWithValue("");
}
);
This was my Multer configuration:
multer({ dest: "/uploads" }).array("files")
The files posted to my endpoint were then available to me at:
req.files
PS: Although the accepted answer kind of did that, he did not mention that you cannot append the entire array at once, which was the main problem for me.

how to store files path in a user record?

I am trying to upload files to store in a user database but its not getting stored.
This is my schema.
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
file: {
type: String,
},
});
<--This is my route.I am using multer to handle multiform data -->
const storage = multer.diskStorage({
destination: "public/upload",
filename: function (req, file, cb) {
cb(
null,
file.fieldname + "-" + Date.now() + path.extname(file.originalname)
);
},
});
//Initialize Upload
const upload = multer({
storage: storage,
fileFilter: function (req, file, cb) {
checkFileType(file, cb);
},
}).single("file");
//Check File Type
function checkFileType(file, cb) {
// Allowed extensions
const fileTypes = /pdf/;
//Check extensions
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:Pdf only");
}
}
router.post("/user/:id/upload", async (req, res) => {
const _id = req.params.id;
try {
const user = await User.findById(_id);
if (!user) {
res.status(404).json({ msg: "No User Found" });
}
user.file = req.file;
upload(req, res, (err) => {
if (err) {
console.log(err);
} else {
console.log(req.file);
}
I am only able to store the files in the public folder.Is there a way to store files for a particular user in db so that I can know this user uploaded this file?
Multer doesn't save files directly in your database. It only stores them in your file disk system.
So instead of doing user.file = req.file you should save the destination + filename you created with multer in the database.
You're saving the filename as file.fieldname + "-" + Date.now() + path.extname(file.originalname) in "public/images" but in your database you're just saving req.file which is completely different.
First of all let me tell you that it's not the best practice to store a file itself in a db. The best practice is to store filepath to user's object so that you can track that which file uploaded by which user.
Let say you have a route like
router.post("/", log, upload.fields([{ name: 'userFile' }]), fileUpload);
and in your fileUpload function just check if files is present, and if file is present then save file's url in the db like this
function fileUpload(req,res){
const user;
if (req.files['userFile']) {
user.filePath = `public/upload/${req.files['userFile'][0].filename}`;
const userObj = new User(user);
userObj.save();
}
}
I just gave a rough implementation to just give you an idea that how can you achieve that.

upload multiple files using multerS3 and NodeJs

I've been trying for days but can't seem to figure out where I'm going wrong. My code works for a single file upload in a local folder but can't seem to upload multiple files even if I use upload.array() and req.files in multer and controller function respectively. Whereas when I try to upload a single/multiple files in my s3 bucket nothing gets uploaded at all. Below I'll also provide my model structure in case you need to see it.
::UPDATE::
The files are getting uploaded in s3 bucket but the database can't catch the file path. imagePath is coming blank inside database
route.js:
const ProductController = require('./../controllers/productController');
const appConfig = require("./../config/appConfig");
const multer = require('multer');
const aws = require('aws-sdk');
const multerS3 = require('multer-s3');
const path = require('path');
const s3 = new aws.S3({ accessKeyId: "***", secretAccessKey: "***" });
var upload = multer({
storage: multerS3({
s3: s3,
bucket: 'mean-ecom',
metadata: function (req, file, cb) {
cb(null, {fieldName: file.originalname + path.extname(file.fieldname)});
},
key: function (req, file, cb) {
cb(null, Date.now().toString())}})});
let setRouter = (app) => {
let baseUrl = appConfig.apiVersion;
app.post(baseUrl+'/post-product', upload.single('imagePath'), ProductController.postProduct)}
module.exports = {
setRouter: setRouter}
ProductController.postProduct:
const ProductModel = mongoose.model('Product')
let postProduct = (req, res) => {
let newProduct = new ProductModel({
imagePath: req.file && req.file.path})
newProduct.save((err, result) => {
if (err) { console.log('Error at saving new Product :: ProductController', err);
res.send(err);
} else {
console.log('Successfully saved new Product'); res.send(result) }
})}
productModel.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let productSchema = new Schema(
{
imagePath: {
type: String,
default: '' }})
mongoose.model('Product', productSchema);
The first thing you need to do is change the model's imagePath property from a string to an array of strings, so you can store multiple images
const Schema = mongoose.Schema;
let productSchema = new Schema(
{
imagePath: {type: Array}
})
mongoose.model('Product', productSchema)
Then, in the router, you should use upload.any() (upload.array() should work the same way).
Finaly, if you want to store the files locations in the model array, you loop throw the files updating the model.
let postProduct = (req, res) => {
// We create a temporal array with the files locations
let files = [];
// We loop throw the req.files array and store their locations in the temporal files array
for (let i = 0; i < req.files.length; i++) {
let file = req.files[i].location
files.push(file)
}
let newProduct = new ProductModel({
imagePath: files}) //We create the model with the temp array
newProduct.save((err, result) => {
if (err) {
console.log('Error at saving new Product :: ProductController', err);
res.send(err);
}
else{
console.log('Successfully saved new Product');
res.send(result)
}
})

How to resize Image on upload using `Multer and Sharp` on Node and Express

I tried to resize my image when it's being uploaded, but I am not succeeding. I am using Node, Expressjs, Mongoose, Multer and Sharp. How do I resize it on upload with Sharp.
Here is my create routes
router.post('/create', upload.single('cover'), async (req, res, next) => {
const fileName = req.file != null ? req.file.filename : null
let witdth = 100;
let height = 100;
sharp(req.file)
.resize(witdth, height).toFile(req.file.path)
const event = new Event({
startingDate: req.body.startingDate,
closingDate: req.body.closingDate,
title: req.body.title,
description: req.body.description,
eventImage: fileName
})
try {
const events = await event.save()
res.redirect("/events")
} catch {
if (event.eventImage != null) {
removeeventImage(event.eventImage)
}
res.render("events/new")
}
});
and my uploader path
const uploadPath = path.join('public', Event.eventImageBasePath)
const imageMineTypes = ['image/jpeg', 'image/png', 'image/gif']
const upload = multer({
dest: uploadPath,
fileFilter: (req, file, callback) => {
callback(null, imageMineTypes.includes(file.mimetype) )
}
})
and this is the error that it's giving
(node:7447) UnhandledPromiseRejectionWarning: Error: Input file is
missing
What am I doing wrong here?
If you specify dest option in multer() it will save the file to that directory and you will have to pass that directory to sharp() like this
sharp(req.file.path)
Otherwise you can drop the dest option and pass the buffer to sharp()
sharp(req.file.buffer)
...
multer({
fileFilter: (req, file, callback) => {
callback(null, imageMineTypes.includes(file.mimetype) )
}

Resources