DEV Community

Ranjeet Karki
Ranjeet Karki

Posted on • Updated on

Resize image and upload to amazon S3 bucket with Laravel and Vue

amazon s3

In this article, I will teach you how to resize and upload image to amazon s3 bucket with laravel and vuejs.

First we need to install two packages in our Laravel vuejs project
Flysystem S3 package : composer require league/flysystem-aws-s3-v3 "^3.0"
Intervention Image : composer require intervention/image

The Flysystem S3 package provides an S3 adapter for Flysystem. It easy to store and retrieve files from S3, and to perform operations like moving, copying, and deleting files with this package.
We are using Intervention Image library to resize the image.

Step1: let's create a bucket on amazon s3.

Search s3 and click the create button

s3 amazon

Give a bucket name, I have given profile-image-uploader as a bucket name. You can leave AWS Region and Object Ownership as default as show in below image

s3 amazon

Uncheck Block all public access and acknowledge it by checking the checkbox

s3 amazon

Enable bucket versioning and click create button. Versioning-enabled buckets can help you recover objects from accidental deletion or overwrite. For example, if you delete an object, Amazon S3 inserts a delete marker instead of removing the object permanently. Read here

amazon s3

Finally, your bucket is created. Now, go to your bucket and create a new folder where you want to store your all images.

S3 amazon

I will give folder name as images

amazon s3
Now, click the create button

amazon s3

Step2: Now, we will attach the policy to the images folder of our bucket profile-image-uploader.
To create the policy, go to AWS Policy Generator
and follow below steps:

  1. Choose s3 Bucket Policy
  2. Choose allow for effect
  3. Put * for Principal
  4. Choose getObject from Actions dropdown

5.For Amazon Resource Name (ARN), use this format

