Upload and download from Laravel private storage

Upload and download from Laravel private storage

In this article, I will share with you, how you can upload and download files from Laravel private storage. First, you need to make sure your config/filesystems.php file look like this.

<?php

// config/filesystems.php

return [

    /*
    |--------------------------------------------------------------------------
    | Default Filesystem Disk
    |--------------------------------------------------------------------------
    |
    | Here you may specify the default filesystem disk that should be used
    | by the framework. The "local" disk, as well as a variety of cloud
    | based disks are available to your application for file storage.
    |
    */

    'default' => "local", // env('FILESYSTEM_DISK', 'local'),

    /*
    |--------------------------------------------------------------------------
    | Filesystem Disks
    |--------------------------------------------------------------------------
    |
    | Below you may configure as many filesystem disks as necessary, and you
    | may even configure multiple disks for the same driver. Examples for
    | most supported storage drivers are configured here for reference.
    |
    | Supported Drivers: "local", "ftp", "sftp", "s3"
    |
    */

    'disks' => [

        'local' => [
            'driver' => 'local',
            'root' => storage_path('app'),
            'throw' => false,
        ],

       // ... other disks

    ],

    /*
    |--------------------------------------------------------------------------
    | Symbolic Links
    |--------------------------------------------------------------------------
    |
    | Here you may configure the symbolic links that will be created when the
    | `storage:link` Artisan command is executed. The array keys should be
    | the locations of the links and the values should be their targets.
    |
    */

    'links' => [
        public_path('storage') => storage_path('app/public'),
    ],

];

Show the form

First I need to show the form with an input field to select the file and a submit button. I will create a web route that when accessed will call a function in controller.

// routes/web.php

Route::get("/private-storage", [UserController::class, "private_storage"]);

Then in your controller class, create a function that will render a blade template.

// app\Http\Controllers\UserController.php

class UserController extends Controller
{
    public function private_storage()
    {
        return view("private-storage");
    }
}

After that, we need to create this view file and create a form in it.

// resources/views/private-storage.blade.php

<form onsubmit="doUpload(event);">
    {{ csrf_field() }}
    <input type="file" name="file" required />

    <input type="submit" name="submit" value="Upload" />
</form>

<script>
    function doUpload() {
        event.preventDefault();

        const form = event.target;
        form.submit.setAttribute("disabled", "disabled");

        const ajax = new XMLHttpRequest();
        ajax.open("POST", "/upload-private", true);

        ajax.onreadystatechange = function () {
            if (this.readyState == 4 && this.status == 200) {
                form.submit.removeAttribute("disabled");
                console.log(this.responseText);
            }
        }

        const formData = new FormData(form);
        ajax.send(formData);
    }
</script>

This calls an AJAX and pass the FormData to it. Now we need to create a route that will handle this request.

Upload the file in Laravel private storage

First, I will create a POST route that will handle this request.

// routes/web.php

Route::post("/upload-private", [UserController::class, "upload_private"]);

Then in UserController we need to create this function.

// app\Http\Controllers\UserController.php

public function upload_private()
{
    $file = request()->file("file");

    $file_path = "files/" . uniqid() . "." . $file->getClientOriginalExtension();
    $file->storeAs("/private", $file_path);

    return response()->json([
        "status" => "success",
        "message" => "File has been uploaded."
    ]);
}

If you test the app now, you will be able to upload the file. You can see that file will be saved in your app/storage/private/files/{file_name}.jpg path.

Save the paths in database

We need to save the paths of uploaded files in database to keep the reference of them. So I will first create a migration for table named “files”.

// CMD

php artisan make:migration create_files_table

Then in your migration file, create the following columns:

  • name: Name of file.
  • path: Path of file.
  • type: Either public or private
<?php

// database/migrations/{date_time}_create_files_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('files', function (Blueprint $table) {
            $table->id();

            $table->text("name")->nullable();
            $table->text("path")->nullable();
            $table->enum("type", ["public", "private"])->default("public");

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('files');
    }
};

Then finally run the migration:

// CMD

php artisan migrate

Now change the UserController@upload_private() function to save the file name and path in database after saving it in private storage.

public function upload_private()
{
    $file = request()->file("file");

    $file_path = "files/" . uniqid() . "." . $file->getClientOriginalExtension();
    $file->storeAs("/private", $file_path);

    DB::table("files")
        ->insertGetId([
            "name" => $file->getClientOriginalName(),
            "path" => $file_path,
            "type" => "private",
            "created_at" => now()->utc(),
            "updated_at" => now()->utc()
        ]);

    return response()->json([
        "status" => "success",
        "message" => "File has been uploaded."
    ]);
}

Try uploading another file and you will see that a new row should be inserted in “files” table.

View the files

In order to download the files, first we need to view them on browser. In this tutorial, I am using Laravel query builder directly in the controller to fetch the data, but you can use Laravel models for re-usability. So change your UserController@private_storage() function to fetch all the uploaded private files from database.

public function private_storage()
{
    $files = DB::table("files")
        ->where("type", "=", "private")
        ->orderBy("id", "desc")
        ->paginate();

    return view("private-storage", [
        "files" => $files
    ]);
}

Then in your blade template resources/views/private-storage.blade.php, you need to loop through all the files and show their name. The name should be a hyperlink which when clicked, should open a new URL in another tab and download the file.

@foreach ($files as $file)
    <p>
        <a href="{{ url('/download-private/' . $file->id) }}" target="_blank">{{ $file->name }}</a>
    </p>

    <hr />
@endforeach

If you refresh the page now, you will start seeing all your uploaded files names.

Download file from Laravel private storage

We need to create a GET route that will handle the download request.

// routes/web.php

Route::get("/download-private/{id}", [UserController::class, "download_private"]);

Then I will create a new function UserController to download the file to the user’s local system.

public function download_private()
{
    $id = request()->id ?? 0;

    $file = DB::table("files")
        ->where("id", "=", $id)
        ->where("type", "=", "private")
        ->first();

    if ($file == null)
    {
        abort(404);
    }

    if ($file->path && Storage::exists("private/" . $file->path))
    {
        return Storage::download("private/" . $file->path);
    }

    abort(404);
}

You can also get authenticated user in it and check if user is authorized to access this file or not. So that’s it, that’s how you can upload and download files from Laravel private storage. Private files does not have a direct URL so they can’t be accessed directly. Thus making them secure as compared to public storage files which are publicly available for everyone.

If you need any help in integrating this in your project, feel free to contact me.