I assume you will integrate the image upload into your existing Laravel 8 application, if you are just getting started then I do recommend following the Laravel installation guide from the documentation. With that out of the way, let's start by creating the model and migration.
Step 1: Create Model and Migration
First, let's make a model with its corresponding migration file to store the path of the uploaded image. Run the command below to generate the model and migration.
php artisan make:model ImageUpload -m
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateImageUploadsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('image_uploads', function (Blueprint $table) { $table->id(); $table->string('image_url'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('image_uploads'); } }
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Factories\HasFactory; class ImageUpload extends Model { use HasFactory; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'image_url' ]; }
Step 2: Define Route
The route will be the path where the user can access the page, for this tutorial the path will be called "/image-upload" and it accepts both "get" and "post" method.
/* allow the route to accept 'get' and 'post' request */ Route::match(['get', 'post'], '/image-upload', function () { /* upload code goes here later */ return view('image-upload'); }) ->name('image-upload');;
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel Example') }}</title> <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"> </head> <body class="flex items-center justify-center w-full h-screen"> <div class="flex flex-col items-center justify-center max-w-xs"> <form action="{{ route('image-upload') }}" method="post" enctype="multipart/form-data" class="w-full bg-gray-100 rounded-lg h-32 p-4" > @csrf <input type="file" name="image-upload" > @error('image-upload') <p class="text-red-500"> {{ $message }} </p> @enderror <button type="submit" class="mt-4 px-4 py-2 rounded bg-green-200 font-medium text-green-700" > Upload Image </button> </form> <div class="mt-4 w-full"> @foreach($images as $image) <img src="{{ \Illuminate\Support\Facades\Storage::disk('public')->url($image->image_url) }}" class="w-full h-auto" alt="Image uploads" > @endforeach </div> </div> </body> </html>
@error('image-upload') <p class="text-red-500"> {{ $message }} </p> @enderror
<div class="mt-4 w-full"> @foreach($images as $image) <img src="{{ \Illuminate\Support\Facades\Storage::disk('public')->url($image->image_url) }}" class="w-full h-auto" alt="Image uploads" > @endforeach </div>
Step 3: Preview of the Image Upload Page
With all of the TailwindCSS classes, the image upload page design will be like below. Although it looks simple, it serve the purpose of this example.

Step 4: Save Image and Validation Logic
Now let's revisit the route and update the code to save the image. The full code will look like below.
use App\Models\ImageUpload; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; /* allow the route to accept 'get' and 'post' request */ Route::match(['get', 'post'], '/image-upload', function (Request $request) { /* check if there's file upload */ if ($request->hasFile('image-upload')) { /* validate the request to check if there's any image upload */ $validatedData = $request->validate([ 'image-upload' => 'required|image|mimes:jpeg,png,jpg|max:1024', ]); /* store the image in the 'uploads' directory */ $path = $validatedData['image-upload']->store('uploads'); /* save the image path into the db */ ImageUpload::create(['image_url' => $path]); } /* retrieve existing uploaded images*/ $images = ImageUpload::all(); /* return the view to front-end and attach any existing images if available */ return view('image-upload', compact('images')); })->name('image-upload');
/* check if there's file upload */ if ($request->hasFile('image-upload')) { /* validate the request to check if there's any image upload */ $validatedData = $request->validate([ 'image-upload' => 'required|image|mimes:jpeg,png,jpg|max:1024', ]); /* store the image in the 'uploads' directory */ $path = $validatedData['image-upload']->store('uploads'); /* save the image path into the db */ ImageUpload::create(['image_url' => $path]); }
To store the image directly call the "store" method from the validated input. and then finally if it's successfully saved to the storage, save the image URL path to the database.
/* store the image in the 'uploads' directory */ $path = $validatedData['image-upload']->store('uploads'); /* save the image path into the db */ ImageUpload::create(['image_url' => $path]);
/* retrieve existing uploaded images*/ $images = ImageUpload::all();
Step 5: Environment Variable and Storage Linking
Do note that you have to update the .env file to set the filesystem driver to "public".
FILESYSTEM_DRIVER=public
php artisan storage:link
Step 6: Image Successfully Uploaded
Below is the preview upon successfully uploading an image.

On the second attempt if you are uploading different files that don't pass the validation, then it will look like below.