arn:aws:s3:::${BucketName}/${KeyName}
my bucket name is profile-image-uploader and keyName is folder name which is images, So my ARN will be like this
arn:aws:s3:::profile-image-uploader/images/*
use /* after the folder name which means everything inside the folder images

amazon s3

amazon s3

amazon s3

Finally, click generate policy button to get the JSON code, copy this JSON code as we need this.

amazon s3

Now, go to your bucket, click permissions tab, scroll down to the bucket policy. Here click edit, paste the JSON code and finally click the save button.

amazon s3

amazon s3

Step3: Now in this part we will create a user and get thekeys

First search IAM, click IAM to go Identity and Access Management (IAM) dashboard.

Iam amazon s3
On the left side, you will see the Access management, click the users from the list.

Give the username, click the radio button I want to create an IAM user, and click the next button to go next page.

amazon s3

Now, on the next page set the permission. First, choose the third option Attach policies directly. Search for AmazonS3FullAccess and check it as shown below in the image.

amazon s3

amazon s3
amazon s3

Image description

Click the user and go to Security Credentails tab

amazon s3
Scroll down and click the access key

amazon s3

On the next page do as shown in the image below and click the next button.
amazon s3

Now copy your access key and Secret access key and save it somewhere as we need this.

amazon s3

That's all with S3 bucket. Now it's time to make an API with Laravel.

First, we will make a column name profile_picture in the users table. Open the users migration file and add the string column profile_picture as shown below in the image.

Laravel Image upload

Now, run the migration with this command from your project directory php artisan migrate
Open you .env file and set the key values like this

AWS_ACCESS_KEY_ID=************
AWS_SECRET_ACCESS_KEY=***************
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=profile-image-uploader
AWS_USE_PATH_STYLE_ENDPOINT=false
Enter fullscreen mode Exit fullscreen mode

You should have AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY while creating the user.

If you open filesystems.php file from config folder you will see s3 setup made by laravel.

s3

Open you web.php and create two routes as below

Route::get('upload',[UserController::class, 'create']);

Route::post('upload/to/s3', [UserController::class, 'uploadImageToS3']);
Enter fullscreen mode Exit fullscreen mode

The first route will be responsible for rendering a view upload.blade.php and the second will be responsible for processing a file. Now, I will make a upload.blade.php and load a vue js component Upload.vue in our view upload.blade.php

    public function index()
    {
        return view('upload'); 
    }
Enter fullscreen mode Exit fullscreen mode

This will be my Upload vue js component
Upload.vue

<template>
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-body">
                    <div class="image-container">
                        <input type="file" ref="myImage" id="image-upload" class="form-control"
                            @change="updateProfilePicture()">
                        <p v-if="status">Uploading...<br>
                        <div class="progress">
                            <div class="progress-bar" role="progressbar" :style="{ width: `${progress}%` }"
                                :aria-valuenow="progress" aria-valuemin="0" aria-valuemax="100">
                                {{ progress }}%
                            </div>
                        </div>
                        </p>
                        <br>
                        <p id="errorUploadingImage"></p>
                        <img class="mx-auto d-block" :src="profileImage">
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue'
const progress = ref('')
const status = ref(false)
const profileImage = ref('')
const myImage = ref(null)

const updateProfilePicture = () => {}

</script>
Enter fullscreen mode Exit fullscreen mode

This is our vue upload component
Vue js upload

This is a Vue template and we have a card with an input field for uploading an image, a progress bar to indicate the status of the upload, and an image container to display the uploaded image. The template uses the v-if directive to show the progress bar and the "Uploading..." message only if status is true. status is a reactive variable defined in the script section of the component. The input field for uploading an image is defined using the input HTML element with a type of "file" and a reference to the element using ref="myImage". The @change event listener is used to call the updateProfilePicture method when a file is selected for upload. The progress bar is defined using the Bootstrap progress-bar class with a dynamic width that is bound to the progress reactive variable. The image container is defined using the img HTML element with a src attribute that is bound to the profileImage reactive variable. profileImage is initially an empty string but is updated with the URL of the uploaded image once the upload is complete. Finally, there is an empty p element with an ID of errorUploadingImage that is used to display any errors that occur during the upload process.

Now, lets process image within a updateProfilePicture

<script setup>
import { ref } from 'vue'
const progress = ref('')
const status = ref(false)
const profileImage = ref('')
const myImage = ref(null)

const updateProfilePicture = () => {
    status.value = true
    const image = myImage.value.files[0];
    const formData = new FormData();
    formData.append('image', image)
    axios.post('/upload/to/s3', formData, {
        headers: {
            'Content-Type': 'multipart-formdata'
        },
        onUploadProgress: (progressEvent) => {
            progress.value = Math.round(
                (progressEvent.loaded * 100) / progressEvent.total
            );
        },
    }).then((response) => {
        status.value = false
        profileImage.value = response.data.url
    }).catch((error) => {
        document.getElementById('errorUploadingImage').innerText = 'Error in uploading image';
        console.log(error)
    })
}

</script
Enter fullscreen mode Exit fullscreen mode

Inside updateProfilePicture function, I have set the status variable to true, indicating that an upload is in progress. I then created a new FormData object and appends the selected image file to it with the key 'image'. This FormData object will be used to send the file to the server using a POST request. The code then uses Axios to send the POST request to the URL /profileImage/upload. The formData object is passed as the second parameter to the post() method. It also sets the 'Content-Type' header to 'multipart-formdata'. The onUploadProgress option is used to track the upload progress. It updates the progress variable, which is bound to the progress bar in the Vue template, with the percentage of the file that has been uploaded. If the request is successful, the function sets the status variable to false, indicating that the upload is complete, and sets the profileImage variable to the URL of the uploaded image returned in the response data. If there is an error, it displays an error message on the page and logs the error to the console.

Now, let's work on uploadImageToS3 method of UserController to upload the file to aws s3 bucket.

 public function uploadImageToS3(Request $request): JsonResponse
 {
        $request->validate([
            'image' => 'required|image|mimes:jpeg,png,jpg|max:2048',
        ]);

        if ($request->hasFile('image')) {
            //extracts the uploaded file
            $image = $request->file('image');
          //generates a unique filename for it using hashName()
            $filename = $image->hashName();

            // Resize the image to a maximum width of 150 pixels, this is form Intervention Image library
            $img = Image::make($image->getRealPath())->resize(150, null, function ($constraint) {
                $constraint->aspectRatio();
            });

            // Upload the resized image to S3
            $path = 'images/' . $filename;
            Storage::disk('s3')->put($path, $img->stream()->__toString());

            // Update the user's profile_picture with the S3 object URL
            User::where('id', auth()->id())->update([
                'profile_picture' =>  $awsPath = Storage::disk('s3')->url($path)
            ]);

            return response()->json(['url' => $awsPath], Response::HTTP_OK);

        }
  }

Enter fullscreen mode Exit fullscreen mode

Above I have written a comment for each line of code to make it easy for you to understand. The function returns a JsonResponse, make sure you use Illuminate\Http\JsonResponse; The function validates the image file uploaded with the request to ensure that it is an image file (jpeg, png, or jpg), is no larger than 2048KB, and is required for the request to be considered valid. After validating the image, we resize the image with Intervention Image Library and uploaded the resized image to the s3 bucket.$img->stream()->__toString() is used to get the raw image data as a string, which is then passed to the put() method of the S3 disk driver to upload the image. Finally, we will update the user profile_picture column with S3 Image URL which will be something like this
https://profile-image-uploader.s3.amazonaws.com/images/01WKdCYuBFSdQfwCENxz5eECU4jsW7xWxqfjrLUr.jpg

Thank you for reading.I hope you enjoyed it!

Top comments (0)