How to use multer with express.Router()? - node.js

I want to use multer in my nodejs app to upload user profile pictures. My routes are managed by express router. I have checked a lot of tutorials but nothing matches my exact use case. I want to let the users upload their profile pictures to my API, but before the request reaches the upload function I want to perform some validations like password and API key checks.
here is my upload controller,
const multer = require("multer");
const path = require("path");
const dp_storage = multer.diskStorage({
destination: path.join(__dirname, "../user_uploads/images/dp"),
filename: function (req, file, cb) {
cb(
null,
file.fieldname + "-" + Date.now() + path.extname(file.originalname)
);
},
});
// Init dp Upload
const dp_upload = multer({
storage: dp_storage,
limits: { fileSize: 2000000 }, // 1 mb
fileFilter: function (req, file, cb) {
checkFileTypeForUserDP(file, cb);
},
}).single("dp");
function checkFileTypeForUserDP(file, cb) {
// Allowed ext
let filetypes = /jpeg|jpg|png|gif|webp/;
// Check ext
let extname = filetypes.test(path.extname(file.originalname).toLowerCase());
// Check mime
let mimetype = filetypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb("Error: jpeg, jpg, png, gif Images Only!");
}
}
exports.uploadDP = async (req, res) => {
try {
dp_upload(req, res, (err) => {
if (err) {
console.log(err);
} else {
if (req.file == undefined) {
res.status(404).json({
success: false,
msg: "File is undefined!",
file: `uploads/${req.file.filename}`,
});
} else {
res.status(200).json({
success: true,
msg: "File Uploaded!",
file: `uploads/${req.file.filename}`,
});
}
}
});
} catch (error) {console.log(error);}
};
The above code works fine if I use it directly without any API key validation or user authentication.
Here is my router,
const express = require("express");
const router = express.Router();
const { authenticateUser ,apiKeyCheck} = require("../server");
const { uploadDP } = require("../controllers/file");
//this route works
router.post(
"/upload/dp_without_authentication",
uploadDP
);
//this is not working
router.post(
"/upload/dp",
apiKeyCheck,
authenticateUser,
uploadDP
);
module.exports = router;
The "/upload/dp" route is failing because the apiKeyCheck and authenticateUser functions can not read the user credentials from req.body.
So, in order to fix that I have added the following lines to my main server file,
const multer = require("multer");
const upload = multer();
app.use(upload.array());
But now the uploadDP function is not even called, instead it returns the following error:
MulterError: Unexpected field
at wrappedFileFilter (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/multer/index.js:40:19)
at Busboy.<anonymous> (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/multer/lib/make-middleware.js:115:7)
at Busboy.emit (node:events:394:28)
at Busboy.emit (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/lib/main.js:38:33)
at PartStream.<anonymous> (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/lib/types/multipart.js:213:13)
at PartStream.emit (node:events:394:28)
at HeaderParser.<anonymous> (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/node_modules/dicer/lib/Dicer.js:51:16)
at HeaderParser.emit (node:events:394:28)
at HeaderParser._finish (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/node_modules/dicer/lib/HeaderParser.js:68:8)
at SBMH.<anonymous> (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/node_modules/dicer/lib/HeaderParser.js:40:12)
If I remove the file from postman request, it is able to call uploadDP function.
What am I doing wrong here?

As Multer official docs warns the use of multer as a global middleware;
WARNING: Make sure that you always handle the files that a user uploads. Never add multer as a global middleware since a malicious
user could upload files to a route that you didn't anticipate. Only
use this function on routes where you are handling the uploaded files.
Therefore I recommend exploring a safer way of handling files as answered
here

Related

Implement multiple middleware for authentication and Multer in NodeJS

