File upload in node.js using multer - node.js

I have created a multer function that uploads a file of following type "jpeg, png, jpg and pdf" in the documents field for uploading documents like PAN, aadhar card etc.(both routes are separate)
Now using that same multer function I want to give permission to user to upload their profile picture but that should be only in image format. It should not accept pdf or txt or any other file extension.
How can I do that?
The multer function:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "./src/uploads/");
},
filename: function (req, file, cb) {
cb(
null,
file.fieldname + "-" + Date.now() + path.extname(file.originalname)
);
},
});
var upload = multer({
storage: storage,
fileFilter: function (req, file, cb) {
var filetypes = /jpeg|jpg|png|pdf/;
var mimetype = filetypes.test(file.mimetype);
var extname = filetypes.test(path.extname(file.originalname).toLowerCase());
if (mimetype && extname) {
return cb(null, true);
}
cb("Please upload valid file");
},
});
both uploading documents fields are different. For uploading documents the field is documents and for display picture it is profile_picture.
The uploadDocuments service:
exports.uploadDocuments = async (_id, file) => {
let document = await User.findByIdAndUpdate(
{ _id },
{
$set: { registration_process_value: 99 },
$push: { documents: { $each: file } },
},
{ new: true }
);
return document;
};

Related

Save file's binary data in MongoDB

I have working file uploading function in multer. It was saving the file data as is. Now I would like to store the file data in the binary form. How can I do that?
The multer function:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "src/uploads/");
},
filename: function (req, file, cb) {
cb(
null,
file.fieldname + "-" + Date.now() + path.extname(file.originalname)
);
},
});
var upload = multer({
storage: storage,
fileFilter: function (req, file, cb) {
var filetypes = /jpeg|jpg|png|pdf/;
var mimetype = filetypes.test(file.mimetype);
var extname = filetypes.test(path.extname(file.originalname).toLowerCase());
if (mimetype && extname) {
return cb(null, true);
}
cb("Please upload valid file");
},
});
The upload document service file:
exports.uploadDocuments = async (_id, file) => {
let path = file.profile_picture.map((item) => {
return item.path;
});
if (file.profile_picture) {
document = await User.findByIdAndUpdate(
{ _id },
{ $set: { profile_picture: path.toString() } },
{ new: true }
);
}
if (file.documents) {
document = await User.findByIdAndUpdate(
{ _id },
{
$set: { registration_process_value: 99 },
$push: { documents: { $each: file.documents } },
},
{ new: true }
);
}
return document;
};
The controller file function:
let uploadDocument = await userService.uploadDocuments(
check_user._id,
req.files
);
In the schema type I have changed the field to Buffer from String.
It is storing profile_picture data into binary form but it is not saving documents data into binary form as I am sending two documents. and it is giving me the casing error:
"path": "documents",
"reason": null,
"name": "CastError",
"message": "Cast to Buffer failed for value \...\" (type Object) at path \"documents\""
}

Upload .txt file in Node.js

I want to upload files for multiple fields (like html_template and preview_image fields) but Node.js is not accepting it and more over it does not logs any error in the console but in the postman it responds with internal server error.
The multer function:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "src/uploads/");
},
filename: function (req, file, cb) {
cb(
null,
file.fieldname + "-" + Date.now() + path.extname(file.originalname)
);
},
});
var upload = multer({
storage: storage,
fileFilter: function (req, file, cb) {
var filetypes = /jpeg|jpg|png|pdf|txt/;
var mimetype = filetypes.test(file.mimetype);
var extname = filetypes.test(path.extname(file.originalname).toLowerCase());
if (mimetype && extname) {
return cb(null, true);
}
cb("Please upload valid file");
},
});
The create template route:
router
.route("/create")
.post(
upload.fields([{ name: "html_template" }, { name: "preview_image" }]),
Template.createTemplate
);
If I remove the field { name: "html_template" } from the route then it works fine but it does not work with this field html_template
The templateCreator controller function:
exports.createTemplate = catchAsync(async (req, res) => {
try {
console.log(req.files);
const template = await templateService.createTemplate(req);
return res.succeed(template, "Template created successfully");
} catch (error) {
console.trace(error);
return res.failed(500, "Internal Server Error", error);
}
});
The service function:
exports.createTemplate = async (req) => {
const name = req.body.name;
const html_template = req.files.html_template;
const preview_image = req.files.preview_image;
const imagePath = preview_image.map((image) => image.path);
const template = new Template({
name,
html_template,
preview_image: imagePath.toString(),
});
await template.save();
return template;
};
I have tried upload.fields and upload.any but it just doesn't work. I am unable to figure out why.
If I send the jpg, jpeg, png file then it accepts it and saves it to the database but not the .txt file. Why is that?
Because the MIME type for plain text files is text/plain. It doesn't contain the string txt.
It'd be better to list the full MIME types that you accept instead of doing a substring match.
The extension shouldn't really matter at that point, but if you want it to matter, you have to check it separately.
You forget to define a list of MIME types to test your file mime type with it.
I edit your fileFilter function like below and it works.
fileFilter: function (req, file, cb) {
var filetypes = /jpeg|jpg|png|pdf|txt/;
var mimeTypes = /text\/plain|image\/jpeg|image\/jpg|image\/png|application\/pdf/;
var mimetype = mimeTypes.test(file.mimetype);
var extname = filetypes.test(path.extname(file.originalname).toLowerCase());
if (mimetype && extname) {
return cb(null, true);
}
cb("Please upload valid file");
}

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 screenshots to google cloud storage bucket with Fluent-ffmpeg

