How can I access all images uploaded that are part of an array of objects and save them to /uploads?
My req.body looks like this:
{
client: 'my_client_test',
city: 'my_city_test',
spots: [
{
photo_1: 'C:\\fakepath\\photo_1_test.jpeg',
photo_2: 'C:\\fakepath\\photo_2_test.jpeg',
address: 'my_address_test'
}
]
}
I need to save all photo_1 and photo_2 of spots array.
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "uploads/");
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
const upload = multer({ storage });
app.post("/admin/add", /* HERE */, (req, res) => {
console.log(req.files); // undefined
console.log(req.body);
});
Here are the inputs:
<form id="form" action="/add" enctype="multipart/form-data">
<!-- (...) -->
<div class="spot">
<label for="photo_1_0">Photo 1:</label>
<input type="file" id="photo_1_0" name="photo_1_0" required />
<label for="photo_2_0">Photo 2:</label>
<input type="file" id="photo_2_0" name="photo_2_0" required />
<label for="address_0">Address:</label>
<input type="text" id="address_0" name="address_0" required />
</div>
Inputs are named photo_1_0, photo_2_0 and address_0 because the user is able to click a button and add another spot (photo_1_1, photo_2_1, address_1)
Index.js request
form.addEventListener("submit", (event) => {
event.preventDefault();
const client = document.getElementById("client").value;
const city = document.getElementById("city").value;
const spotsArray = [];
for (let i = 0; i < spotIndex; i++) {
const photo1 = document.getElementById(`photo_1_${i}`).value;
const photo2 = document.getElementById(`photo_2_${i}`).value;
const address = document.getElementById(`address_${i}`).value;
spotsArray.push({ photo_1: photo1, photo_2: photo2, address });
}
const data = {
client,
city,
spots: spotsArray,
};
console.log(data); // everything looks good here
fetch("http://localhost:3334/admin/add", {
method: "POST",
headers: {
"Content-Type": "application/json", // should be "multipart/form-data" ?
},
body: JSON.stringify(data),
});
});
Multer works with multipart/form-data
requests. You appear to be sending your request with an application/json
body which I cannot recommend for file uploads (even with base64 encoding).
For something like this, I'd use Multer's any()
handler due to the dynamic field names.
On the client-side, use field names with square-bracket notation to create a structure...
<label for="photo_1_0">Photo 1:</label>
<!-- note the field name syntax 👇 -->
<input type="file" id="photo_1_0" name="spots[0][photo_1]" required />
<label for="photo_2_0">Photo 2:</label>
<input type="file" id="photo_2_0" name="spots[0][photo_2]" required />
<label for="address_0">Address:</label>
<input type="text" id="address_0" name="spots[0][address]" required />
then construct a FormData
instance to upload your files and other data
form.addEventListener("submit", async (e) => {
e.preventDefault();
const body = new FormData(e.target);
const res = await fetch("/admin/add", {
method: "POST",
body,
});
});
Unfortunately, Multer isn't particularly good at dynamic file field names but you can write a small piece of middleware using Lodash's .set() to make it easier to work with
import set from "lodash/set.js";
const mapFiles = (req, _res, next) => {
if (Array.isArray(req.files)) {
req.files = req.files.reduce(
(map, { fieldname, ...file }) => set(map, fieldname, file),
{}
);
}
next();
};
app.post("/admin/add", upload.any(), mapFiles, (req, res) => {
const bodySpots = req.body.spots; // an array of objects with `address`
/*
[
{
address: "123 Fake St"
}
]
*/
const fileSpots = req.files.spots; // an array of file pairs
/*
[
{
photo_1: {
originalname: ...,
...
},
photo_2: {
originalname: ...,
...
}
}
]
*/
});