Cara Upload File Di Laravel Ke Storage Public Dengan Aman

Berikut ini cara upload file di Laravel ke folder storage public dan menyimpan path-nya di database. tujuannya untuk menampilkan gambar dari database dan melakukan aksi simpan, update, dan delete. saya pakai Laravel 11 dengan pendekatan Storage, karena lebih aman dan fleksibel dibanding memindahkan file langsung ke public.
Install Laravel di localhost
di tutorial ini saya pakai Laravel versi terbaru (11.x). langsung saja jalankan composer:
composer create-project laravel/laravel upload-file
bagi yang belum paham instalasi laravel bisa baca Cara Install Laravel di Windows atau Cara install PHP di Windows dan Berjalan di CMD (Command Prompt)
Menyiapkan storage public
sebelum upload, kita siapkan dulu symlink storage supaya file yang disimpan di storage/app/public bisa diakses publik:
php artisan storage:link
command ini membuat folder public/storage yang menunjuk ke storage/app/public. jadi file yang kita simpan di storage public bisa diakses lewat URL /storage/....
Membuat Controller untuk upload file
buat controller app/Http/Controllers/ImageUploadController.php yang melakukan validasi dan penyimpanan file:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class ImageUploadController extends Controller
{
public function imageUpload()
{
return view('imageUpload');
}
public function imageUploadPost(Request $request)
{
$request->validate([
'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
]);
// generate nama file unik biar tidak bentrok
$imageName = Str::uuid() . '.' . $request->image->extension();
// simpan ke storage/app/public/images
$path = $request->image->storeAs('images', $imageName, 'public');
return back()
->with('success', 'Berhasil melakukan upload file di laravel')
->with('image', $path);
}
}
Penjelasan :
$request->validate([...]): validasi file.imagememastikan file adalah gambar,mimesmembatasi ekstensi,max:2048membatasi ukuran maksimal 2 MB (dalam kilobyte)Str::uuid(): generate nama unik supaya dua user yang upload file sama tidak saling menimpa$request->image->storeAs('images', $imageName, 'public'): menyimpan file ke folderstorage/app/public/imagesdengan diskpublic, method ini yang menggantikanmove()ke public_path. hasilnya path relatif sepertiimages/xxx.webp
Membuat route di Laravel
lanjut buat route di routes/web.php:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ImageUploadController;
Route::get('image-upload', [ImageUploadController::class, 'imageUpload'])->name('image.upload');
Route::post('image-upload', [ImageUploadController::class, 'imageUploadPost'])->name('image.upload.post');
Penjelasan :
Route::get('image-upload', ...): endpoint URLimage-uploaddengan method GET, menampilkan formRoute::post('image-upload', ...): endpoint yang sama tapi method POST, menerima file dari form->name('image.upload'): penamaan route supaya bisa dipanggil lewatroute('image.upload')di blade
Membuat view blade HTML form upload
buat file resources/views/imageUpload.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Laravel File Upload</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
/>
</head>
<body>
<div class="container mt-5">
<div class="card">
<div class="card-header"><h2>Laravel File Upload</h2></div>
<div class="card-body">
@if (session('success'))
<div class="alert alert-success">
<strong>{{ session('success') }}</strong>
</div>
@if (session('image'))
<img src="{{ asset('storage/' . session('image')) }}" class="img-fluid mt-2" style="max-width: 300px" />
@endif
@endif @if ($errors->any())
<div class="alert alert-danger">
<strong>Whoops!</strong>, Ada masalah ketika melakukan input data.
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('image.upload.post') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="mb-3">
<input type="file" name="image" class="form-control" />
</div>
<button type="submit" class="btn btn-success">Upload</button>
</form>
</div>
</div>
</div>
</body>
</html>
Penjelasan :
@csrf: token CSRF wajib di form POST Laravel, tanpa ini request akan ditolak (error 419)enctype="multipart/form-data": wajib untuk form yang mengirim fileasset('storage/' . session('image')): menampilkan gambar dari storage public, perluphp artisan storage:linktadi supaya bisa diakses
Menjalankan project Laravel di localhost
jalankan server:
php artisan serve
buka http://127.0.0.1:8000/image-upload, pilih file gambar, lalu klik Upload. kalau sukses, muncul notifikasi dan preview gambar.
Review kode: mengamankan dari vulnerability upload file
ada beberapa hal yang perlu diperhatikan supaya upload file tidak jadi celah keamanan:
1. Jangan pakai nama file asli dari client
kode seperti ini berbahaya karena bisa disisipi path traversal atau nama file yang menimpa file lain:
// jangan lakukan ini
$imageName = $request->image->getClientOriginalName();
pakai nama unik dari server:
$imageName = Str::uuid() . '.' . $request->image->extension();
2. Ambil ekstensi dari MIME, bukan nama file
getClientOriginalExtension() mengambil ekstensi dari nama file yang dikirim client, bisa dimanipulasi. sedangkan extension() membaca tipe file yang sebenarnya:
// lebih aman, baca dari signature file
$imageName = Str::uuid() . '.' . $request->image->extension();
3. Selalu validasi sebelum simpan
jangan pernah menyimpan file tanpa validasi tipe, ukuran, dan jangan eksekusi file yang diupload sebagai kode. untuk kasus ekstrem, simpan file di luar public atau gunakan whitelist MIME.
Pertanyaan yang Sering Diajukan
Kenapa pakai Storage Laravel, bukan move() ke public_path?
Storage memakai sistem filesystem yang fleksibel (local, S3, dll) dan mengelola nama file secara aman. move() langsung ke public_path rentan dan tidak scalable, apalagi kalau nanti pindah ke cloud storage.
Kenapa file upload saya 419 di Laravel?
Itu karena token CSRF tidak terkirim. Pastikan di form pakai @csrf, atau untuk API tambahkan middleware sanctum dan kirim header Accept: application/json.
Bagaimana cara validasi ukuran dan tipe file upload?
Pakai $request->validate() dengan aturan image, mimes, dan max. Contoh: image|mimes:jpeg,png,jpg,gif|max:2048. max dihitung dalam kilobyte.
Bagaimana menampilkan gambar yang diupload dari database?
Jalankan php artisan storage:link untuk membuat symlink public/storage ke storage/app/public, lalu akses lewat asset("storage/" . $path).

