You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express中Zod验证含多文件FormData时files属性未定义问题

问题:Express中使用Zod验证含多文件的FormData时,files属性提示undefined

HTML元素

<input type="file" id="files" name="files" multiple />

前端JavaScript代码

// Create a new FormData object
const formData = new FormData();

// Append the form data to the FormData object
formData.append("title", $("#title").val());
formData.append("content", $("#content").val());

// Append the files to the FormData object
const files = document.getElementById("files").files;
for (let i = 0; i < files.length; i++) {
    formData.append("files", files[i]);
}

// Use fetch to post the FormData object to the server
// This will automatically set the Content-Type header to multipart/form-data
const response = await fetch("http://localhost:3001/blog", {
    method: "POST",
    body: formData,
});
console.log(await response.json());

Express路由代码

import express from "express";
import multer from "multer";

import { validate } from "../middleware/schemaValidator.js";
import { blogSchema } from "../schemas/blogSchema.js";
import { addBlogPost } from "../controllers/blogController.js";

const storage = multer.diskStorage({
  destination: (_req, _file, cb) => {
    cb(null, "public/uploads/");
  },
  filename: (_req, file, cb) => {
    cb(null, file.originalname);
  },
});
const upload = multer({ storage });

const router = express.Router();

// Validate the blogSchema before adding a new blog post
// The validate function will return an error if the request body does not match the schema
// If there is no error, the addBlogPost function will be called
router.post("/", upload.array("files", 2), validate(blogSchema), addBlogPost);

export default router;

Zod Schema代码

import { z } from "zod";
import { zfd } from "zod-form-data";

const ACCEPTED_IMAGE_TYPES = ["image/png"];

export const blogSchema = zfd.formData({
  title: z.string(),
  content: z.string(),
  files: z.array(z.instanceof(File)).refine((files) => {
    return files.every((file) => ACCEPTED_IMAGE_TYPES.includes(file.type));
  }),
});

错误信息

[
    {
      "code": "invalid_type",
      "expected": "array",
      "received": "undefined",
      "path": [
        "files"
      ],
      "message": "Required"
    }
]

问题原因

  1. multer的处理逻辑:multer解析上传的文件后,不会将文件放入req.body,而是存放在req.files(使用upload.array时)中,而你的Zod验证逻辑默认只解析req.body,所以找不到files字段。
  2. Node端无File类:浏览器中的File对象在Node.js环境中不存在,multer处理后的文件是包含originalname、mimetype、path等属性的自定义对象,用z.instanceof(File)验证必然失败。

解决方案

1. 修改验证中间件,合并req.body和req.files

在schemaValidator.js的validate函数中,把req.files合并到待验证的数据对象:

// schemaValidator.js
import { z } from "zod";

export const validate = (schema) => async (req, res, next) => {
  try {
    // 合并body和multer解析后的files
    const data = {
      ...req.body,
      files: req.files
    };
    await schema.parseAsync(data);
    next();
  } catch (err) {
    res.status(400).json(err.errors);
  }
};

2. 调整Zod Schema,适配multer返回的文件结构

替换z.instanceof(File)为描述multer文件对象的Schema:

import { z } from "zod";

const ACCEPTED_IMAGE_TYPES = ["image/png"];

// 定义multer返回的单个文件结构
const multerFileSchema = z.object({
  originalname: z.string(),
  mimetype: z.string(),
  path: z.string(),
  size: z.number()
});

export const blogSchema = z.object({
  title: z.string(),
  content: z.string(),
  files: z.array(multerFileSchema).refine((files) => {
    return files.every((file) => ACCEPTED_IMAGE_TYPES.includes(file.mimetype));
  })
});

3. 验证中间件顺序说明

upload.array必须在验证中间件之前执行,确保multer先解析文件到req.files,验证逻辑才能拿到正确的数据。

修改完成后,Zod即可正确识别files数组并验证文件类型。


内容的提问来源于stack exchange,提问作者RobertFrenette

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 01:03:15