DEV Community

Hala
Hala

Posted on

How to Fix Image Upload Failures & 404 Errors in React Native + Express/Multer

Uploading images from a React Native (Expo) client to a Node.js/Express backend using Multer is a common task, but it often comes with two frustrating issues:

  1. TypeError: Network request failed during the upload process.
  2. 404 Not Found warnings when trying to render uploaded images in the mobile app.

Here is a breakdown of why these errors happen and how to resolve them cleanly on both the backend and client side.


The Error Logs

1. Upload Phase Error (React Native Console)

ERROR Upload error: [TypeError: Network request failed]

Enter fullscreen mode Exit fullscreen mode

2. Asset Rendering Phase Warning (Expo / LogBox)

WARN Unexpected HTTP code Response{protocol=http/1.1, code=404, message=Not Found, url=http://192.168.8.101:8000/uploads/1784893610816-edeedac9.jpeg}

Enter fullscreen mode Exit fullscreen mode

Root Cause Analysis

Problem 1: Unhandled Missing Upload Directory (ENOENT)

When using multer.diskStorage, you specify a target directory like public/uploads. Multer does not create missing folders automatically. If that folder does not exist on your server when an upload request hits the endpoint, Node.js throws an ENOENT exception, abruptly closing the socket connection. From the React Native side, this manifests as TypeError: Network request failed.

Problem 2: Incorrect Static File Route Prefix

Using app.use(express.static("public")) in Express serves the contents of the public folder directly from the root path (http://IP:PORT/filename.jpg). If your client tries to fetch http://IP:PORT/uploads/filename.jpg, Express cannot map the path correctly and returns a 404 Not Found.

Problem 3: Android File URI Formatting

Local image URIs retrieved on Android devices often require explicit formatting (uri, name, type) inside FormData to be parsed properly as a binary stream by fetch.


The Solution

1. Auto-create Directory in multer.js

Update your Multer configuration to check for and create the target directory dynamically using the standard fs module:

import multer from "multer";
import fs from "fs";
import path from "path";

const uploadDir = "public/uploads";

if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir, { recursive: true });
}

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, uploadDir);
  },
  filename: (req, file, cb) => {
    const ext = path.extname(file.originalname) || ".jpg";
    cb(null, `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`);
  },
});

const upload = multer({ storage });

export default upload;

Enter fullscreen mode Exit fullscreen mode

2. Map Static Route Correctly in server.js

Explicitly map requests containing the /uploads prefix to the physical public/uploads directory on disk:

import express from "express";

const app = express();

app.use("/uploads", express.static("public/uploads"));

Enter fullscreen mode Exit fullscreen mode

3. Handle Client-Side Uploads in React Native (functions.js)

Format the image payload cleanly inside FormData before making the network call:

import { Platform } from "react-native";
import { API_URL } from "@env";

export async function uploadImage(token, localUri) {
  try {
    const filename = localUri.split("/").pop() || "profile.jpg";
    const match = /\.(\w+)$/.exec(filename);
    const type = match ? `image/${match[1]}` : "image/jpeg";

    const fileUri = Platform.OS === "android" ? localUri : localUri.replace("file://", "");

    const formData = new FormData();
    formData.append("profilePicture", {
      uri: fileUri,
      name: filename,
      type: type,
    });

    const authHeader = token?.startsWith("Bearer ") ? token : `Bearer ${token}`;

    const response = await fetch(`${API_URL}/user/profile-picture`, {
      method: "PUT",
      headers: {
        Authorization: authHeader,
        Accept: "application/json",
      },
      body: formData,
    });

    const data = await response.json();

    if (!response.ok) {
      throw new Error(data.error || data.message || `Server Error: ${response.status}`);
    }

    return data;
  } catch (error) {
    console.error("Upload error:", error);
    throw error;
  }
}

Enter fullscreen mode Exit fullscreen mode

Verification & Results

Applying these updates addresses the issue on both ends:

  • The backend responds with HTTP status 200 OK on image upload requests.
  • MongoDB stores the reachable absolute URL string.
  • Both iOS and Android clients render the updated profile image without broken image icons or caching issues.

Conclusion

When building mobile apps with React Native and Node.js, ensure your server dynamically verifies target upload paths at runtime, and double-check that Express static route configurations mirror the URL paths requested by the frontend client.

Top comments (0)