Image file upload from React Native ( Expo ) to Node server ( Express )
Asked Answered
R

2

-1

How can I upload an image file (png, jpeg etc.) from React Native using expo to a Node server using Express?

I'm a beginner to mobile UI development and the Expo documentation didn't help much. I also tried using multer (as explained here: File upload from React Native ( expo ) to Node ( multer )), but it didn't work.

Any suggestion, example, or direction would be highly appreciated in this regard.

Reptile answered 12/10, 2024 at 5:9 Comment(0)
R
0

One potential way to handle image upload between the Expo client and the Express server is to convert the image to base64 and communicate with API. Please check the comments in the code to understand each step.

Sample code is as follows.

Expo client > index.tsx

import React, { useState } from 'react';
import { Text, View, StyleSheet, Image, Alert, TouchableOpacity, Platform } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import * as FileSystem from 'expo-file-system';

const UPLOAD_URL = `${process.env.UPLOAD_URL} || http://localhost:3000/upload`;
const PICK_IMAGE_LABEL = 'Pick an Image';
const UPLOAD_IMAGE_LABEL = 'Upload Image';

// Custom hook to handle image picking
const useImagePicker = () => {
    const [selectedImage, setSelectedImage] = useState<string | undefined>(undefined);

    const pickImageAsync = async () => {
        try {
            const result = await ImagePicker.requestMediaLibraryPermissionsAsync();
            if (!result.granted) {
                alert('Permission to access gallery is required!');
                return;
            }

            const pickerResult = await ImagePicker.launchImageLibraryAsync({
                mediaTypes: ImagePicker.MediaTypeOptions.Images,
                allowsEditing: true,
                quality: 1,
            });

            if (!pickerResult.canceled) {
                setSelectedImage(pickerResult.assets[0].uri);
            } else {
                alert('You did not select any image.');
            }
        } catch (error) {
            console.error('Error picking image:', error);
            Alert.alert('Error', 'An error occurred while picking the image.');
        }
    };

    return { selectedImage, pickImageAsync };
};

// Custom hook to handle image upload
const useImageUploader = (selectedImage: string | undefined) => {
    const uploadImage = async () => {
        if (!selectedImage) {
            Alert.alert('No image selected', 'Please select an image first.');
            return;
        }

        let base64Image;

        try {
            if (Platform.OS !== 'web') {
                base64Image = await FileSystem.readAsStringAsync(selectedImage, {
                    encoding: FileSystem.EncodingType.Base64,
                });
                base64Image = `data:image/jpeg;base64,${base64Image}`;
            } else {
                base64Image = selectedImage;
            }

            const response = await fetch(UPLOAD_URL, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ image: base64Image }),
            });

            const result = await response.json();
            if (result.success) {
                Alert.alert('Success', 'Image uploaded successfully!');
            } else {
                console.log('Upload failed:', result.message);
                Alert.alert('Upload Failed', result.message);
            }
        } catch (error) {
            console.error('Error uploading the image:', error);
            Alert.alert('Error', 'An error occurred while uploading the image.');
        }
    };

    return { uploadImage };
};

export default function Index() {
    const { selectedImage, pickImageAsync } = useImagePicker();
    const { uploadImage } = useImageUploader(selectedImage);

    return (
        <View style={styles.container}>
            <Text style={styles.title}>Demo App</Text>
            <Text style={styles.subtitle}>Please upload an image</Text>

            <TouchableOpacity style={styles.pickImageButton} onPress={pickImageAsync}>
                <Text style={styles.pickImageText}>{PICK_IMAGE_LABEL}</Text>
            </TouchableOpacity>

            {selectedImage && (
                <Image source={{ uri: selectedImage }} style={styles.imagePreview} />
            )}

            <TouchableOpacity style={styles.uploadButton} onPress={uploadImage}>
                <Text style={styles.uploadButtonText}>{UPLOAD_IMAGE_LABEL}</Text>
            </TouchableOpacity>
        </View>
    );
}

const styles = StyleSheet.create({...});

Express server > app.ts

import express from "express";
import cors from "cors";
import bodyParser from "body-parser";
import path from "path";
import fs from "fs";

const PORT = process.env.PORT || 3000;

const app = express();

// Middleware setup
app.use(cors());
app.use(express.json({ limit: '50mb' }));
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));

app.get("/", (req, res) => {
    res.json({
       message: "Hello from Express Backend!",
       success: true
    });
});

app.post('/', (req, res) => {
    console.log(req.body)
    res.status(200)
})

// Image upload route to handle Base64 encoded images
app.post('/upload', (req: any, res: any): void => {
    console.log(req.body)

    const { image } = req.body;

    // Check if the 'image' field exists
    if (!image) {
        return res.status(400).json({ success: false, message: "No image provided" });
    }

    // Extract the Base64 string (data:image/jpeg;base64,...)
    const matches = image.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/);

    if (!matches || matches.length !== 3) {
        return res.status(400).json({ success: false, message: "Invalid image format" });
    }

    const fileType = matches[1]; // Image type (jpeg, png, etc.)
    const base64Data = matches[2]; // Base64 data
    const buffer = Buffer.from(base64Data, 'base64'); // Convert Base64 to binary

    // Define a unique filename (e.g., timestamp and file type)
    const fileName = `${Date.now()}.${fileType}`;
    const filePath = path.join(__dirname, 'uploads', fileName);

    // Write the file to the 'uploads' directory
    fs.writeFile(filePath, buffer, (err) => {
        if (err) {
            console.error("Error saving the file:", err);
            return res.status(500).json({ success: false, message: "File upload failed" });
        }

        // Respond with success and the file path
        res.json({
            success: true,
            message: "File uploaded successfully",
            filePath: `/uploads/${fileName}`
        });
    });
});


app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});
export default app;

If you want to handle image files with large sizes, you can consider resizing the image, changing the resolution, or using a different format like webp.

Reptile answered 12/10, 2024 at 5:9 Comment(0)
J
0

make sure you send a formData instead of json. the multer is working fine here is the example.

formData.append("image", {
            uri: image?.uri,
            name: image?.fileName,
            type: image?.mimeType,
        } as any);

const response = await fetch(
            "your server url",
            {
                method: "POST",
                headers: {
                    "Content-Type": "multipart/form-data",
                },
                body: formData,
            }
        );

this is the middleware and controller where I get the image.

// middlleware
import multer from "multer";

const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, "/tmp");
    },
    filename: function (req, file, cb) {
        cb(null, file.originalname);
    },
});
const upload = multer({ storage });
app.use("/api/v1/effects", upload.single("image"), effectsRouter);

// to get the image
const image: Express.Multer.File | undefined = req.file;
Jere answered 13/10, 2024 at 14:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.