Image upload using React & Laravel 8

In this post I talk about using Laravel as an API server which means there will be no mentioning of its blade templating system.

Image upload using React & Laravel 8

In this post I talk about using Laravel as an API server which means there will be no mentioning of its blade templating system.

In many cases you will be sending some data to your back-end infrastructure (Laravel), from some client-side framework, lib or plain JavaScript. Your users want to upload some documents or images then save it somewhere in the cloud or on a remote server somewhere. Now, how to do that? Let's take a look at a JavaScript implementation to get the image data needed:

// shared.js

/**
 * Extracts the base64 data from a blob.
 * 
 * @param {file} Blob  
 * @returns Promise
 */
export const getBase64Data = async (file) => {
    let reader = new FileReader();
    reader.readAsDataURL(file);

    return new Promise((reslove, reject) => {
        reader.onload = () => reslove(reader.result);
        reader.onerror = (error) => reject(error);
    });
};

Not limited to images, the above code snippet is a JavaScript function that extracts the base64 data from a file (Blob) by returning a promise. This promise returns the extracted data or an error. We'll see how to use this next.

Sending data to Laravel

I'll be using React with React Dropzone to allow users to upload their images or documents then send it to our back-end. In side a .js file:


import React from 'react';
import axios from 'axios';
import { getBase64Data } from './shared.js';

const [files, setFiles] = React.useState([]);
const [hasError, setHasError] = React.useState(false);

/**
 * POST /some-url
 *
 * Sends data to endpoint.
 * 
 * @returns {void}
 */
const handleUploadImages = async () => {
    
    // Data you want to send to your backend
    let arrayObj = [];

    try {
        
        // Assuming we can upload multiple images.
        for (const file of files) {
            arrayObj.push({
                fileName: file.name,
                fileSize: file.size,
                fileType: file.type,
                data: await getBase64Data(file)
            });
    	}
        
        const { data } = await axios.post('some-url', { images: arrayObj });
        
        // An array of urls
        console.log(data);
   
    } catch (error) {
        // Handle error gracefully
        // console.log(error);
        setHasError(true);
    }

};

If we were to use a basic example from React Dropzone, we could add another functionality that sends a POST request to our endpoint with our data (images). Dropzone is not needed as you could be following this tutorial using plain JavaScript or another client library.

getBase64Data function returns a Promise hence we iterate with a for loop instead of a forEach. Should something goes wrong, the loop will break then we could handle any error gracefully.

Amazon S3

Next, we need to configure Laravel Filesystem to use S3. Once you're finished reading, head back here to continue the setup process.

Laravel store function.

We won't go into much details about routing, security, validation etc, we'll focus solely on the code that gets the job done. Here's a class controller using PHP:  ImageUploadController

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class ImageUploadController extends Controller
{
	// A constructor will not be used in this example.
    
    public function store(Request $request)
    {
        $fileName = $request["fileName"];
        $fileType = $request["fileType"];
        $fileSize = $request["fileSize"];
        $images = $request["images"]; 

        try {
            $urls = $this->uploadImage($images);

            return response()->json($urls, 200);
        } catch (\RunTimeException $e) {
            // $error = $e->getMessage();
            $error = 'Tell the user what went wrong';
            return response()->json(['error' => $error], 500);
        }
    }

    private function uploadImage($images): array
    {
        // Name this whatever you like but generally
        // give the folder path a name of the type of
        // document(s) you're uploading, ie:
        // avatars, photos, documents...
        $folderPath = "photos/";
        
        // Some current user's ID
        // Or $request->user()->id (depending on your setup)
        $userId = 1;

        // Data you'd like to return to the client
        $urls = [];

        foreach ($images as $image) {
            $key = $folderPath . $userId . '/' . $image["name"];

            $url = 'https://' . env('AWS_BUCKET') . '.s3-' . env('AWS_DEFAULT_REGION') . '.amazonaws.com/' . $key;

            $imageParts = explode(";base64,", $image["data"]);
            $base64 = base64_decode($imageParts[1]);
			$theWorldCanViewThesePhotos = 'public';

            Storage::disk('s3')->put($key, $base64, $theWorldCanViewThesePhotos);
			
            // Some function to save the url
            // against the user.
            
            array_push($urls, $url);
        }

        return $urls;
    }
}

It seem to be a convention to use the store function name to save objects or items to a database using Laravel. You could rename this function to anything you like. The private function uploadImage describes what it actually does: upload.

This above code snippet uses environment variables. After reading about Laravel Filesystem, update your existing .env with:

AWS_ACCESS_KEY_ID=***
AWS_SECRET_ACCESS_KEY=***
AWS_DEFAULT_REGION=***
AWS_BUCKET=***

Replace *** with the actual values for each key. Making a POST request to our api, you should get back a response with the S3 url(s) or an error response if there was an error uploading the images.

This tutorial is just a simple way of storing data in the cloud. I'd expect you to implement some validation and deletion of objects stored. The $key is very important when you want to delete the uploaded image or document. It's as simple as:

<?php

// In a function
$key = "GET_THE_KEY";
Storage::disk('s3')->delete($key);

For sensitive data, you would remove $theWorldCanViewThesePhotos or set its value to private then restrict S3's permissions to be accessible via a specified domain etc. That's a topic for itself. For now, you've just learnt how to upload an image, well... an array of images, to S3 using Laravel. ✌🏼

Credits

Post image by Aaron Burden.