I am struggling to get this example code to work correctly. I am new to all of the technologies being used in this example, so I also don’t know what steps to take to try and debug things. I tried to add some console.log
logging, but failed to do this as well.
Here is the server side code:
const express = require("express");
const multer = require("multer");
const cors = require("cors"); // why is this needed? I tried to find out but still don't understand what it does
const fs = require("fs");
const app = express();
const port = 3000;
app.use(express.json());
app.use(cors()); // what does it do?
const storage = multer.diskStorage({
destination: (request, file, cb) => {
cb(null, "uploads_test/"); // should be saving to `./uploads_test/` if I understand correctly, the folder does exist
},
filename: (request, file, cb) => {
cb(null, file.fieldname + "-" + Date.now() + ".jpg");
},
});
const upload = multer({ storage: storage });
app.post("/upload", upload.single("image"), async (request, response) => {
try {
await fs.promises.mkdir("uploads_test", { recursive: true });
// none of this appeared to be helpful
//console.log(request.body);
//console.log(request.body.formData);
// this just didn't work
//console.log(request.body.formData[0]);
response.status(200).json({
message: "image upload success",
file: request.file,
});
} catch (error) {
console.error("Error handling image upload:", error.message);
// Send a response indicating failure
response.status(500).json({ error: "Internal Server Error" });
}
});
async function main() {
if (!fs.existsSync("uploads_test")) {
fs.mkdirSync("uploads_test");
}
app.listen(port, () => {
console.log(
`server imageServer (example) is listening at http://localhost:${port}`
);
});
}
main().catch((error) => {
console.error(error);
});
Here is the React-Native side code:
import React, { useState, useEffect } from "react";
import { View, Text, Image, Button, StyleSheet } from "react-native";
import * as ImagePicker from "expo-image-picker";
import axios from "axios";
const ImageUploadScreenExpo = () => {
const [hasGallaryPermissions, setHasGalleryPermissions] = useState(null);
const [selectedImage, setSelectedImage] = useState(null);
useEffect(() => {
(async () => {
const galleryStatus = await ImagePicker.requestCameraPermissionsAsync();
setHasGalleryPermissions(galleryStatus.status === "granted");
if (galleryStatus.status !== "granted") {
alert("permission denied");
}
})();
}, []);
const handleImageUploadAsync = async () => {
if (!selectedImage) {
alert("Please select an image first");
return;
}
const fileExtension = selectedImage.split(".").pop();
const fileType = `image/${fileExtension}`;
const formData = new FormData();
formData.append("image", {
uri: selectedImage,
type: fileType,
name: `image.${fileExtension}`,
});
try {
const response = await axios.post("http://192.168.0.11:3000/upload", {
formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
// I keep seeing this in the logs, but the image is not saved
console.log("Image uploaded successfully:", response.data);
} catch (error) {
console.error(error);
}
};
const selectImageAsync = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.canceled) {
setSelectedImage(result.assets[0].uri);
} else {
alert("no image selected");
}
};
if (hasGallaryPermissions === false) {
return <Text>No access to Internal Storage</Text>;
}
return (
<View style={styles.container}>
<Text>Select an image to upload:</Text>
<Button title="Select Image" onPress={selectImageAsync} />
<Button title="Upload Image" onPress={handleImageUploadAsync} />
{selectedImage && (
<View style={styles.imageContainer}>
<Image
source={{ uri: selectedImage }}
style={{ width: 200, height: 200 }}
/>
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "white",
alignItems: "center",
justifyContent: "center",
},
});
export default ImageUploadScreenExpo;
Everything seems to be working ok as far as any logging and errors are concerned, but no images are being saved to the server directory. I can’t seem to figure out why this is the case, and I also don’t have any idea where to start with debugging. For example, if I were going to add some logging somewhere on the server side to check if the data for the image is really there or not, how would I go about doing that?
Edit
I managed to get some sensible logging information using this:
console.log(request.body.formData);
for (let key in request.body.formData) {
console.log(key);
console.log(request.body.formData[key]);
}
which produces this:
{ _parts: [ [ 'image', [Object] ] ] }
_parts
[
[
'image',
{
uri: 'file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252Ftestapp-cac352d9-fc7a-41dc-9f00-ac0cc0ed4542/ImagePicker/3005453c.jpeg',
type: 'image/jpeg',
name: 'image.jpeg'
}
]
]
That doesn’t appear to contain any image data, so I am not surprised that no image is being saved to disk. Can anyone point me in the right direction as to what to do next?
Wireshark
I captured the TCP stream using Wireshark, and this is the result. As can be seen there is no image data contained in the request.
This also appears to hint at the problem. The "form data" is somehow being encoded as JSON, rather than what it should be. And the HTTP headers declare the type to be "application/json" which is also wrong.
POST /upload HTTP/1.1
accept: application/json, text/plain, */*
Content-Type: application/json
Content-Length: 320
Host: 192.168.0.232:3000
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.2
{"formData":{"_parts":[["image",{"uri":"file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252Fpicky-cac352d9-fc7a-41dc-9f00-ac0cc0ed4542/ImagePicker/740ec546-cf6d-40fd-982d-88722cafb53c.jpeg","type":"image/jpeg","name":"image.jpeg"}]]},"config":{"headers":{"Content-Type":"multipart/form-data"}}}
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Content-Length: 34
ETag: W/"22-or0YmP0zqfrLr/aFLTWZuX2LMys"
Date: Tue, 12 Dec 2023 15:39:05 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"message":"image upload success"}
2
Answers
I am not totally sure why using
axios.post
didn't work as expected.Using the alternative API where the method (
POST
) is specified as part of the configuration object passed to the function does work.The transform request parameter is required to prevent axios converting the data to String/JSON?
See this answer.
i have tried to make an avatar upload function using expressjs and multer before. So I think my code can be a useful reference for you. I’ll put it below.
Upload middleware (uploadAvatar.js)
Receive upload avatar route (userRoute.js)
Handle upload avatar (userController.js)