Asked 1 month ago by LunarNomad342
How can I resolve express-validator validation errors with multer in my Node.js file upload process?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by LunarNomad342
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm building a Node.js backend with Express, and I'm encountering unexpected validation errors when creating a new post with file uploads.
Despite providing the required text fields in the form-data (using Postman), I get errors stating that the title and content are empty while images are successfully uploaded to Cloudinary.
Below is my current setup:
Middleware (uploadImage.js):
JAVASCRIPTconst cloudinary = require("cloudinary").v2; const { CloudinaryStorage } = require("multer-storage-cloudinary"); const multer = require("multer"); // Configure Cloudinary storage for multer const storage = new CloudinaryStorage({ cloudinary: cloudinary, params: { folder: "post/uploads", public_id: (req, file) => file.originalname, resource_type: "image", // Ensure the resource type is image transformation: [ { width: 1000, crop: "scale" }, // Resize the image to a width of 1000 pixels { quality: "auto:best" }, // Automatically adjust the best quality { fetch_format: "auto" }, // Automatically convert to the best format ], }, }); // Multer upload middleware const upload = multer({ storage }).fields([ { name: "imageUri", maxCount: 1 }, { name: "thumbnails", maxCount: 4 }, ]); module.exports = upload;
Routes (postRoutes.js):
JAVASCRIPTconst express = require("express"); const router = express.Router(); const { body } = require("express-validator"); const postController = require("../controller/postController"); const upload = require("../middleware/uploadImage"); router.get("/posts", postController.getPosts); router.post( "/posts/new", [ body("title") .trim() .isLength({ min: 5 }) .notEmpty() .withMessage("Title is required"), body("content") .trim() .isLength({ min: 5 }) .notEmpty() .withMessage("Content is required"), ], upload, postController.createPost ); router.get("/posts/:id", postController.getPostById); router.put( "/posts/:id", [ body("title") .trim() .isLength({ min: 5 }) .notEmpty() .withMessage("Title is required"), body("content") .trim() .isLength({ min: 5 }) .notEmpty() .withMessage("Content is required"), ], postController.updatePostById ); router.delete("/posts/:id", postController.deletePostById); module.exports = router;
Controller (postController.js):
JAVASCRIPTexports.createPost = async (req, res, next) => { console.log("Request Body:", req.body); console.log("Request Files:", req.files); const { title, content } = req.body; const error = validationResult(req); if (!error.isEmpty()) { return res.status(400).json({ status: "Failed", code: 400, message: "Validation failed", errors: error.array(), }); } try { const imageUri = req.files.imageUri ? req.files.imageUri[0].path : null; const thumbnails = req.files.thumbnails ? req.files.thumbnails.map(file => file.path) : []; const post = new Post({ title, content, imageUri, thumbnails, }); const result = await post.save(); return res.status(201).json({ status: "Success", code: 201, message: "Post created successfully", data: result, }); } catch (error) { return res.status(500).json({ status: "Failed", code: 500, message: "An error occured while creating post", error, }); } };
Model (postModel.js):
JAVASCRIPTconst mongoose = require("mongoose"); const Schema = mongoose.Schema; const postSchema = new Schema( { title: { type: String, required: true, }, content: { type: String, required: true, }, imageUri: { type: String, required: false, }, thumbnails: { type: [String], required: false, }, }, { timestamps: true } ); module.exports = mongoose.model("post", postSchema);
Server Setup (server.js):
JAVASCRIPTrequire("dotenv").config(); const express = require("express"); const bodyParser = require("body-parser"); const mongoose = require("mongoose"); const postRoutes = require("./routes/postRoutes"); const cloudinary = require('./config/CloudinaryConfig') const hostname = "localhost"; const port = 8080; const MONGODB_URI = process.env.MONGODB_URI; const app = express(); app.use(bodyParser.json()); app.use((req, res, next) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET , POST , PUT , DELETE"); res.setHeader("Access-Control-Allow-Headers", "Content-Type , Authorization"); next(); }); app.use("/", postRoutes); mongoose .connect(MONGODB_URI) .then(() => { console.log("Connected to database"); app.listen(port, hostname, () => { console.log(`Server is running at http://${hostname}:${port}`); }); }) .catch((err) => { console.log(err); });
Request in Postman:
Method: POST
Body: form-data
Server Logs:
BASH[nodemon] starting `node app.js` Connected to database Server is running at http://localhost:8080 Request Body: [Object: null prototype] { title: 'First Post', content: 'Hello World' } Request Files: [Object: null prototype] { imageUri: [ { fieldname: 'imageUri', originalname: 'Leonardo_Kino_XL_wildlife_jungle_and_include_all_animals_2.jpg', encoding: '7bit', mimetype: 'image/jpeg', path: 'https://res.cloudinary.com/du9ze60lr/image/upload/v1738605266/post/uploads/Leonardo_Kino_XL_wildlife_jungle_and_include_all_animals_2.jpg.jpg', size: 190411, filename: 'post/uploads/Leonardo_Kino_XL_wildlife_jungle_and_include_all_animals_2.jpg' } ], thumbnails: [ { fieldname: 'thumbnails', originalname: 'NEW_Vision.webp', encoding: '7bit', mimetype: 'image/webp', path: 'https://res.cloudinary.com/du9ze60lr/image/upload/v1738605268/post/uploads/NEW_Vision.webp.png', size: 48171, filename: 'post/uploads/NEW_Vision.webp' } ]
Validation Error in Postman Response:
JSON{ "status": "Failed", "code": 400, "message": "Validation failed", "errors": [ { "type": "field", "value": "", "msg": "Invalid value", "path": "title", "location": "body" }, { "type": "field", "value": "", "msg": "Title is required", "path": "title", "location": "body" }, { "type": "field", "value": "", "msg": "Invalid value", "path": "content", "location": "body" }, { "type": "field", "value": "", "msg": "Content is required", "path": "content", "location": "body" } ] }
Question:
I appreciate any help! Thanks!
Comments 0
•Answers 2
•Views 80
Answer by TychoAI Agent • 1 month ago
The root cause is that your validations run before Multer has parsed the multipart/form-data, which means the title and content fields are empty at the time of validation—even though they're present in the request. Multer (configured in your upload middleware) will parse the request and fill req.body, but in your current route order the express-validator middlewares run first.
Images are getting uploaded because the Multer middleware processes the file fields independently (sending them to Cloudinary) and that happens after the validation middleware is registered. Even though text fields aren’t available yet for validation, the file upload still works.
Reorder your middleware so that Multer runs before the validation chain. This way, req.body will be populated with the form-data fields before express-validator checks them.
JAVASCRIPTrouter.post( "/posts/new", // First, process the multipart/form-data upload, // Then run validations on req.body [ body("title") .trim() .isLength({ min: 5 }) .notEmpty() .withMessage("Title is required"), body("content") .trim() .isLength({ min: 5 }) .notEmpty() .withMessage("Content is required") ], postController.createPost );
Why Validation Error?
The validation middleware is running before Multer parses the multipart/form-data, so req.body remains empty at validation time.
Image Upload Success?
Multer is still successfully processing the file inputs and uploading them to Cloudinary even though the text fields are not yet available for validation.
Switching the order of middlewares ensures that req.body is populated before validation runs, which should resolve the validation errors for your text fields.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by InterstellarEngineer055 • 1 month ago
As you're sending multipart request, express-validator cannot process it, so it fails, and afterwards multer middleware actually processes the request, along with the files, and then your route handler sends the error because validations failed.
So, you could change the order of middleware (and validation logic) to first run multer middleware to get access to req.body
, and then the validators:
JAVASCRIPTrouter.post( "/posts/new", upload, // run multer middleware first [ body("title") .trim() .isLength({ min: 5 }) .notEmpty() .withMessage("Title is required"), body("content") .trim() .isLength({ min: 5 }) .notEmpty() .withMessage("Content is required"), ], postController.createPost );
edit:
If you want to add file validation and be able to cancel file uploading, you could process files in memory first, and then save them to cloudinary if all validations pass (by using multer's memoryStorage()
), or you could use multer's fileFilter
method to validate file, but as it runs sequentially, one file would end up uploading, so maybe implement a separate service which would validate files and remove them if files or expres validator's validations fail, for example:
JAVASCRIPT// use multer's memoryStorage to process files, and then save them const storage = multer.memoryStorage() const upload = multer({ storage: storage }); // ... // validate all, then save files to cloudinary
or use fileFilter method:
JAVASCRIPT// service to check fieldnames, remove files // reset fieldname counters const validationService = { checkFiles(req, file) { }, removeFiles() { }, reset() { } };
JAVASCRIPT// use multer's fileFilter to validate files function fileFilter(req, file, cb) { // check fieldnames, add counter, to check if second fieldname is missing const filesCheck = validationService.checkFiles(req, file); if (!filesCheck) { console.log('files err, cancel uploading'); filesCheck.removeFiles(); return cb(new Error('handle files err')); } else { // all good, reset service, or from the controller validationService.reset(); } cb(null, true); } // Multer upload middleware -> add fileFilter const upload = multer({ storage, fileFilter }).fields([ { name: "imageUri", maxCount: 1 }, { name: "thumbnails", maxCount: 4 }, ]);
JAVASCRIPT// remove files here also const error = validationResult(req); if (!error.isEmpty()) { // remove files filesCheck.removeFiles(); return res.status(400).json({ //...
No comments yet.
No comments yet.