Upload and fetch images using Digital Ocean Spaces and Laravel 5

Before i start, I'd like to point out how pleasantly surprised I was at how easy Digital Ocean Spaces was to setup and get running. It took me no longer than 15 minutes, to create a new space, upload a file and then fetch the file. 

Compare this to Amazon S3. Where i found myself having to read several guides and watch several YouTube videos on how to setup IAM, buckets and other policies. It was a while ago, but it took a good portion of the day just to wrap my head around everything you needed to do just to get started.

That's not to say S3 is bad, far from it. It's very good infact. The level of control it gives you is unrivaled. However Digital Ocean's Spaces ticks all the boxes for me due to the following:

 With that in mind, let's continue on with the guide.

Creating your Space

From the Digital Ocean dashboard, scroll down and find the section entitled "Start using Spaces"

Create a new SpaceThis will take you to a section where you can fill out a few options.

Choose a datacenter region

Fairly obvious this one, choose the datacenter nearest to where your droplet is hosted. Since I'm in the UK, Amsterdam (AMS) was my choice.

CDN (Content Delivery Network)

If the files you are storing are going to be assets that you serve up via your website or app, it makes sense to enable this option. I won't go into the benefits of a CDN - feel free to look that up. However Digital Ocean claim that you can:

"Deliver web assets up to 70% faster with global edge caching technology."

70% is a big speed increase, do as you will. For this guide i left this option disabled.

Allow File Listing

This option lets you specify if all the files you upload are publically available to list or not. This is usually an option you want to disable. I don't want anyone to be able to list the files i have stored.

It's important to note, that this doesn't control the privacy of each individual file you upload. You can choose, for each file wether you want to make it public or not.

Choose a unique name

This is the equivilent to chosing a bucket on S3. This will be your unique subdomain for your space. Unfortunately, someone has already taken "assets" so you'll need to be more creative with your name.

A good option here is to use your domain name. E.g. assets-yourdomain. This will create a spaces URL which looks similar to this: https://assets-mydomain.ams3.digitaloceanspaces.com

Select a Project

This lets you assign a Space to a project. Choose a project from the list and click "Create a Space". This will create a new space for you, which you can find in your projects dashboard.

Creating API keys for your Space

Once you've created your Space, you'll need to generate some API keys to access it through your Laravel app. To do this, navigate to the left hand menu panel and under the Management category, select API.

Once on this page, select the "Tokens/Keys" sub menu and scroll down to the "Spaces Access Keys" section. Click on the "Generate new Key" button and note down your key and secret.

That's it for the Digital Ocean side, now let's move on to your Laravel application.

Uploading and fetching files in Laravel

In order to use Spaces in Laravel, we first need to fetch the S3 flysystem packed from composer. In your terminal issue the following command:

$ cd your-project
$ composer require league/flysystem-aws-s3-v3 ~1.0 

 

This will pull down the required files. Next step to update you config settings to use Digital Ocean Spaces.

Open up the filesystems file located in app/config/filesystems.php and scroll down to the "Filesystem Disks" section.

Under S3 copy and paste the following code:

'spaces' => [
    'driver' => 's3',
    'key' => env('DO_SPACES_KEY'),
    'secret' => env('DO_SPACES_SECRET'),
    'endpoint' => env('DO_SPACES_ENDPOINT'),
    'region' => env('DO_SPACES_REGION'),
    'bucket' => env('DO_SPACES_BUCKET'),
],

 

Make sure you add a comma to the end of the S3 array!

You'll notice that I'm referencing some environment variables here, the next step will be to add these to your .env file in the root of the project.

Add the following new config to the .env file - anywhere will do, I usually add new config to the bottom of the file.

DO_SPACES_KEY=YOURKEY
DO_SPACES_SECRET=YOURSECRET
DO_SPACES_ENDPOINT=https://ams3.digitaloceanspaces.com
DO_SPACES_REGION=ams3
DO_SPACES_BUCKET=assets-yourdomain

Fairly self explanitory, however the endpoint and region will be whatever you chose in the setup, in my case it was AMS3. the bucket is the unique subdomain you chose also, be careful to only use the subdomain and not the whole URL spaces gives you!

Save both files and we'll move on to uploading a file next!

Uploading a file to Digital Ocean Spaces

In this example we'll create a very basic page which will display a form to upload a single image, a controller to handle the file upload, a model to save the filepath in a database and a page to view the images you have uploaded. There won't be any validation or sanitization, but these are things you should add in later if you are to use this in a production environment.

Creating the views, model and controller

in your terminal cd to your project and issue the following command:

$ php artisan make:model Image -a

 This will create an Image model, but will also create a controller, migration and factory file for you in one command!

Populate the migration file

The first step will be to populate the migration file. You can find this file in your database/migrations folder. Enter the following in the "up()" method:

Schema::create('tests', function (Blueprint $table) {
    $table->increments('id');
    $table->string('title');
    $table->string('filename');
    $table->timestamps();
});

We'll be storing a title and the file path of the uploaded image.

Setup the model

The next step, we'll add the fillable method to the Images model. You can find this file in app/Image.php. The fillable method allows you to specify which fields are mass assignable when you update or create a new record in Eloquent. Add the following code to your model class:

/**
* The attributes that are mass assignable.
*
* @var array
*/

protected $fillable = [
    'title', 'filename',
];

Run the migration

Before running the migration, make sure you've updated the database details in the .env file. Once you have done this, in your terminal run the following command:

$ php artisan migrate

this will create an images tables which we can use to store our image details.

Setup the controller

Next step is to write the logic to handle the file upload. Nearly done now - I promise!

Find the index and store methods and paste in the following code:


/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{       
    $images = Image::all();
    $paths = [];

    foreach($images as $img) {
        $paths[] = Storage::disk('spaces')->url($img);
    }

    return view('images', compact('paths));
}

/**
* Store a newly created resource in storage.
*
* @param  \Illuminate\Http\Request  $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{

    $storagePath = Storage::disk('spaces')->put($request->file('image'));

    if($storagePath) {

        $request->merge([
            'filename' => $request->file('image')->hashName()
        ]);

        $image = Image::create($request->all());

        return redirect('image-upload')->with('status', 'Image Uploaded!');

    }

    return redirect('image-upload')->with('status', 'Failed to upload image.');
}

Setup the Views

Create a new view file in your resources folder called images.blade.php and paste in the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>DigitalOcean Spaces File Upload</title>
</head>
<body>
    <div>

        @forelse ($paths as $path)
            <img src="{{ $path }}">
        @empty
            <p>No images</p>
        @endforelse

    </div>
    
    <form method="POST" action="/images">
        <input type="file" name="image">
        <button type="submit">Upload Image</button>
    </form>

    @if (session('status'))
        <div class="alert alert-success">
            {{ session('status') }}
        </div>
    @endif
</body>
</html>

And there you have it. A very basic setup which allows you to upload and display images using DigitalOcean Spaces!


More Posts

Laravel Queue Delays Not Working

Using Laravel 5.7 queues along with Redis I found that whilst the events were being dispatched and handled by the...