HARI 4: Advanced Features dan API Development β
Bootcamp Laravel - Sistem Pengelolaan Dokumen (SiDoku) β
π Informasi Sesi β
| Item | Keterangan |
|---|---|
| Hari | 4 dari 5 |
| Durasi | 8 Jam (09:00 - 17:00 WIB) |
| Topik Utama | Form Handling, Validation, Auth, File Upload, REST API |
| Project | SiDoku - Fitur lanjutan |
π― Learning Objectives β
Setelah menyelesaikan sesi ini, peserta akan mampu:
- Melakukan validasi form dengan Laravel
- Mengimplementasikan autentikasi dengan Laravel Breeze
- Menangani file upload
- Membuat middleware custom
- Membangun RESTful API
- Menggunakan Laravel Sanctum untuk API authentication
SESI 1: Form Handling dan Validation β
1.1 CSRF Protection β
Laravel secara otomatis melindungi dari Cross-Site Request Forgery:
<form method="POST" action="/dokumen">
@csrf
{{-- Form fields --}}
</form>1.2 Form Request β
Buat Form Request untuk validasi terpisah:
php artisan make:request StoreDokumenRequestEdit app/Http/Requests/StoreDokumenRequest.php:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreDokumenRequest extends FormRequest
{
public function authorize(): bool
{
return true; // atau cek permission
}
public function rules(): array
{
return [
'judul' => 'required|string|max:255',
'nomor' => 'required|string|max:50|unique:dokumens,nomor',
'kategori_id' => 'required|exists:kategoris,id',
'deskripsi' => 'nullable|string|max:1000',
'tanggal_dokumen' => 'required|date|before_or_equal:today',
'file' => 'nullable|file|mimes:pdf,doc,docx|max:2048',
];
}
public function messages(): array
{
return [
'judul.required' => 'Judul dokumen wajib diisi.',
'judul.max' => 'Judul maksimal 255 karakter.',
'nomor.required' => 'Nomor dokumen wajib diisi.',
'nomor.unique' => 'Nomor dokumen sudah digunakan.',
'kategori_id.required' => 'Kategori wajib dipilih.',
'kategori_id.exists' => 'Kategori tidak valid.',
'tanggal_dokumen.before_or_equal' => 'Tanggal tidak boleh lebih dari hari ini.',
'file.mimes' => 'File harus berformat PDF, DOC, atau DOCX.',
'file.max' => 'Ukuran file maksimal 2MB.',
];
}
public function attributes(): array
{
return [
'judul' => 'Judul Dokumen',
'nomor' => 'Nomor Dokumen',
'kategori_id' => 'Kategori',
'tanggal_dokumen' => 'Tanggal Dokumen',
];
}
}Penggunaan di Controller:
use App\Http\Requests\StoreDokumenRequest;
public function store(StoreDokumenRequest $request)
{
// Validasi sudah otomatis berjalan
$validated = $request->validated();
Dokumen::create($validated);
return redirect()->route('dokumen.index')
->with('success', 'Dokumen berhasil ditambahkan!');
}1.3 Validasi Rules β
Rules Umum:
| Rule | Keterangan |
|---|---|
required | Wajib diisi |
nullable | Boleh kosong |
string | Harus string |
integer | Harus integer |
numeric | Harus angka |
email | Format email valid |
min:n | Minimal n karakter/nilai |
max:n | Maksimal n karakter/nilai |
between:min,max | Antara min dan max |
in:val1,val2 | Harus salah satu nilai |
unique:table,column | Unik di tabel |
exists:table,column | Harus ada di tabel |
date | Format tanggal valid |
before:date | Sebelum tanggal |
after:date | Setelah tanggal |
file | Harus file upload |
image | Harus gambar |
mimes:ext1,ext2 | Ekstensi tertentu |
Validasi Kondisional:
public function rules(): array
{
return [
'email' => 'required|email|unique:users,email',
'password' => $this->isMethod('post')
? 'required|min:8|confirmed'
: 'nullable|min:8|confirmed',
];
}1.4 Menampilkan Error di View β
{{-- Semua errors --}}
@if($errors->any())
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
<ul class="list-disc list-inside">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
{{-- Error per field --}}
<div class="mb-4">
<label>Judul</label>
<input type="text"
name="judul"
value="{{ old('judul') }}"
class="border rounded px-3 py-2 w-full @error('judul') border-red-500 @enderror">
@error('judul')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</div>SESI 2: Authentication dengan Laravel Breeze β
2.1 Instalasi Laravel Breeze β
composer require laravel/breeze --dev
php artisan breeze:install blade
npm install
npm run build
php artisan migrateβ οΈ CATATAN PENTING tentang Layout:
- Breeze menggunakan component layout dengan
{{ $slot }}diresources/views/layouts/app.blade.php- Jika Anda sudah membuat views dengan
@extends('layouts.app')dan@section('content')di hari-2, Anda perlu menyesuaikan layout untuk mendukung keduanya:blade{{-- Dalam resources/views/layouts/app.blade.php --}} <main> @if(isset($slot) && $slot->isNotEmpty()) {{ $slot }} @else @yield('content') @endif </main>
2.2 Fitur yang Tersedia β
Setelah instalasi, Anda mendapatkan:
- Login & Register
- Password Reset
- Email Verification
- Profile Management
Routes yang ditambahkan:
| URL | Fungsi |
|---|---|
/register | Halaman registrasi |
/login | Halaman login |
/logout | Logout |
/forgot-password | Lupa password |
/profile | Edit profil |
2.3 Protecting Routes β
// Satu route
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware('auth');
// Group routes (RECOMMENDED - Laravel 12)
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::resource('dokumen', DokumenController::class);
});
// Middleware per route
Route::resource('dokumen', DokumenController::class)
->middleware('auth')
->except(['index', 'show']);
// Γ’Ε‘Β Γ―ΒΈΒ CATATAN PENTING Laravel 12:
// Menggunakan $this->middleware() di constructor TIDAK LAGI DIDUKUNG di Laravel 12!
// Gunakan middleware di routes (seperti di atas) sebagai gantinya.2.4 Mengakses User yang Login β
// Di Controller
$user = auth()->user();
$userId = auth()->id();
$isLoggedIn = auth()->check();
// Di Blade
@auth
<p>Halo, {{ auth()->user()->name }}!</p>
@endauth
@guest
<a href="/login">Login</a>
@endguest2.5 Authorization (Policies) β
Buat policy:
php artisan make:policy DokumenPolicy --model=DokumenEdit app/Policies/DokumenPolicy.php:
<?php
namespace App\Policies;
use App\Models\Dokumen;
use App\Models\User;
class DokumenPolicy
{
public function viewAny(User $user): bool
{
return true;
}
public function view(User $user, Dokumen $dokumen): bool
{
return true;
}
public function create(User $user): bool
{
return true;
}
public function update(User $user, Dokumen $dokumen): bool
{
return $user->id === $dokumen->user_id || $user->is_admin;
}
public function delete(User $user, Dokumen $dokumen): bool
{
return $user->id === $dokumen->user_id || $user->is_admin;
}
}Penggunaan di Controller:
Γ’Ε‘Β Γ―ΒΈΒ PENTING Laravel 12: Untuk menggunakan
$this->authorize(), controller harus menggunakan traitAuthorizesRequests!
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class DokumenController extends Controller
{
use AuthorizesRequests; // Wajib untuk Laravel 12!
public function update(Request $request, Dokumen $dokumen)
{
$this->authorize('update', $dokumen);
// atau
if ($request->user()->cannot('update', $dokumen)) {
abort(403);
}
// Update logic
}Penggunaan di Blade:
@can('update', $dokumen)
<a href="{{ route('dokumen.edit', $dokumen) }}">Edit</a>
@endcan
@cannot('delete', $dokumen)
<span class="text-gray-400">Tidak bisa hapus</span>
@endcannotSESI 3: File Upload β
3.1 Konfigurasi Storage β
Laravel menggunakan filesystem abstraction. Konfigurasi di config/filesystems.php:
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
],Buat symbolic link:
php artisan storage:linkβ οΈ PENTING: Perintah ini WAJIB dijalankan sebelum upload file! Ini membuat link
public/storageβstorage/app/publicsehingga file yang diupload bisa diakses via URL.
3.2 Upload File di Controller β
use Illuminate\Support\Facades\Storage;
public function store(StoreDokumenRequest $request)
{
$validated = $request->validated();
// Handle file upload
if ($request->hasFile('file')) {
$file = $request->file('file');
// Generate nama unik
$filename = time() . '_' . $file->getClientOriginalName();
// Simpan ke disk 'public', folder 'dokumen'
$path = $file->storeAs('dokumen', $filename, 'public');
$validated['file_path'] = $path;
}
$validated['user_id'] = auth()->id();
Dokumen::create($validated);
return redirect()->route('dokumen.index')
->with('success', 'Dokumen berhasil diupload!');
}3.3 Menampilkan dan Download File β
Di View:
@if($dokumen->file_path)
<div class="mt-4">
<h3 class="font-medium">File Lampiran:</h3>
<a href="{{ Storage::url($dokumen->file_path) }}"
target="_blank"
class="text-blue-500 hover:underline">
Lihat File
</a>
<a href="{{ route('dokumen.download', $dokumen) }}"
class="text-green-500 hover:underline ml-4">
Download
</a>
</div>
@endifRoute dan Controller untuk Download:
// Route
Route::get('/dokumen/{dokumen}/download', [DokumenController::class, 'download'])
->name('dokumen.download');
// Controller
public function download(Dokumen $dokumen)
{
if (!$dokumen->file_path || !Storage::disk('public')->exists($dokumen->file_path)) {
abort(404, 'File tidak ditemukan');
}
return Storage::disk('public')->download(
$dokumen->file_path,
$dokumen->nomor . '.' . pathinfo($dokumen->file_path, PATHINFO_EXTENSION)
);
}3.4 Hapus File β
public function destroy(Dokumen $dokumen)
{
// Hapus file jika ada
if ($dokumen->file_path && Storage::disk('public')->exists($dokumen->file_path)) {
Storage::disk('public')->delete($dokumen->file_path);
}
$dokumen->delete();
return redirect()->route('dokumen.index')
->with('success', 'Dokumen berhasil dihapus!');
}SESI 4: Middleware β
4.1 Pengenalan Middleware β
Middleware adalah filter yang memproses HTTP request sebelum mencapai controller.
Middleware bawaan:
auth- Memastikan user sudah loginguest- Memastikan user belum loginverified- Email sudah diverifikasithrottle- Rate limiting
4.2 Membuat Custom Middleware β
php artisan make:middleware CheckRoleEdit app/Http/Middleware/CheckRole.php:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckRole
{
public function handle(Request $request, Closure $next, string $role): Response
{
if (!$request->user()) {
return redirect('login');
}
if ($request->user()->role !== $role) {
abort(403, 'Anda tidak memiliki akses ke halaman ini.');
}
return $next($request);
}
}4.3 Mendaftarkan Middleware β
Edit bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'role' => \App\Http\Middleware\CheckRole::class,
]);
})4.4 Menggunakan Middleware β
// Di route
Route::get('/admin', function () {
return 'Admin Area';
})->middleware('role:admin');
// Di group
Route::middleware(['auth', 'role:admin'])->prefix('admin')->group(function () {
Route::get('/dashboard', [AdminController::class, 'dashboard']);
Route::resource('/users', UserController::class);
});SESI 5: REST API Development β
5.1 Pengenalan REST API β
REST (Representational State Transfer) adalah arsitektur untuk membangun web services.
HTTP Methods:
| Method | CRUD | Contoh |
|---|---|---|
| GET | Read | Ambil data |
| POST | Create | Buat data baru |
| PUT/PATCH | Update | Update data |
| DELETE | Delete | Hapus data |
Response Codes:
| Code | Arti |
|---|---|
| 200 | OK |
| 201 | Created |
| 204 | No Content |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 422 | Unprocessable Entity |
| 500 | Server Error |
5.2 API Routes β
API routes ada di routes/api.php:
<?php
use App\Http\Controllers\Api\DokumenController;
use App\Http\Controllers\Api\KategoriController;
use Illuminate\Support\Facades\Route;
// Public routes
Route::get('/kategoris', [KategoriController::class, 'index']);
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
// PENTING: Gunakan ->parameters() untuk memastikan route model binding benar
Route::apiResource('dokumen', DokumenController::class)
->parameters(['dokumen' => 'dokumen']); // Mencegah nama parameter menjadi {dokuman}
});Γ’Ε‘Β Γ―ΒΈΒ CATATAN: Laravel membuat nama parameter berdasarkan singular resource. Untuk resource
dokumen, Laravel mungkin menghasilkan{dokuman}. Gunakan->parameters(['dokumen' => 'dokumen'])untuk memastikan nama parameter sesuai dengan parameter di controller.
5.3 API Controller β
php artisan make:controller Api/DokumenController --apiEdit app/Http/Controllers/Api/DokumenController.php:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Dokumen;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class DokumenController extends Controller
{
public function index(Request $request): JsonResponse
{
$query = Dokumen::with(['kategori', 'user:id,name']);
if ($request->has('status')) {
$query->where('status', $request->status);
}
if ($request->has('search')) {
$query->cari($request->search);
}
$dokumens = $query->orderBy('created_at', 'desc')
->paginate($request->per_page ?? 15);
return response()->json([
'success' => true,
'message' => 'Daftar dokumen berhasil diambil',
'data' => $dokumens,
]);
}
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'judul' => 'required|string|max:255',
'nomor' => 'required|string|max:50|unique:dokumens',
'kategori_id' => 'required|exists:kategoris,id',
'deskripsi' => 'nullable|string',
'tanggal_dokumen' => 'required|date',
]);
$validated['user_id'] = $request->user()->id;
$validated['status'] = 'draft';
$dokumen = Dokumen::create($validated);
$dokumen->load(['kategori', 'user:id,name']);
return response()->json([
'success' => true,
'message' => 'Dokumen berhasil ditambahkan',
'data' => $dokumen,
], 201);
}
public function show(Dokumen $dokumen): JsonResponse
{
$dokumen->load(['kategori', 'user:id,name', 'tags']);
return response()->json([
'success' => true,
'message' => 'Detail dokumen',
'data' => $dokumen,
]);
}
public function update(Request $request, Dokumen $dokumen): JsonResponse
{
$validated = $request->validate([
'judul' => 'required|string|max:255',
'nomor' => 'required|string|max:50|unique:dokumens,nomor,' . $dokumen->id,
'kategori_id' => 'required|exists:kategoris,id',
'deskripsi' => 'nullable|string',
'tanggal_dokumen' => 'required|date',
'status' => 'nullable|in:draft,pending,approved,rejected',
]);
$dokumen->update($validated);
$dokumen->load(['kategori', 'user:id,name']);
return response()->json([
'success' => true,
'message' => 'Dokumen berhasil diupdate',
'data' => $dokumen,
]);
}
public function destroy(Dokumen $dokumen): JsonResponse
{
$dokumen->delete();
return response()->json([
'success' => true,
'message' => 'Dokumen berhasil dihapus',
]);
}
}5.4 API Resource (Transformers) β
Untuk kontrol lebih terhadap response JSON:
php artisan make:resource DokumenResource
php artisan make:resource DokumenCollection --collectionEdit app/Http/Resources/DokumenResource.php:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class DokumenResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'judul' => $this->judul,
'nomor' => $this->nomor,
'deskripsi' => $this->deskripsi,
'status' => $this->status,
'tanggal_dokumen' => $this->tanggal_dokumen->format('Y-m-d'),
'tanggal_indonesia' => $this->tanggal_indonesia,
'file_url' => $this->file_path
? asset('storage/' . $this->file_path)
: null,
'kategori' => [
'id' => $this->kategori->id,
'nama' => $this->kategori->nama,
],
'pembuat' => [
'id' => $this->user->id,
'nama' => $this->user->name,
],
'created_at' => $this->created_at->toIso8601String(),
'updated_at' => $this->updated_at->toIso8601String(),
];
}
}Penggunaan:
use App\Http\Resources\DokumenResource;
public function show(Dokumen $dokumen): DokumenResource
{
return new DokumenResource($dokumen->load(['kategori', 'user']));
}
public function index(): JsonResponse
{
$dokumens = Dokumen::with(['kategori', 'user'])->paginate(15);
return response()->json([
'success' => true,
'data' => DokumenResource::collection($dokumens),
'meta' => [
'current_page' => $dokumens->currentPage(),
'total' => $dokumens->total(),
'per_page' => $dokumens->perPage(),
],
]);
}5.5 API Authentication dengan Sanctum β
Instalasi:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrateβ οΈ PENTING: Perintah
vendor:publishakan membuat file migration untuk tabelpersonal_access_tokens. Jika Anda menggunakan Laravel 11+, migration ini sudah termasuk secara default. Jalankanphp artisan migrateuntuk membuat tabelnya.
Setup Model User:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}Auth Routes:
// routes/api.php
use App\Http\Controllers\Api\AuthController;
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum');
Route::get('/me', [AuthController::class, 'me'])->middleware('auth:sanctum');Auth Controller:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function register(Request $request): JsonResponse
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'success' => true,
'message' => 'Registrasi berhasil',
'data' => [
'user' => $user,
'token' => $token,
],
], 201);
}
public function login(Request $request): JsonResponse
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['Kredensial tidak valid.'],
]);
}
// Hapus token lama
$user->tokens()->delete();
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'success' => true,
'message' => 'Login berhasil',
'data' => [
'user' => $user,
'token' => $token,
],
]);
}
public function logout(Request $request): JsonResponse
{
$request->user()->currentAccessToken()->delete();
return response()->json([
'success' => true,
'message' => 'Logout berhasil',
]);
}
public function me(Request $request): JsonResponse
{
return response()->json([
'success' => true,
'data' => $request->user(),
]);
}
}5.6 Testing API dengan Postman/cURL β
Login:
curl -X POST http://localhost:8000/api/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@sidoku.test", "password": "password"}'Get Dokumen (dengan token):
curl -X GET http://localhost:8000/api/dokumen \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Accept: application/json"Create Dokumen:
curl -X POST http://localhost:8000/api/dokumen \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"judul": "SK Pengangkatan",
"nomor": "SK/100/2025",
"kategori_id": 1,
"tanggal_dokumen": "2025-06-20"
}'PRAKTIKUM: Implementasi Fitur Lanjutan β
Praktikum 1: Update Form dengan Validasi β
Update resources/views/dokumen/create.blade.php dengan validasi lengkap dan file upload.
Praktikum 2: Tambah Role ke User β
php artisan make:migration add_role_to_users_tablepublic function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->enum('role', ['admin', 'operator', 'viewer'])->default('viewer')->after('email');
});
}Praktikum 3: Test API β
- Register user baru via API
- Login dan dapatkan token
- Create dokumen via API
- List dokumen dengan filter
- Update dan delete dokumen
β οΈ Catatan Penting untuk Laravel 12 β
Laravel 12 memiliki beberapa perubahan signifikan yang perlu diperhatikan:
1. Controller Middleware Deprecated β
// [TIDAK DIDUKUNG] di Laravel 12:
public function __construct()
{
$this->middleware('auth');
}
// [BENAR] GUNAKAN middleware di routes:
Route::middleware('auth')->group(function () {
Route::resource('dokumen', DokumenController::class);
});2. AuthorizesRequests Trait Wajib β
// Untuk menggunakan $this->authorize(), tambahkan trait:
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class DokumenController extends Controller
{
use AuthorizesRequests; // WAJIB di Laravel 12!
public function update(Request $request, Dokumen $dokumen)
{
$this->authorize('update', $dokumen);
// ...
}
}3. Route Parameter Binding β
// Untuk resource dengan nama non-English, gunakan ->parameters():
Route::resource('dokumen', DokumenController::class)
->parameters(['dokumen' => 'dokumen']);
// Ini mencegah Laravel membuat parameter {dokuman} yang salah4. Blade Layout Compatibility β
Jika menggunakan Breeze dengan blade stack dan custom views dengan @extends:
{{-- Layout harus mendukung keduanya: --}}
<main>
@if(isset($slot) && $slot->isNotEmpty())
{{ $slot }}
@else
@yield('content')
@endif
</main>β Checkpoint Hari 4 β
Pastikan Anda sudah bisa:
- Membuat Form Request untuk validasi
- Mengimplementasikan autentikasi dengan Breeze
- Mengupload dan mengelola file
- Membuat middleware custom
- Membangun RESTful API
- Menggunakan Sanctum untuk API authentication
Selamat! Anda siap untuk Hari 5: Testing dan Deployment!