I am currently using multer to upload videos to my google storage bucket, and fluent-ffmpeg to capture thumbnails of the videos. Videos are being uploaded into the buckets correctly, but not the thumbnails from ffmpeg. How can I change the location of the thumbnails to my google storage bucket?
Back-End Video upload
require ('dotenv').config()
const express = require('express');
const router = express.Router();
const multer = require("multer");
var ffmpeg = require('fluent-ffmpeg');
const multerGoogleStorage = require('multer-google-storage');
const { Video } = require("../models/Video");
const {User} = require("../models/User")
const { auth } = require("../middleware/auth");
var storage = multer({
destination: function (req, file, cb) {
cb(null, 'videos/')
},
filename: function (req, file, cb) {
cb(null, `${Date.now()}_${file.originalname}`)
},
fileFilter: (req, file, cb) => {
const ext = path.extname(file.originalname)
if (ext !== '.mp4' || ext !== '.mov' || ext !== '.m3u' || ext !== '.flv' || ext !== '.avi' || ext !== '.mkv') {
return cb(res.status(400).end('Error only videos can be uploaded'), false);
}
cb(null,true)
}
})
// Set location to google storage bucket
var upload = multer({ storage: multerGoogleStorage.storageEngine() }).single("file")
router.post("/uploadfiles", (req, res) => {
upload(req, res, err => {
if (err) {
return res.json({sucess: false, err})
}
return res.json({ success: true, filePath: res.req.file.path, fileName: res.req.file.filename})
})
});
Back-end thumbnail upload
router.post("/thumbnail", (req, res) => {
let thumbsFilePath = "";
let fileDuration = "";
ffmpeg.ffprobe(req.body.filePath, function (err, metadata) {
console.dir(metadata);
console.log(metadata.format.duration);
fileDuration = metadata.format.duration;
})
ffmpeg(req.body.filePath)
.on('filenames', function (filenames) {
console.log('Will generate ' + filenames.join(', '))
thumbsFilePath = "thumbnails/" + filenames[0];
})
.on('end', function () {
console.log('Screenshots taken');
return res.json({ success: true, thumbsFilePath: thumbsFilePath, fileDuration: fileDuration })
})
//Can this be uploaded to google storage?
.screenshots({
// Will take 3 screenshots
count: 3,
folder: '/thumbnails/',
size: '320x240',
//Names file w/o extension
filename:'thumbnail-%b.png'
});
});
Front-end video upload
const onDrop = (files) => {
let formData = new FormData();
const config = {
header: {'content-type': 'multipart/form-data'}
}
console.log(files)
formData.append("file", files[0])
axios.post('/api/video/uploadfiles', formData, config)
.then(response => {
if (response.data.success) {
let variable = {
filePath: response.data.filePath,
fileName: response.data.fileName
}
setFilePath(response.data.filePath)
//Thumbnail
axios.post('/api/video/thumbnail', variable)
.then(response => {
if (response.data.success) {
setDuration(response.data.fileDuration)
setThumbnail(response.data.thumbsFilePath)
} else {
alert("Failed to generate a thumbnail");
}
})
} else {
alert('Failed to save video to the server')
}
})
}
Here you can find the sample code of an application web page prompting the user to supply a file to be stored in Cloud Storage. The code is configuring bucket using environment variables and creates a new blob in the bucket to upload the file data.
I hope this information helps.
You may have to just move them after they're generated with ffmpeg.
For example, I'm writing them to a temp directory outputted by ffmpeg, and then moving after to a Cloud Storage bucket in my cloud function:
const uploadResult = await bucket.upload(targetTempFilePath, {
destination: targetStorageFilePath,
gzip: true
});
Not sure which environment you're using (flex, cloud run, etc) but these were the instructions I was referencing, and are generally the same steps you'll want to follow: https://firebase.google.com/docs/storage/extend-with-functions

uploading images in mlab

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

Resources