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 usingIntervention Image
library to resize the image.
Step1: let's create a bucket on amazon s3.
Search s3
and click the create button
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
Uncheck Block all public access and acknowledge it by checking the checkbox
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
Finally, your bucket is created. Now, go to your bucket and create a new folder where you want to store your all images.
I will give folder name as images
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:
- Choose s3 Bucket Policy
- Choose allow for effect
- Put * for Principal
- 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
Finally, click generate policy button to get the JSON code, copy this JSON code as we need this.
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.
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.
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.
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.
Click the user and go to Security Credentails
tab
Scroll down and click the access key
On the next page do as shown in the image below and click the next button.
Now copy your access key
and Secret access key
and save it somewhere as we need this.
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.
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
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.
Open you web.php
and create two routes as below
Route::get('upload',[UserController::class, 'create']);
Route::post('upload/to/s3', [UserController::class, 'uploadImageToS3']);
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');
}
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>
This is our vue upload component
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 usingref="myImage"
. The@change
event listener is used to call theupdateProfilePicture
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 theURL
of the uploaded image once the upload is complete. Finally, there is an emptyp
element with anID
oferrorUploadingImage
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
Inside
updateProfilePicture
function, I have set the status variable to true, indicating that an upload is in progress. I then created anew 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'
. TheonUploadProgress
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);
}
}
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 theput()
method of theS3
disk driver to upload the image. Finally, we will update the userprofile_picture
column withS3
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)