I'm trying to use Zod to perform Schema Validation on a FormData Object which has multiple files (HTML file input element with multi-select) in an Express App. When the Schema is validated, it appears the files property is undefined. I'm hoping someone can provide some guidance as to why the property doesn't seem to exist in the request.
HTML element
<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 Route
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));
}),
});
Error
[
{
"code": "invalid_type",
"expected": "array",
"received": "undefined",
"path": [
"files"
],
"message": "Required"
}
]
GitHub Repo with complete code
Thanks in advance for any guidance!
The problem is that you pass req.body
to your validation middleware here:
// Validate the request body against the schema
schema.parse(req.body);
but multer
stores files in req.files
, which is why it's undefined, while other fields are in req.body
, which is why it works when files are not present. So, you'd need to include req.files
in validation.
Also, multer
doesn't use File
types, it creates custom objects, you can see their properties here in the docs: File information.
So, you could instead validate files as array of objects.
Try this:
add files to the validator:
schema.parse({...req.body, files:req.files});
add validation against multer objects (array of two multer objects):
const multerFile = z.object({
fieldname: z.string(),
originalname: z.string(),
encoding: z.string(),
mimetype: z.string(),
destination: z.string(),
filename: z.string(),
path: z.string(),
size: z.number()
});
export const blogSchema = zfd.formData({
title: z.string(),
content: z.string(),
// max 2 files, modify as needed
files: z.array(multerFile).max(2).refine((files) => {
return files.every((file) => ACCEPTED_IMAGE_TYPES.includes(file.mimetype));
})
});