skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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;
    

    The full source code is available on GitHub: https://github.com/ashenwgt/react-native-expo-nodejs-express-image-upload-demo

    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.


  2. 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;
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search