Problem
For an application, I need to be able to update the description and the avatar picture of a user.
For the file upload, I use multer and it works without any problems, but I have a small problem with the description.
I created a middleware validUserDescription which checks if the number of characters in the description is between 2 and 199. But the problem is that if I don’t put first the multer middleware, then the req.body is empty and my validator gives the green light to everything.
Now, you should tell me why not put multer first then? The problem is that if I put it first, then before the code checks the user’s description, the file would be uploaded!
Let’s not talk about the security problems, let’s imagine the following case:
- The file is first uploaded but the validator doesn’t give the green light.
- It means that this file’s path won’t be recorded in the database.
- It also led to my code being incapable of locating it and deleting it when the user correctly updates his avatar picture the next time.
Trials
I tried to use JSON.parse and JSON.stringify on the req.body in my validUserDescription middleware, but they resulted in errors.
I also tried using multiparty but without success too.
Code
Multer middleware: multer-config.js
const multer = require("multer");
const MIME_TYPES = {
"image/jpg": "jpg",
"image/jpeg": "jpg",
"image/png": "png",
"image/gif": "gif"
};
const fileFilter = (req, file, callback) => {
if (file.mimetype == "image/jpg" || file.mimetype == "image/jpeg"
|| file.mimetype == "image/png" || file.mimetype == "image/gif") {
callback(null, true);
} else {
return callback(new Error("Invalid file format! - From multerConfig"), false);
}
};
const storage = multer.diskStorage({
destination: (req, file, callback) => {
callback(null, "images");
},
filename: (req, file, callback) => {
const name = file.originalname.split(" ").join("_");
const extension = MIME_TYPES[file.mimetype];
callback(null, name + Date.now() + "." + extension);
}
});
module.exports = multer({ storage, fileFilter }).single("image");
User routes: user.routes.js
const router = require("express").Router();
const userCtrl = require("../controllers/user.controller");
const { validUserDescription } = require("../middleware/validInputs");
const multer = require("../middleware/multer-config");
const { auth } = require("../middleware/auth");
router.put('/:id', auth, validUserDescription, multer, userCtrl.updateUser);
module.exports = router;
Validator middleware: validInputs.js
exports.validUserDescription = async (req, res, next) => {
try {
const contentRegex = new RegExp(/^.{2,199}$/);
if (contentRegex.test(req.body.user_description)) {
next();
} else {
return res.status(401).json({
success: false,
message: "The number of characters in the must be between 2 and 199! - From validInputs"
});
}
} catch (error) {
return res.status(500).send({
success: false,
error: error,
message: 'From validInputs!'
});
}
};
Controller to update user’s description and avatar picture: user.controller.js
self.updateUser = async (req, res) => {
try {
const userID = req.params.id;
const userExist = await user.findOne({ where: { user_id: userID } });
if (userExist) {
const fileName = userExist.user_pictureURL.split('/images/')[1];
const userObject = req.file
? {
...fs.unlink('images/' + fileName, () => { }),
user_pictureURL: `${req.protocol}://${req.get('host')}/images/${req.file.filename}`,
user_description: req.body.user_description,
updatedAt: Date.now()
}
: {
user_description: req.body.user_description,
updatedAt: Date.now()
};
const updatedUser = await user.update({ ...userObject }, { where: { user_id: userID } });
if (updatedUser[0] === 1) {
return res.status(200).json({
success: true,
message: `User with the id=${userID} has been updated! - From userCtlr`
});
} else {
return res.status(400).json({
success: false,
message: `User with the id=${userID} has not been updated! - From userCtlr`
});
}
} else {
return res.status(404).json({
success: false,
message: `User with the id=${userID} does not exist! - From userCtlr`
});
}
} catch (error) {
return res.status(500).json({
success: false,
error: error,
message: "From userCtlr"
});
}
};
My server which is divided into two parts:
app.js
const express = require('express');
const path = require('path');
const usersRoutes = require("./routes/user.routes");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
res.setHeader('Access-Control-Allow-Methods', '*');
next();
});
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use('/api/users', usersRoutes);
module.exports = app;
server.js
const http = require('http');
const app = require('./app');
function normalizePort(val) {
const port = parseInt(val, 10);
if (isNaN(port)) {
return val;
}
if (port >= 0) {
return port;
}
return false;
};
const port = normalizePort('8080');
app.set('port', port);
function errorHandler(error) {
if (error.syscall !== 'listen') {
throw error;
}
const address = server.address();
const bind = typeof address === 'string' ? 'pipe ' + address : 'port: ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges.');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use.');
process.exit(1);
break;
default:
throw error;
}
};
const server = http.createServer(app);
server.on('error', errorHandler);
server.on('listening', () => {
const address = server.address();
const bind = typeof address === 'string' ? 'pipe ' + address : 'port ' + port;
console.log('Listening on ' + bind);
});
server.listen(port);