In this section we will see how to use upload image in laravel 9 with Spatie Media Library. For this tutorial we use laravel breeze and tailwind css .
Step 1: Install Laravel & Connect Database
Run below command to create laravel project.
composer create-project laravel/laravel project-name
Now, you have to connect the laravel app to the database, hence open the .env configuration file and add the database credentials as suggested below.
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=database_name
DB_USERNAME=database_user_name
DB_PASSWORD=database_password
Step 2: Install Breeze
Install laravel breeze via composer:
composer require laravel/breeze --dev
Next, run below command.
php artisan breeze:install
And final install Dependencies
npm install && npm run dev
php artisan migrate
Step 3: Create Blog Modal Migration and Controller
Run below command to create model, migration and controller
php artisan make:model Blog -mcr
create_blogs_table.php
public function up()
{
Schema::create('blogs', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->timestamps();
});
}
app/Models/Blog.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Blog extends Model
{
use HasFactory;
protected $fillable = [
'title',
'content'
];
}
web.php
Route::resource('blogs', BlogController::class);
Step 4: install media library laravel
Run below command to install media library.
composer require "spatie/laravel-medialibrary:^10.0.0"
Next, You need to publish the migration to create the media table:
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="migrations"
After that, you need to run migrations.
php artisan migrate:fresh
Step 5: Set Up laravel-medialibrary
Now, you need to add few class laravel-medialibrary ( HasMedia, InteractsWithMedia)
app/Models/Blog.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Blog extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia;
protected $fillable = [
'title',
'content'
];
}
Now , you need to link storage, so type the command in the terminal and run the command.
php artisan storage:link
Notice: we need to set url path like 8000, or your current server
APP_URL=http://localhost
to
APP_URL=http://localhost:8000
in production you need to your Domain otherwise you are not seen image
Step 6: Perform Blog CRUD With image Upload Using Spatie Media Library
Add blog crud logic with spatie image upload in controller
app/Http/Controllers/BlogController.php
<?php
namespace App\Http\Controllers;
use App\Models\Blog;
use Illuminate\Http\Request;
class BlogController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$blogs = Blog::all();
return view('blogs.index', compact('blogs'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('blogs.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$data = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required',
'image' => 'nullable|image|mimes:jpg,png,jpeg,gif,svg|max:2048'
]);
$blog = Blog::create($data);
if ($request->hasFile('image')) {
$blog->addMediaFromRequest('image')->toMediaCollection('images');
}
return redirect()->route('blogs.index');
}
/**
* Display the specified resource.
*
* @param \App\Models\Blog $blog
* @return \Illuminate\Http\Response
*/
public function show(Blog $blog)
{
return view('blogs.show', compact('blog'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Blog $blog
* @return \Illuminate\Http\Response
*/
public function edit(Blog $blog)
{
return view('blogs.edit', compact('blog'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Blog $blog
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Blog $blog)
{
$data = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required',
'image' => 'nullable|image|mimes:jpg,png,jpeg,gif,svg|max:2048'
]);
$blog->update($data);
if ($request->hasFile('image')) {
$blog->clearMediaCollection('images');
$blog->addMediaFromRequest('image')->toMediaCollection('images');
}
return redirect()->route('blogs.index');
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Blog $blog
* @return \Illuminate\Http\Response
*/
public function destroy(Blog $blog)
{
$blog->delete();
return redirect()->route('blogs.index');
}
}
Step 7: Create Blade View File
view/blogs/create.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Blogs') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<h1 class="text-2xl text-center">Laravel 9 Blog CRUD With image Upload Using Spatie Media Library</h1>
<div class="mt-1 mb-4">
<a class="px-2 py-2 text-sm text-white bg-blue-600 rounded"
href="{{ route('blogs.create') }}">{{ __('Add Blog') }}</a>
</div>
<div class="relative overflow-x-auto shadow-md sm:rounded-lg">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<thead
class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
#
</th>
<th scope="col" class="px-6 py-3">
Title
</th>
<th scope="col" class="px-6 py-3">
Image
</th>
<th scope="col" class="px-6 py-3">
Edit
</th>
<th scope="col" class="px-6 py-3">
Show
</th>
<th scope="col" class="px-6 py-3">
Delete
</th>
</tr>
</thead>
<tbody>
@foreach ($blogs as $blog)
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row"
class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap">
{{$blog->id}}
</th>
<td class="px-6 py-4">
{{$blog->title}}
</td>
<td class="px-6 py-4">
<img src="{{ $blog->getFirstMediaUrl('images') }}" alt="no image"
class="w-12 h-12">
</td>
<td class="px-6 py-4">
<a href="{{ route('blogs.edit',$blog->id) }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-blue-600">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</svg>
</a>
</td>
<td class="px-6 py-4">
<a href="{{ route('blogs.show',$blog->id) }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-blue-600">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</a>
</td>
<td class="px-6 py-4">
<form action="{{ route('blogs.destroy',$blog->id) }}" method="POST"
onsubmit="return confirm('{{ trans('are You Sure ? ') }}');"
style="display: inline-block;">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="submit" class="px-4 py-2 text-white bg-red-700 rounded"
value="Delete">
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
view/blogs/index.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Blogs') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<h1 class="text-2xl text-center">Laravel 9 Blog CRUD With image Upload Using Spatie Media Library</h1>
<div class="mt-1 mb-4">
<a class="px-2 py-2 text-sm text-white bg-blue-600 rounded"
href="{{ route('blogs.create') }}">{{ __('Add Blog') }}</a>
</div>
<div class="relative overflow-x-auto shadow-md sm:rounded-lg">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<thead
class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
#
</th>
<th scope="col" class="px-6 py-3">
Title
</th>
<th scope="col" class="px-6 py-3">
Image
</th>
<th scope="col" class="px-6 py-3">
Edit
</th>
<th scope="col" class="px-6 py-3">
Show
</th>
<th scope="col" class="px-6 py-3">
Delete
</th>
</tr>
</thead>
<tbody>
@foreach ($blogs as $blog)
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row"
class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap">
{{$blog->id}}
</th>
<td class="px-6 py-4">
{{$blog->title}}
</td>
<td class="px-6 py-4">
<img src="{{ $blog->getFirstMediaUrl('images') }}" alt="no image"
class="w-12 h-12">
</td>
<td class="px-6 py-4">
<a href="{{ route('blogs.edit',$blog->id) }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-blue-600">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</svg>
</a>
</td>
<td class="px-6 py-4">
<a href="{{ route('blogs.show',$blog->id) }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-blue-600">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</a>
</td>
<td class="px-6 py-4">
<form action="{{ route('blogs.destroy',$blog->id) }}" method="POST"
onsubmit="return confirm('{{ trans('are You Sure ? ') }}');"
style="display: inline-block;">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="submit" class="px-4 py-2 text-white bg-red-700 rounded"
value="Delete">
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
view/blogs/edit.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Blogs Edit') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<form method="POST" action="{{ route('blogs.update',$blog->id) }}" enctype="multipart/form-data">
@csrf
@method('put')
<div class="mb-6">
<label class="block">
<span class="text-gray-700 @error('title') text-red-500 @enderror">Title</span>
<input type="text" name="title"
class="block @error('title') border-red-500 bg-red-100 text-red-900 @enderror w-full mt-1 rounded-md"
placeholder="" value="{{old('title',$blog->title)}}" />
</label>
@error('title')
<div class="flex items-center text-sm text-red-600">
{{ $message }}
</div>
@enderror
</div>
<div class="mb-6">
<label class="block">
<span class="text-gray-700 @error('content') text-red-500 @enderror">Content</span>
<textarea
class="block @error('content') border-red-500 bg-red-100 text-red-900 @enderror w-full mt-1 rounded-md"
name="content" rows="3">{{old('content',$blog->content)}}</textarea>
</label>
@error('content')
<div class="flex items-center text-sm text-red-600">
{{ $message }}
</div>
@enderror
</div>
<div class="mb-6">
<label class="block">
<span class="sr-only">Choose File</span>
<input type="file" name="image"
class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100" />
</label>
@error('image')
<div class="flex items-center text-sm text-red-600">
{{ $message }}
</div>
@enderror
<img src="{{ $blog->getFirstMediaUrl('images') }}" class="w-12 h-12" alt="no image">
</div>
<button type="Update"
class="text-white bg-blue-600 rounded text-sm px-5 py-2.5">Submit</button>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
view/blogs/show.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Blog Show') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex flex-col items-center justify-center">
<h1 class="text-2xl font-bold">{{ $blog->title }}</h1>
<div>{!! $blog->content !!}</div>
<img src="{{ $blog->getFirstMediaUrl('images') }}" class="w-60 h-60"alt="no image">
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
Step 8: Run Laravel and vite server
php artisan serve
//and next terminal
npm run dev
Read Also
Laravel 9 Upload Multiple Image Using Spatie Media Library
How To Upload Multiple Images In Laravel 9 With Intervention
Laravel 9 Upload Multiple Images Tutorial Example
How to update multiple images in laravel 9