I have a form sent from an Angular front-end to a nodeJS server.
This form can contain input file (not mandatory) and other text fields.
So i'm using FormData for the multipart encoding.
Here is the code from my Angular service :
const formDataGenerated = generateFormDataFromForm(form);
formDataGenerated.append('id', this.tokenStorage.getUser().id);
if (file !== null) {
formDataGenerated.append('file', file);
formDataGenerated.set('riddleFile', file.name);
}
return this.http.post(QUEST_API + 'create', formDataGenerated)
.pipe(
map((res: any) => {
return res;
})
);
i'm using this route in nodeJS: router.post('/create', [auth, multer], questCtrl.create);
The second middleware 'multer' is working fine
const util = require("util");
const multer = require("multer");
const path = require('path')
const pathFile = path.resolve('./resources/assets/uploads/')
let storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, pathFile);
},
filename: (req, file, cb) => {
cb(null, file.originalname);
},
});
let uploadFile = multer({
storage: storage,
}).single("file");
let uploadFileMiddleware = util.promisify(uploadFile);
module.exports = uploadFileMiddleware;
And when i'm in my questController i can get all the form values like this :
const formValue = JSON.parse(JSON.stringify(req.body));
At this point, my file is correctly uploaded and i can get all the needed data from the submited form
But now, i want to add another middleware that will allow me to authenticate the user using JwtToken.
In this code i need to access req.body.id that is submitted in FormData
But now, due to the multipart enconding i can't access this property so easily.
So my question is, how to implement my 'auth' middleware and after the user is authorized, continue on my multer middleware and controller (as it's currently working)
Generally, the request in Express falls through middleware until it is not broken by res.send(). The "jump" to next middleware is secured with next() function, that is passed to the middleware as third argument.
app.use((req, res, next) => {
if (req.body.password === 'mypassword') {
req.myAuthentication = true // append whatever props to req
console.log('I have authenticated the user!')
next() <-- sends request to the next middleware
})
app.get('/authenticated', (req, res, next) => {
if (req.myAuthentication) { // use your property in next middleware
res.sendFile('authenticated.html')
}
res.status(401)
next() <-- no effect, res.send/res.status finishes the middleware fn

req.file is undefined: uploading images in express using multer

I am trying to upload images in an express server using multer, however, uploading images using postman using the route below, gives the json message { msg: 'image uploaded successfully' } (i.e., the route is reached correctly), but req.file gives undefined. Why? the related file structure is as follows, to make sure I am referencing the destination correctly:
-backend
--routes
---uploadRoutes.js
--server.js
-frontend
-uploads
uploadRoutes.js
import path from 'path';
import express from 'express';
import multer from 'multer';
const router = express.Router();
const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, 'uploads');
},
filename(req, file, cb) {
cb(
null,
`${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`
);
},
});
function checkFileType(file, cb) {
const filetypes = /jpg|jpeg|png/;
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = filetypes.test(file.mimetype);
if (extname && mimetype) {
return cb(null, true);
} else {
cb('Images only!');
}
}
const upload = multer({
storage,
fileFilter: function (req, file, cb) {
checkFileType(file, cb);
},
});
router.post('/', upload.single('image'), (req, res) => {
console.log(req.file);
try {
res.status(200).json({ msg: 'image uploaded successfully' });
} catch (error) {
console.error(error.message);
}
// res.send(`/${req.file.path}`);
});
export default router;
just check the header and body form-data request, because your code is correctly if you have this line in the app file
app.use("/uploads", express.static("uploads"));
header of request

How to upload images directly on cloudinary without storing it into local directory?

