最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Validation Error with Express Validator and Multer in Node.js - Stack Overflow

programmeradmin0浏览0评论

I'm working on a Node.js backend using Express, and I'm encountering a validation error when trying to create a new post with file uploads.

Despite providing the required data in the form-data section of Postman, I'm getting a validation error indicating that the title and content fields are empty. However, my images are being successfully stored in Cloudinary.

Here is my setup:

Middleware (uploadImage.js):

const 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):

const 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):

exports.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):

const 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):

require("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

  • URL: http://localhost:3000/api/posts/new

  • Body: form-data

    • title: First Post
    • content: Hello World
    • imageUri:(file)
    • thumbnails: (file)

Logs from the server:

[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: '.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: '.webp.png',
      size: 48171,
      filename: 'post/uploads/NEW_Vision.webp'
    }
  ]
}

Despite providing the data, I'm getting the following validation error in Postman

{
  "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: Why am I getting validation errors for the title and content fields even though they are provided in the request? How can I resolve this issue? Additionally, why are my images being successfully stored in Cloudinary despite the validation errors?

I'd Appreciate Some help! Thanks!!

I'm working on a Node.js backend using Express, and I'm encountering a validation error when trying to create a new post with file uploads.

Despite providing the required data in the form-data section of Postman, I'm getting a validation error indicating that the title and content fields are empty. However, my images are being successfully stored in Cloudinary.

Here is my setup:

Middleware (uploadImage.js):

const 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):

const 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):

exports.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):

const 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):

require("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

  • URL: http://localhost:3000/api/posts/new

  • Body: form-data

    • title: First Post
    • content: Hello World
    • imageUri:(file)
    • thumbnails: (file)

Logs from the server:

[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/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/du9ze60lr/image/upload/v1738605268/post/uploads/NEW_Vision.webp.png',
      size: 48171,
      filename: 'post/uploads/NEW_Vision.webp'
    }
  ]
}

Despite providing the data, I'm getting the following validation error in Postman

{
  "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: Why am I getting validation errors for the title and content fields even though they are provided in the request? How can I resolve this issue? Additionally, why are my images being successfully stored in Cloudinary despite the validation errors?

I'd Appreciate Some help! Thanks!!

Share Improve this question edited Feb 3 at 19:48 traynor 8,7523 gold badges15 silver badges28 bronze badges asked Feb 3 at 18:28 PrincePrince 2710 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

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:

router.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:

// 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:

// service to check fieldnames, remove files
// reset fieldname counters
const validationService = {

    checkFiles(req, file) {         
    },

    removeFiles() {

    },
    
    reset() {

    }

};
// 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 },
]);
  // remove files here also
  const error = validationResult(req);

  if (!error.isEmpty()) {

    // remove files
    filesCheck.removeFiles();

    return res.status(400).json({
    //...
发布评论

评论列表(0)

  1. 暂无评论