Building a Simple File Upload API with Laravel 9 and AWS S3
Tags: aws, laravel, beginners, api
Published: 2023-03-31 13:41:26


Uploading and storing files securely and efficiently is essential for many modern web applications. Whether it's uploading images, documents, or other types of files, having a reliable file upload system is crucial for the success of any online business or application.

This article will explore how to build a simple file upload API using Laravel 9 and Amazon Web Services (AWS) S3. Laravel is a popular PHP web framework that provides a rich set of features for building web applications. AWS S3 is a cloud storage service that offers high scalability and reliability, making it an ideal solution for storing and retrieving files of any size.

We will explore the process of setting up a new Laravel project, creating an API endpoint for file uploads, and configuring AWS S3 to store and retrieve the files. By the end of this article, readers will have a fully functional file upload API that they can integrate into their web application or use as a standalone service.  So, let's get started!


## **Prerequisites**

Before diving into building this API, there are a few things we need to have in place:


- Basic knowledge of PHP and the Laravel framework.
- Composer: Composer is the dependency manager for the PHP programming language that provides a standard format for managing dependencies of PHP software and required libraries. We’ll use it to install some packages in the project directory. Download it [here](https://getcomposer.org/download/).
- AWS Account: We need an AWS account to use AWS S3. Open a free account [here](https://portal.aws.amazon.com/billing/signup#/start/email). (Payment details might be required)
- Understanding of HTTP clients like Postman.

## Installation and Setup
### Creating an IAM User

AWS Identity and Access Management (IAM) is a service that enables users to manage access to AWS resources securely. IAM enables users to create and manage AWS users and groups, and to assign permissions to these entities to access AWS resources.

When a new AWS account is created, an initial IAM user is automatically generated. This user has full access to all AWS services and resources and can be used to manage the account and its associated resources. However, it is not recommended to utilize the root user for day-to-day tasks due to security reasons. It is advisable to create additional IAM users with minimal privilege access to carry out routine activities. This is considered a best practice for maintaining the security of AWS resources.

Follow these steps in the AWS IAM console to create a new user with access only to the S3 service:


1. Go to the IAM console and select "Add User".
2. Enter the desired username and select "Attach policy directly" in the "Permissions" tab.
3. Search for "S3" and select the "AmazonS3FullAccess" policy.
4. Click on "Create User".
5. Make sure to save the user's access key ID and secret access key, as the secret key can only be viewed once.


### Creating a New Laravel Project

Let’s start by creating a new Laravel project. To do that, run the following command in the terminal.


    composer create-project laravel/laravel file-upload-api 9.5

By executing this command, we are using Composer to create a new Laravel 9.5 project called **file-upload-api** into the specified directory.

Next, we check our project is running correctly by starting our server.

Windows users should run the below in their terminal.


    cd file_upload_api
    php artisan serve

 Mac users should run the code below.


    cd file_upload_api
    valet park

Laravel Valet is a development environment for macOS that always configures Mac to run Nginx in the background. For a comprehensive guide on setting up Laravel Valet, click [here](https://laravel.com/docs/9.x/valet#serving-sites).

### Updating the Env File

Next, we’ll open the **.env file** and update it with the credentials gotten earlier.

```.env
    AWS_ACCESS_KEY_ID= 
    AWS_SECRET_ACCESS_KEY=
    AWS_DEFAULT_REGION=<region_name> // us-east-1 is default
    AWS_BUCKET=<bucket_name> //specify the bucket name here 
    AWS_USE_PATH_STYLE_ENDPOINT=false
```

Note: Use this [link](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html) to ensure the right bucket name format is being used.

### Installing the AWS SDK

The AWS SDK (Software Development Kit) is a collection of tools, libraries, and documentation provided by Amazon Web Services to make it easier for developers to build applications on top of AWS services. It abstracts away the low-level details of interacting with AWS services. It provides a more developer-friendly interface, allowing developers to focus on their application logic rather than infrastructure management.

To install the AWS SDK, run the following command:


    composer require aws/aws-sdk-php

This will install the necessary packages for using AWS S3 with PHP.


## Setting up the Controller

Now we’re all set. Let’s now create a new controller by running the command below.


    php artisan make:controller uploadController

This command creates a controller named **uploadController** in the **app/Http/Controllers** directory.


Open the **uploadController** file and paste the following code at the top of the file.

```php
    use Aws\S3\S3Client; 
    use Aws\S3\Exception\S3Exception;
```

The above code snippet imports the **S3Client** and **S3Exception** from the previously installed AWS SDK. We will use **S3Client** to instantiate a new **S3Client** class, while **S3Exception** will handle any exceptions that may occur in our application. 

Now, having imported all the needed classes, let’s dive into the primary implementation. We will paste the following code into the **uploadController** class.
```php

    public function UploadFile(Request $request){
        $request->validate([
            'file' => 'required|mimes:pdf,docx,doc,pptx,ppt,xls,xlsx,jpg|max:2048'
        ]);
    
        // file handling code here
    }
```

We create a function called uploadFile that contains the entire application logic. Then, we check the file's existence, format, and size using the `validate` method.


```php
    $file = $request->file('file');
            
            //create s3 client
            $s3 = new S3Client([
                'version' => 'latest',
                'region'  => env('AWS_DEFAULT_REGION'),
                'credentials' => [
                    'key'    => env('AWS_ACCESS_KEY_ID'),
                    'secret' => env('AWS_SECRET_ACCESS_KEY'),
                ]
            ]);
```

Next, we retrieve the uploaded file using `$request->file('file')`. We will then use the **S3Client** class to create a new S3 client object using the credentials we specified in our **.env** folder earlier. 

Note: It’s good practice to store sensitive details in the **.env** file in order to protect our site from bad actors.

```php
    $keyname = 'uploads/' . $file->getClientOriginalName();

```

We are defining the key name for the uploaded file by using a folder named "uploads" and the original filename of the uploaded file. To get the original name of the file uploaded, we use the **getClientOriginalName()** method.

```php

    if (!$s3->doesBucketExist(env('AWS_BUCKET'))) {
                // Create bucket if it doesn't exist
                try{
                    $s3->createBucket([
                        'Bucket' => env('AWS_BUCKET'),
                    ]);
                } catch (S3Exception $e) {
                    return response()->json([
                        'Bucket Creation Failed' => $e->getMessage()
                    ]);
                }
            }
```

In the snippet above, we first check if the bucket exists using the **doesBucketExist()** method. If it does not exist, we try to create it using the **createBucket()** method, an error is thrown if any exception occurs during the bucket creation process. 
If the bucket already exists, we skip the creation step and move on to the file upload. By adding this check, we ensure that we don't attempt to create a bucket that already exists, which could cause errors or unnecessary API calls.

```php
    try {
                $result = $s3->putObject([
                    'Bucket' => env('AWS_BUCKET'),
                    'Key'    => $keyname,
                    'Body'   => fopen($file, 'r'),
                    'ACL'    => 'public-read'
                ]);
```

Finally, we try to upload the file to S3 using the **putObject()** method. We pass in the S3 bucket name, key name, file body, and permissions. In this case, we are setting the file to be publicly readable.

```php
    return response()->json([
        'message' => 'File uploaded successfully',
        'file link' => $result['ObjectURL']
    ]);
```
    

If the upload is successful, we return a response with the message "File uploaded successfully" and a link to the uploaded file in the S3 bucket.

```php
    } catch (S3Exception $e) {
        return response()->json([
            'Upload Failed' => $e->getMessage()
        ]);
    }
```
    

If the upload fails, we catch the **S3Exception** and return a response with the error message.

Here is the complete code.


```php
    <?php
    namespace App\Http\Controllers;
    use Aws\S3\S3Client;  
    use Illuminate\Http\Request;
    use Aws\S3\Exception\S3Exception;
    use Illuminate\Support\Facades\Storage;
    class UploadController extends Controller
    {
        public function UploadFile(Request $request){
            
            $request->validate([
                'file' => 'required|mimes:pdf,docx,doc,pptx,ppt,xls,xlsx,jpg|max:2048'
            ]);
            $file = $request->file('file');
            
            //create s3 client
            $s3 = new S3Client([
                'version' => 'latest',
                'region'  => env('AWS_DEFAULT_REGION'),
                'credentials' => [
                    'key'    => env('AWS_ACCESS_KEY_ID'),
                    'secret' => env('AWS_SECRET_ACCESS_KEY'),
                ]
            ]);
            $keyname = 'uploads/' . $file->getClientOriginalName();
            //create bucket
            if (!$s3->doesBucketExist(env('AWS_BUCKET'))) {
                // Create bucket if it doesn't exist
                try{
                    $s3->createBucket([
                        'Bucket' => env('AWS_BUCKET'),
                    ]);
                } catch (S3Exception $e) {
                    return response()->json([
                        'Bucket Creation Failed' => $e->getMessage()
                    ]);
                }
            }
            //upload file
            try {
                $result = $s3->putObject([
                    'Bucket' => env('AWS_BUCKET'),
                    'Key'    => $keyname,
                    'Body'   => fopen($file, 'r'),
                    'ACL'    => 'public-read'
                ]);
                // Print the URL to the object.
                return response()->json([
                    'message' => 'File uploaded successfully',
                    'file link' => $result['ObjectURL']
                ]);
            } catch (S3Exception $e) {
                return response()->json([
                    'Upload Failed' => $e->getMessage()
                ]);
            }
        }
    }
    
   ``` 


## Setting up the Route

Open the **routes/api.php** file to create the route that will be used in our application.

```php
    use App\Http\Controllers\UploadController;
    
    Route::post('/upload', [UploadController::class, 'uploadFile']);
```

## Testing the API

To test our API, we can utilize any HTTP client that we prefer. However, in this article, Postman will be used to demonstrate the process.

Navigate to the endpoint, which is:

- [http://127.0.0.1:8001/api/](http://127.0.0.1:8001/api/register)upload for Windows users.
- [http://upload-pdf-api.test/api/upload](http://upload-pdf-api.test/api/upload) for Mac users.


1. Click on “POST”
2. Go to the “Body” tab, and then select “form-data.”
3. In the “key” field, type “file” and change the input type to “file.”
4. In the “value” field, attach the file to be uploaded.
5. Click the “Send” button.

If everything works fine, the below response will be displayed.


![Postman Response](https://paper-attachments.dropboxusercontent.com/s_F16528ED3C781AE9E6C60BA20B1D52882CAA174009CEB2AF1244B7175CCDB616_1677962472585_Screen+Shot+2023-03-04+at+9.41.01+PM.png)



The uploaded file can be downloaded by clicking on the link provided in the response.

## Conclusion

In this tutorial, we covered the basics of creating an AWS S3 bucket, creating a Laravel API endpoint for file uploads, uploading files to S3 using the AWS SDK and testing the API using Postman. Additionally, We explored how to create an IAM user on AWS and learned how to handle errors and exceptions when uploading files.

By following this tutorial, developers can create a file upload API that is both robust and scalable, making it ideal for use in production environments. Overall, this tutorial is an excellent starting point for anyone looking to create a file upload API with Laravel 9 and AWS S3.

## Resources

Here are some resources for further reading.


1. [Using S3 for File Storage in Laravel](https://www.honeybadger.io/blog/laravel-s3/)
2. [AWS PHP SDK Documentation](https://docs.aws.amazon.com/sdk-for-php/index.html)