I am new in ExpressJs and working on creating api for one of a dashboard created in reactjs. There is a form in a dashboard which is collecting some of information from the users like "title", "description" and "image". I have created an express server to collect that information and to save it into mongodb. For images What I have done is that, I am uploading image to Cloudinary and storing uploaded url and public_id into database.
So after following some of tutorials I have done something like this.
index.js
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const db = require("./db");
// Api router import goes here
const sectionTypesRouter = require("./routes/section-types-router");
const app = express();
const apiPort = 3000;
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(bodyParser.json());
db.on("error", console.error.bind(console, "MongoDB connection error:"));
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.use("/api", sectionTypesRouter);
app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`));
Than, First I have create a file multer.js :
const multer = require("multer");
const storage = multer.diskStorage({
destination: "public/uploads",
filename: (req, file, cb) => {
cb(null, file.fieldname + "-" + Date.now());
},
});
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
cb(null, true);
} else {
//reject file
cb({ message: "Unsupported file format" }, false);
}
};
const upload = multer({
storage: storage,
fileFilter: fileFilter,
});
module.exports = upload;
Below is my api router section-type-router.js :
const express = require("express");
const upload = require("../utils/multer");
const SectionTypesCtrl = require("../controllers/section-types-ctrl");
const router = express.Router();
router.post(
"/section-type",
upload.single("image"),
SectionTypesCtrl.createSectionType
);
router.get("/section-types", SectionTypesCtrl.getSectionTypes);
module.exports = router;
This is the section-type-ctrl.js :
const SectionType = require("../models/section-type-model");
const fs = require("fs");
const path = require("path");
const cloudinaryUploader = require("../utils/cloudinaryUploader");
const createSectionType = async (req, res) => {
const body = req.body;
if (!body) {
return res.status(400).json({
success: false,
error: "Required parameter are missing",
});
}
cloudinaryUploader
.uploads(req.file.path, "Section-Types")
.then((result) => {
const sectionType = new SectionType({
title: body.title,
description: body.description,
image: {
url: result.url,
publicId: result.public_id,
},
});
sectionType
.save()
.then(() => {
return res.status(201).json({
success: true,
id: sectionType._id,
message: "Section type created!",
});
})
.catch((error) => {
return res.status(400).json({
error,
message: "Section type not created!",
});
});
})
.catch((error) => {
res.status(500).send({
message: "failure",
error,
});
});
};
module.exports = {
createSectionType,
};
And lastly this is cloudinaryUpload.js :
const cloudinary = require("../config/cloudinary");
exports.uploads = (file, folder) => {
return new Promise((resolve) => {
cloudinary.uploader.upload(
file,
{
resource_type: "auto",
folder: folder,
},
(err, result) => {
if (!err) {
resolve({
url: result.url,
public_id: result.public_id,
});
} else {
throw err;
}
}
);
}).catch((error) => {
throw error;
});
};
Now, everything is working properly. Images is uploading to the cloudinary and returned url and public_id is storing in database. But the problem is that image that I have uploaded is also upload on local directory public/uploads/. This will may create a storage issue while host a site. So Is there any best way to upload image directly to the cloudinary without creating a copy in local directory which also should work on production mode ?
In your example, the file is being stored to public/uploads on your server because you're telling multer to do so via multer.diskStorage
As #Molda's comment above says, you can avoid this by using the multer-storage-cloudinary package to have Multer store the file in Cloudinary automatically.
Another possibility is to change how you're using Multer so it doesn't store the file anywhere, then take the uploaded file while it's in memory and pass it to Cloudinary's SDK as a stream.
There's an example of this in this blog post on the Cloudinary site: https://cloudinary.com/blog/node_js_file_upload_to_a_local_server_or_to_the_cloud
In your case, you can stop using multer.diskStorage, in favour of just using multer() then use streamifier or another library to turn the uploaded file into a stream, and pass that to cloudinary.uploader.upload_stream()

How can i upload an image after verifying user data in express.js

I am trying to create a user profile in express.js and MongoDB. I am using multer for image uploading. Multer middleware always uploads the image before verifying my user data. If user validation is failed, nevertheless image is uploaded. But, I want to upload an image after validating user data. That means, I will check user data in the controller, and if it is valid then I will upload image and store user data to MongoDB. How can I do that? Thanks in advance!
multerConfig.js
exports.multerConfig = (multer) => {
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, './uploads/');
},
filename: (req, file, cb) => {
cb(null, 'img-' + new Date().toISOString() + '-' + file.originalname);
}
});
const fileFilter = (req, file, cb) => {
(file.mimetype === 'image/png' || file.mimetype === 'image/jpeg' || file.mimetype === 'image/jpg')
? cb(null, true)
: cb(null, false)
};
return multer({
storage: storage,
limits: { fileSize: 1048576 },
fileFilter: fileFilter
});
};
user.js(Routes)
const multer = require('multer');
const express = require('express');
const userController = require('../controllers/user');
const { multerConfig } = require('../utility/multerConfig');
const validateObjectId = require('../middleware/validateObjectId');
const asyncErrorHandler = require('../middleware/asyncErrorHandler');
const router = express.Router();
const upload = multerConfig(multer);
router.post('/', upload.single('image'), asyncErrorHandler(userController.createUser));
module.exports = router;
user.js(Controller)
const { User, validate } = require('../models/user');
const { deleteFile } = require('../utility/fileUtility');
const { failed, success } = require('../utility/utility');
exports.createUser = async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send({ ...failed, message: error.details[0].message });
const { name, address, mobile, email, password } = req.body;
if (!req.file) return res.status(400).send({ ...failed, message: `you have to upload an image!` });
const isUserExist = await User.find().or([{ mobile }, { email }]);
if (isUserExist.length > 0) return res.status(409).send({ ...failed, message: `${name} is already exists!` });
const image = req.file.path;
const newUser = new User({ name, address, mobile, email, password, image });
const savedUser = await newUser.save();
if (!savedUser) return res.status(500).send({ ...failed, message: `user ${name} is failed to save!` });
res.send({
...success,
data: savedUser,
message: `user ${name} is saved successfully`
});
};
You can use two multer middleware (one for parsing text, one for uploading your file).
Let's say you have a form with a name (text field) and avatar (file field), you can do this:
var express = require('express');
var multer = require('multer');
var app = express();
var upload = multer({ dest: 'uploads/' });
app.post('/profile',
upload.none(), function (req, res, next) {
// validate `req.body.name` here
// and call next(err) if it fails
next();
},
upload.single('avatar'), function (req, res, next) {
// file is now uploaded, save the location to the database
res.end('done!');
});
app.listen(9000);

Setting response statuses using Multer in Express?

I'm using Multer as Express middleware. In my example I'm checking that the file extension and mimetype are correct (for a wave file), and I want to respond with 415 if they aren't. However I don't know how to do this with Multer's fileFilter, so I'm checking if the file exists in router handler's request object, which feels a bit awkward. Also maybe I want to implement different fileFilter's and error codes in the future. Is there a recommended pattern for setting response statuses with Multer in Express?
const upload = multer(
{
dest: UPLOAD_PATH,
fileFilter: function(req, file, cb) {
const filetypes = /wave|wav/;
const mimetype = filetypes.test(file.mimetype);
const extname = filetypes.test(
path.extname(file.originalname).toLowerCase());
cb(null, (mimetype && extname));
},
}
);
router.post('/', upload.single('wave'), (req, res) => {
const file = req.file;
if (!file) {
return res.status(415).send('Only audio/wav files are supported.');
}
// Do some async task with file
return res.sendStatus(200);
});
You can do it like this:
const upload = multer({
dest: 'uploads/',
fileFilter: function (req, file, cb) {
const filetypes = /wave|wav/;
const mimetype = filetypes.test(file.mimetype);
const extname = filetypes.test(
path.extname(file.originalname).toLowerCase());
if (mimetype && extname) {
cb(undefined, true);
} else {
req.notWAVEMimeType = 'Only audio/wav files are supported.';
return cb(undefined, false);
}
}
});
router.post('/', upload.single('wave'), (req, res) => {
if (req.notWAVEMimeType) {
return res.status(415).end(req.notWAVEMimeType);
}
// Do some async task with file
return res.sendStatus(200);
});
The notWAVEMimeType property on the request object allows you to return the error message to the POST method. According to this you can add other mime types and check the existence of the related property of req afterwards.

Resources