MyUnlimitedhost MySoftware Hosting - 2
Q: https://laravel.anekakebutuhan.com/posts --> OK
sudah di test, kecuali fungsi 'hapus'
A: Tampilan sudah bagus! Draft post juga tampil dengan label "Draft". 🎉
Coba test semua fungsi sekarang:
- Buat post baru → klik tombol "+ Buat Post"
- Edit → klik "Edit" di salah satu post
- Hapus → klik "Hapus" — akan muncul konfirmasi dialog
Silakan dicoba semua — kabari hasilnya! 👀
A: Cek isi file index.blade.php sekarang:
cat ~/laravel.anekakebutuhan.com/resources/views/posts/index.blade.php | head -10
Kabari hasilnya! 👀
A:
File sudah benar, tombol ada di sana. Masalahnya kemungkinan layout x-guest-layout membatasi lebar konten sehingga tombol terpotong atau tidak terlihat.
Coba scroll ke kanan atau zoom out browser — atau screenshot full halaman untuk lihat apakah tombolnya ada tapi di luar viewport.
Alternatifnya, pindahkan tombol ke bawah judul:
cat > ~/laravel.anekakebutuhan.com/resources/views/posts/index.blade.php << 'EOF'
<x-guest-layout>
<h1 class="text-2xl font-bold text-gray-800 mb-2">Daftar Post</h1>
<a href="/posts/create" class="inline-block bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700 text-sm mb-4">+ Buat Post</a>
@if(session('success'))
<div class="bg-green-100 text-green-700 px-4 py-2 rounded mb-4">{{ session('success') }}</div>
@endif
@foreach($posts as $post)
<div class="mb-4 p-4 bg-white rounded shadow">
<div class="flex justify-between items-start">
<div>
<h2 class="text-lg font-semibold">{{ $post->title }}</h2>
<p class="text-sm text-gray-500">oleh {{ $post->author }} · {{ $post->created_at->diffForHumans() }}
@if(!$post->is_published)
<span class="bg-yellow-100 text-yellow-700 px-2 py-0 rounded text-xs ml-1">Draft</span>
@endif
</p>
<p class="text-gray-600 mt-2">{{ Str::limit($post->content, 100) }}</p>
</div>
<div class="flex gap-2 ml-4">
<a href="/posts/{{ $post->id }}" class="text-indigo-600 hover:underline text-sm">Lihat</a>
<a href="/posts/{{ $post->id }}/edit" class="text-yellow-600 hover:underline text-sm">Edit</a>
<form method="POST" action="/posts/{{ $post->id }}" onsubmit="return confirm('Hapus post ini?')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:underline text-sm">Hapus</button>
</form>
</div>
</div>
</div>
@endforeach
</x-guest-layout>
EOF
Coba akses lagi https://laravel.anekakebutuhan.com/posts! 👀
A: Tombol ada tapi warnanya sama dengan background biru dari layout — jadi tidak terlihat. Ganti warna teks tombol menjadi putih eksplisit dan tambahkan styling lebih jelas:
cat > ~/laravel.anekakebutuhan.com/resources/views/posts/index.blade.php << 'EOF'
<x-guest-layout>
<h1 class="text-2xl font-bold text-gray-800 mb-2">Daftar Post</h1>
<a href="/posts/create" style="display:inline-block;background:#4f46e5;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;margin-bottom:16px;text-decoration:none;">+ Buat Post</a>
@if(session('success'))
<div style="background:#dcfce7;color:#15803d;padding:10px;border-radius:6px;margin-bottom:16px;">{{ session('success') }}</div>
@endif
@foreach($posts as $post)
<div style="background:#fff;padding:16px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px;">
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
<div>
<h2 style="font-size:18px;font-weight:600;">{{ $post->title }}</h2>
<p style="font-size:13px;color:#6b7280;">oleh {{ $post->author }} · {{ $post->created_at->diffForHumans() }}
@if(!$post->is_published)
<span style="background:#fef9c3;color:#a16207;padding:1px 6px;border-radius:4px;font-size:11px;">Draft</span>
@endif
</p>
<p style="color:#374151;margin-top:8px;">{{ Str::limit($post->content, 100) }}</p>
</div>
<div style="display:flex;gap:8px;margin-left:16px;">
<a href="/posts/{{ $post->id }}" style="color:#4f46e5;font-size:13px;">Lihat</a>
<a href="/posts/{{ $post->id }}/edit" style="color:#d97706;font-size:13px;">Edit</a>
<form method="POST" action="/posts/{{ $post->id }}" onsubmit="return confirm('Hapus post ini?')">
@csrf
@method('DELETE')
<button type="submit" style="color:#dc2626;font-size:13px;background:none;border:none;cursor:pointer;">Hapus</button>
</form>
</div>
</div>
</div>
@endforeach
</x-guest-layout>
EOF
Kabari hasilnya! 👀
🎉 Sekarang tampil dengan benar! Tombol "+ Buat Post" terlihat jelas dengan warna ungu.
Sekarang test semua fungsi CRUD:
- Klik "+ Buat Post" → isi form → simpan
- Klik "Edit" → ubah data → update
- Klik "Hapus" → konfirmasi → hapus
Kabari hasilnya! 👀
A: skrip tak bisa di paste!!
lanjut dulu
A: 🎉 Tombol Update sudah muncul! Sekarang test semua:
- Klik Update — apakah data berhasil diupdate?
- Kembali ke
/posts/create— apakah tombol Simpan juga sudah muncul? - Test Hapus salah satu post
Kabari hasilnya! 👀
A:
🎉 CRUD Lengkap Berfungsi Sempurna!
Terlihat: - ✅ Update — "Post berhasil diupdate!" muncul - ✅ Delete — konfirmasi dialog "Hapus post ini?" muncul - ✅ Tombol — semua tombol terlihat dengan benar
Recap CRUD Laravel yang sudah dikuasai:
| Method | Route | Fungsi |
|---|---|---|
| GET | /posts |
Index — daftar semua post |
| GET | /posts/create |
Form buat post baru |
| POST | /posts |
Store — simpan post baru |
| GET | /posts/{id} |
Show — detail post |
| GET | /posts/{id}/edit |
Form edit post |
| PUT | /posts/{id} |
Update — simpan perubahan |
| DELETE | /posts/{id} |
Destroy — hapus post |
Ini adalah RESTful Resource Controller — standar Laravel! 🚀
Mau lanjut ke mana?
1. Seeder & Factory — generate data dummy otomatis
2. Relasi Eloquent — tambah model Category yang berelasi dengan Post
3. Middleware & Auth — batasi CRUD hanya untuk user yang login
4. Pagination — tampilkan post dengan halaman
A: Buat seeder dan factory untuk Post:
cd ~/laravel.anekakebutuhan.com
# Buat Factory
php artisan make:factory PostFactory --model=Post
# Buat Seeder
php artisan make:seeder PostSeeder
Kabari hasilnya! 👀
A: Berhasil! Sekarang isi Factory dan Seeder:
# Isi PostFactory
cat > ~/laravel.anekakebutuhan.com/database/factories/PostFactory.php << 'EOF'
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(5),
'content' => fake()->paragraphs(3, true),
'author' => fake()->name(),
'is_published' => fake()->boolean(70), // 70% chance published
];
}
}
EOF
# Isi PostSeeder
cat > ~/laravel.anekakebutuhan.com/database/seeders/PostSeeder.php << 'EOF'
<?php
namespace Database\Seeders;
use App\Models\Post;
use Illuminate\Database\Seeder;
class PostSeeder extends Seeder
{
public function run(): void
{
// Hapus data lama
Post::truncate();
// Generate 20 post dummy
Post::factory(20)->create();
$this->command->info('20 post dummy berhasil dibuat!');
}
}
EOF
Lalu jalankan seeder:
php artisan db:seed --class=PostSeeder
Kabari hasilnya! 👀
A:
Posisi folder salah, perlu cd dulu ke folder Laravel:
cd ~/laravel.anekakebutuhan.com
php artisan db:seed --class=PostSeeder
Kabari hasilnya! 👀
A:
Model Post belum menggunakan trait HasFactory. Tambahkan:
cat > ~/laravel.anekakebutuhan.com/app/Models/Post.php << 'EOF'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title',
'content',
'author',
'is_published',
];
protected $casts = [
'is_published' => 'boolean',
];
}
EOF
Lalu jalankan seeder lagi:
php artisan db:seed --class=PostSeeder
Kabari hasilnya! 👀
A: 🎉 Seeder berhasil! 20 post dummy dibuat!
Cek hasilnya di browser:
https://laravel.anekakebutuhan.com/posts
Seharusnya sekarang ada banyak post dengan judul dan konten random dari Faker! 👀
A: 🎉 Seeder & Factory berjalan sempurna!
Sekarang kamu sudah kuasai:
- Factory — template data dummy dengan Faker
- Seeder — script untuk mengisi database
- fake()->sentence(), fake()->paragraphs(), fake()->name(), fake()->boolean() — helper Faker
Mau lanjut ke mana?
- Pagination — tampilkan post per halaman (misal 5 per halaman)
- Relasi Eloquent — tambah model
Categoryberelasi denganPost - Middleware & Auth — batasi CRUD hanya untuk user login
- Kembali ke meeting dulu 😄
A:
Pagination di Laravel sangat mudah! Update PostController dan view:
# Update index() di PostController - ganti get() dengan paginate()
sed -i 's/Post::latest()->get()/Post::latest()->paginate(5)/' ~/laravel.anekakebutuhan.com/app/Http/Controllers/PostController.php
Lalu tambahkan link pagination di view:
cat > ~/laravel.anekakebutuhan.com/resources/views/posts/index.blade.php << 'EOF'
<x-guest-layout>
<h1 class="text-2xl font-bold text-gray-800 mb-2">Daftar Post</h1>
<a href="/posts/create" style="display:inline-block;background:#4f46e5;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;margin-bottom:16px;text-decoration:none;">+ Buat Post</a>
@if(session('success'))
<div style="background:#dcfce7;color:#15803d;padding:10px;border-radius:6px;margin-bottom:16px;">{{ session('success') }}</div>
@endif
@foreach($posts as $post)
<div style="background:#fff;padding:16px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px;">
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
<div>
<h2 style="font-size:18px;font-weight:600;">{{ $post->title }}</h2>
<p style="font-size:13px;color:#6b7280;">oleh {{ $post->author }} · {{ $post->created_at->diffForHumans() }}
@if(!$post->is_published)
<span style="background:#fef9c3;color:#a16207;padding:1px 6px;border-radius:4px;font-size:11px;">Draft</span>
@endif
</p>
<p style="color:#374151;margin-top:8px;">{{ Str::limit($post->content, 100) }}</p>
</div>
<div style="display:flex;gap:8px;margin-left:16px;">
<a href="/posts/{{ $post->id }}" style="color:#4f46e5;font-size:13px;">Lihat</a>
<a href="/posts/{{ $post->id }}/edit" style="color:#d97706;font-size:13px;">Edit</a>
<form method="POST" action="/posts/{{ $post->id }}" onsubmit="return confirm('Hapus post ini?')">
@csrf
@method('DELETE')
<button type="submit" style="color:#dc2626;font-size:13px;background:none;border:none;cursor:pointer;">Hapus</button>
</form>
</div>
</div>
</div>
@endforeach
{{-- Pagination links --}}
<div style="margin-top:16px;">
{{ $posts->links() }}
</div>
</x-guest-layout>
EOF
Coba akses https://laravel.anekakebutuhan.com/posts — seharusnya muncul 5 post per halaman dengan navigasi halaman! 👀
A: 🎉 Pagination berjalan sempurna!
Dengan hanya mengubah .get() menjadi .paginate(5) dan menambahkan {{ $posts->links() }} — Laravel otomatis handle semua logika pagination!
Mau lanjut ke mana?
- Relasi Eloquent — tambah model
Categoryberelasi denganPost - Middleware & Auth — batasi CRUD hanya untuk user login
- Istirahat dulu 😄
A: Buat model Category sekaligus migration-nya:
cd ~/laravel.anekakebutuhan.com
php artisan make:model Category -m
Kabari hasilnya! 👀
A:
Berhasil! Sekarang isi migration Category dan tambah kolom category_id ke posts:
# Isi migration categories
cat > ~/laravel.anekakebutuhan.com/database/migrations/2026_05_12_093418_create_categories_table.php << 'EOF'
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('categories');
}
};
EOF
# Buat migration tambah kolom category_id ke posts
php artisan make:migration add_category_id_to_posts_table --table=posts
Kabari hasilnya! 👀
A: Isi migration add_category_id:
cat > ~/laravel.anekakebutuhan.com/database/migrations/2026_05_12_093504_add_category_id_to_posts_table.php << 'EOF'
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropForeign(['category_id']);
$table->dropColumn('category_id');
});
}
};
EOF
# Jalankan migration
php artisan migrate
Kabari hasilnya! 👀
A: Tabel berhasil dibuat! Sekarang setup model relasi:
# Isi model Category
cat > ~/laravel.anekakebutuhan.com/app/Models/Category.php << 'EOF'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
protected $fillable = ['name', 'slug', 'description'];
// Satu Category punya banyak Post
public function posts()
{
return $this->hasMany(Post::class);
}
}
EOF
# Update model Post - tambah relasi ke Category
cat > ~/laravel.anekakebutuhan.com/app/Models/Post.php << 'EOF'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title',
'content',
'author',
'is_published',
'category_id',
];
protected $casts = [
'is_published' => 'boolean',
];
// Satu Post belongs to satu Category
public function category()
{
return $this->belongsTo(Category::class);
}
}
EOF
Lalu buat seeder untuk Category:
php artisan make:seeder CategorySeeder
Kabari hasilnya! 👀
A: Isi CategorySeeder:
cat > ~/laravel.anekakebutuhan.com/database/seeders/CategorySeeder.php << 'EOF'
<?php
namespace Database\Seeders;
use App\Models\Category;
use App\Models\Post;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
class CategorySeeder extends Seeder
{
public function run(): void
{
$categories = [
['name' => 'Teknologi', 'slug' => 'teknologi', 'description' => 'Artikel seputar teknologi'],
['name' => 'Bisnis', 'slug' => 'bisnis', 'description' => 'Artikel seputar bisnis'],
['name' => 'Kesehatan', 'slug' => 'kesehatan', 'description' => 'Artikel seputar kesehatan'],
['name' => 'Pendidikan', 'slug' => 'pendidikan', 'description' => 'Artikel seputar pendidikan'],
];
foreach ($categories as $cat) {
Category::create($cat);
}
// Assign category ke post yang ada secara random
$categoryIds = Category::pluck('id')->toArray();
Post::all()->each(function ($post) use ($categoryIds) {
$post->update(['category_id' => $categoryIds[array_rand($categoryIds)]]);
});
$this->command->info('4 kategori dibuat & assigned ke semua post!');
}
}
EOF
# Jalankan seeder
php artisan db:seed --class=CategorySeeder
Kabari hasilnya! 👀
A: 🎉 Berhasil! Sekarang test relasi via Tinker:
php artisan tinker --execute="
// Cek post beserta kategorinya
\$posts = App\Models\Post::with('category')->take(3)->get();
foreach(\$posts as \$post) {
echo \$post->title . ' --> ' . (\$post->category ? \$post->category->name : 'No Category') . PHP_EOL;
}
// Cek kategori beserta jumlah postnya
\$categories = App\Models\Category::withCount('posts')->get();
foreach(\$categories as \$cat) {
echo \$cat->name . ': ' . \$cat->posts_count . ' posts' . PHP_EOL;
}
"
Kabari hasilnya! 👀
A:
🎉 Relasi Eloquent berjalan sempurna!
Terlihat:
- ✅ Post::with('category') — eager loading relasi
- ✅ Setiap post sudah punya kategori
- ✅ withCount('posts') — hitung jumlah post per kategori
Sekarang tampilkan kategori di halaman posts. Update index.blade.php:
cat >> ~/laravel.anekakebutuhan.com/resources/views/posts/index.blade.php << 'EOF'
EOF
# Tambahkan tampilan kategori di card post
sed -i 's/{{ Str::limit($post->content, 100) }}/{{ Str::limit($post->content, 100) }}<\/p><p style="font-size:12px;color:#6b7280;margin-top:4px;">Kategori: {{ $post->category ? $post->category->name : "Uncategorized" }}/' ~/laravel.anekakebutuhan.com/resources/views/posts/index.blade.php
Atau cara lebih bersih, update PostController untuk eager load category:
sed -i 's/Post::latest()->paginate(5)/Post::with("category")->latest()->paginate(5)/' ~/laravel.anekakebutuhan.com/app/Http/Controllers/PostController.php
Lalu update view index untuk tampilkan kategori:
cat > ~/laravel.anekakebutuhan.com/resources/views/posts/index.blade.php << 'EOF'
<x-guest-layout>
<h1 class="text-2xl font-bold text-gray-800 mb-2">Daftar Post</h1>
<a href="/posts/create" style="display:inline-block;background:#4f46e5;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;margin-bottom:16px;text-decoration:none;">+ Buat Post</a>
@if(session('success'))
<div style="background:#dcfce7;color:#15803d;padding:10px;border-radius:6px;margin-bottom:16px;">{{ session('success') }}</div>
@endif
@foreach($posts as $post)
<div style="background:#fff;padding:16px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px;">
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
<div>
<h2 style="font-size:18px;font-weight:600;">{{ $post->title }}</h2>
<p style="font-size:13px;color:#6b7280;">
oleh {{ $post->author }} · {{ $post->created_at->diffForHumans() }}
@if($post->category)
· <span style="background:#e0e7ff;color:#4f46e5;padding:1px 6px;border-radius:4px;">{{ $post->category->name }}</span>
@endif
@if(!$post->is_published)
<span style="background:#fef9c3;color:#a16207;padding:1px 6px;border-radius:4px;font-size:11px;">Draft</span>
@endif
</p>
<p style="color:#374151;margin-top:8px;">{{ Str::limit($post->content, 100) }}</p>
</div>
<div style="display:flex;gap:8px;margin-left:16px;">
<a href="/posts/{{ $post->id }}" style="color:#4f46e5;font-size:13px;">Lihat</a>
<a href="/posts/{{ $post->id }}/edit" style="color:#d97706;font-size:13px;">Edit</a>
<form method="POST" action="/posts/{{ $post->id }}" onsubmit="return confirm('Hapus post ini?')">
@csrf
@method('DELETE')
<button type="submit" style="color:#dc2626;font-size:13px;background:none;border:none;cursor:pointer;">Hapus</button>
</form>
</div>
</div>
</div>
@endforeach
<div style="margin-top:16px;">
{{ $posts->links() }}
</div>
</x-guest-layout>
EOF
Coba akses https://laravel.anekakebutuhan.com/posts! 👀
A: 🎉 Relasi Eloquent tampil sempurna!
Label kategori "Kesehatan" muncul dengan styling biru indigo yang rapi di bawah nama author.
Sekarang kamu sudah kuasai:
- ✅ hasMany — Category punya banyak Post
- ✅ belongsTo — Post milik satu Category
- ✅ with('category') — Eager loading (hindari N+1 query)
- ✅ withCount('posts') — Hitung relasi
Mau lanjut ke mana?
- Middleware & Auth — batasi CRUD hanya untuk user login
- Filter by Category — tampilkan post berdasarkan kategori
- Category CRUD — kelola kategori dari browser
- Istirahat dulu 😄
A:
Kita batasi akses CRUD hanya untuk user yang sudah login. Update PostController:
.....error paste codeing, lihat asli ,,,
lanjut dulu:
A:
Error ini karena Laravel 13 sudah tidak support $this->middleware() di constructor. Cara baru menggunakan middleware di routes. Fix:
# Hapus __construct dari PostController
sed -i '/public function __construct/,/}/{ /public function __construct/d; /\/\/ Hanya index/d; /\$this->middleware/d; /^ }/{ /^ }/{N; s/ }\n//} } }' ~/laravel.anekakebutuhan.com/app/Http/Controllers/PostController.php
Cara lebih aman — edit langsung file:
cat > ~/laravel.anekakebutuhan.com/app/Http/Controllers/PostController.php << 'EOF'
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index()
{
$posts = Post::with('category')->latest()->paginate(5);
return view('posts.index', compact('posts'));
}
public function create()
{
$categories = Category::all();
return view('posts.create', compact('categories'));
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
'category_id' => 'nullable|exists:categories,id',
]);
Post::create([
'title' => $request->title,
'content' => $request->content,
'author' => $request->author,
'category_id' => $request->category_id,
'is_published' => $request->has('is_published'),
]);
return redirect('/posts')->with('success', 'Post berhasil dibuat!');
}
public function show($id)
{
$post = Post::with('category')->findOrFail($id);
return view('posts.show', compact('post'));
}
public function edit($id)
{
$post = Post::findOrFail($id);
$categories = Category::all();
return view('posts.edit', compact('post', 'categories'));
}
public function update(Request $request, $id)
{
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
'category_id' => 'nullable|exists:categories,id',
]);
$post = Post::findOrFail($id);
$post->update([
'title' => $request->title,
'content' => $request->content,
'author' => $request->author,
'category_id' => $request->category_id,
'is_published' => $request->has('is_published'),
]);
return redirect('/posts')->with('success', 'Post berhasil diupdate!');
}
public function destroy($id)
{
$post = Post::findOrFail($id);
$post->delete();
return redirect('/posts')->with('success', 'Post berhasil dihapus!');
}
}
EOF
Lalu pindahkan middleware ke routes:
# Update routes posts dengan middleware auth
cat >> ~/laravel.anekakebutuhan.com/routes/web.php << 'EOF'
// Override resource routes dengan middleware
Route::middleware('auth')->group(function () {
Route::resource('posts', App\Http\Controllers\PostController::class)
->except(['index', 'show']);
});
EOF
Kabari hasilnya! 👀
A: Middleware auth berjalan! Redirect ke login otomatis. 🎉
Untuk login, ada 2 opsi:
1. Register user baru — akses https://laravel.anekakebutuhan.com/register
2. Atau buat user via Tinker:
cd ~/laravel.anekakebutuhan.com
php artisan tinker --execute="
App\Models\User::create([
'name' => 'Admin',
'email' => 'admin@anekakebutuhan.com',
'password' => bcrypt('Admin1234'),
]);
echo 'User created!';
"
Pilih yang mana? 😄
A: 🎉 Middleware Auth berjalan sempurna!
- ✅ User tidak login → redirect ke halaman login
- ✅ Register user baru berhasil
- ✅ Login berhasil untuk kedua user
- ✅ Validasi "email already taken" juga berfungsi
Recap semua yang sudah dikuasai hari ini:
| Fitur | Status |
|---|---|
| Route & Controller | ✅ |
| Blade Template & Layout | ✅ |
| Eloquent ORM & Migration | ✅ |
| CRUD Lengkap | ✅ |
| Seeder & Factory | ✅ |
| Relasi hasMany & belongsTo | ✅ |
| Pagination | ✅ |
| Middleware & Auth | ✅ |
Laravel dasarnya sudah sangat solid! 🚀
Mau lanjut ke mana?
- Filter by Category — tampilkan post berdasarkan kategori
- Category CRUD — kelola kategori dari browser
- Search — fitur pencarian post
- Istirahat dulu 😄
A:
Tambahkan filter by category di PostController dan view:
# Update method index di PostController
cat > ~/laravel.anekakebutuhan.com/app/Http/Controllers/PostController.php << 'EOF'
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(Request $request)
{
$categories = Category::all();
$query = Post::with('category')->latest();
// Filter by category
if ($request->category) {
$query->whereHas('category', function($q) use ($request) {
$q->where('slug', $request->category);
});
}
$posts = $query->paginate(5)->withQueryString();
$selectedCategory = $request->category;
return view('posts.index', compact('posts', 'categories', 'selectedCategory'));
}
public function create()
{
$categories = Category::all();
return view('posts.create', compact('categories'));
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
'category_id' => 'nullable|exists:categories,id',
]);
Post::create([
'title' => $request->title,
'content' => $request->content,
'author' => $request->author,
'category_id' => $request->category_id,
'is_published' => $request->has('is_published'),
]);
return redirect('/posts')->with('success', 'Post berhasil dibuat!');
}
public function show($id)
{
$post = Post::with('category')->findOrFail($id);
return view('posts.show', compact('post'));
}
public function edit($id)
{
$post = Post::findOrFail($id);
$categories = Category::all();
return view('posts.edit', compact('post', 'categories'));
}
public function update(Request $request, $id)
{
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
'category_id' => 'nullable|exists:categories,id',
]);
$post = Post::findOrFail($id);
$post->update([
'title' => $request->title,
'content' => $request->content,
'author' => $request->author,
'category_id' => $request->category_id,
'is_published' => $request->has('is_published'),
]);
return redirect('/posts')->with('success', 'Post berhasil diupdate!');
}
public function destroy($id)
{
$post = Post::findOrFail($id);
$post->delete();
return redirect('/posts')->with('success', 'Post berhasil dihapus!');
}
}
EOF
Update view index tambah filter kategori:
cat > ~/laravel.anekakebutuhan.com/resources/views/posts/index.blade.php << 'EOF'
<x-guest-layout>
<h1 class="text-2xl font-bold text-gray-800 mb-2">Daftar Post</h1>
<a href="/posts/create" style="display:inline-block;background:#4f46e5;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;margin-bottom:16px;text-decoration:none;">+ Buat Post</a>
{{-- Filter Kategori --}}
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px;">
<a href="/posts" style="padding:6px 12px;border-radius:20px;font-size:13px;text-decoration:none;
{{ !$selectedCategory ? 'background:#4f46e5;color:#fff;' : 'background:#e5e7eb;color:#374151;' }}">
Semua
</a>
@foreach($categories as $category)
<a href="/posts?category={{ $category->slug }}" style="padding:6px 12px;border-radius:20px;font-size:13px;text-decoration:none;
{{ $selectedCategory == $category->slug ? 'background:#4f46e5;color:#fff;' : 'background:#e5e7eb;color:#374151;' }}">
{{ $category->name }}
</a>
@endforeach
</div>
@if(session('success'))
<div style="background:#dcfce7;color:#15803d;padding:10px;border-radius:6px;margin-bottom:16px;">{{ session('success') }}</div>
@endif
@forelse($posts as $post)
<div style="background:#fff;padding:16px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px;">
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
<div>
<h2 style="font-size:18px;font-weight:600;">{{ $post->title }}</h2>
<p style="font-size:13px;color:#6b7280;">
oleh {{ $post->author }} · {{ $post->created_at->diffForHumans() }}
@if($post->category)
· <span style="background:#e0e7ff;color:#4f46e5;padding:1px 6px;border-radius:4px;">{{ $post->category->name }}</span>
@endif
@if(!$post->is_published)
<span style="background:#fef9c3;color:#a16207;padding:1px 6px;border-radius:4px;font-size:11px;">Draft</span>
@endif
</p>
<p style="color:#374151;margin-top:8px;">{{ Str::limit($post->content, 100) }}</p>
</div>
<div style="display:flex;gap:8px;margin-left:16px;">
<a href="/posts/{{ $post->id }}" style="color:#4f46e5;font-size:13px;">Lihat</a>
<a href="/posts/{{ $post->id }}/edit" style="color:#d97706;font-size:13px;">Edit</a>
<form method="POST" action="/posts/{{ $post->id }}" onsubmit="return confirm('Hapus post ini?')">
@csrf
@method('DELETE')
<button type="submit" style="color:#dc2626;font-size:13px;background:none;border:none;cursor:pointer;">Hapus</button>
</form>
</div>
</div>
</div>
@empty
<div style="text-align:center;color:#6b7280;padding:32px;">Tidak ada post di kategori ini.</div>
@endforelse
<div style="margin-top:16px;">
{{ $posts->links() }}
</div>
</x-guest-layout>
EOF
Coba akses https://laravel.anekakebutuhan.com/posts — seharusnya muncul tombol filter kategori di atas! 👀
A: 🎉 Filter by Category berjalan sempurna!
Tombol filter kategori tampil rapi dengan: - ✅ "Semua" aktif (warna ungu) - ✅ Teknologi, Bisnis, Kesehatan, Pendidikan sebagai filter - ✅ Label kategori di tiap post
Coba klik salah satu kategori — misalnya "Kesehatan" — pastikan hanya post kategori Kesehatan yang tampil! 👀
A: 🎉 Filter by Category berfungsi sempurna!
Mau lanjut ke mana?
- Search — fitur pencarian post by judul/konten
- Category CRUD — kelola kategori dari browser
- Tampilan show post — perbaiki halaman detail post
- Istirahat dulu 😄
A:
Update PostController dan view untuk tambah fitur search:
# Update method index - tambah search
sed -i 's/\/\/ Filter by category/\/\/ Filter by search\n if ($request->search) {\n $query->where(function($q) use ($request) {\n $q->where("title", "like", "%{$request->search}%")\n ->orWhere("content", "like", "%{$request->search}%");\n });\n }\n\n \/\/ Filter by category/' ~/laravel.anekakebutuhan.com/app/Http/Controllers/PostController.php
# Tambah search ke compact
sed -i 's/compact("posts", "categories", "selectedCategory")/compact("posts", "categories", "selectedCategory", "request")/' ~/laravel.anekakebutuhan.com/app/Http/Controllers/PostController.php
Cara lebih aman — update langsung method index:
cat > ~/laravel.anekakebutuhan.com/app/Http/Controllers/PostController.php << 'EOF'
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(Request $request)
{
$categories = Category::all();
$query = Post::with('category')->latest();
// Filter by search
if ($request->search) {
$query->where(function($q) use ($request) {
$q->where('title', 'like', "%{$request->search}%")
->orWhere('content', 'like', "%{$request->search}%");
});
}
// Filter by category
if ($request->category) {
$query->whereHas('category', function($q) use ($request) {
$q->where('slug', $request->category);
});
}
$posts = $query->paginate(5)->withQueryString();
$selectedCategory = $request->category;
$search = $request->search;
return view('posts.index', compact('posts', 'categories', 'selectedCategory', 'search'));
}
public function create()
{
$categories = Category::all();
return view('posts.create', compact('categories'));
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
'category_id' => 'nullable|exists:categories,id',
]);
Post::create([
'title' => $request->title,
'content' => $request->content,
'author' => $request->author,
'category_id' => $request->category_id,
'is_published' => $request->has('is_published'),
]);
return redirect('/posts')->with('success', 'Post berhasil dibuat!');
}
public function show($id)
{
$post = Post::with('category')->findOrFail($id);
return view('posts.show', compact('post'));
}
public function edit($id)
{
$post = Post::findOrFail($id);
$categories = Category::all();
return view('posts.edit', compact('post', 'categories'));
}
public function update(Request $request, $id)
{
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
'category_id' => 'nullable|exists:categories,id',
]);
$post = Post::findOrFail($id);
$post->update([
'title' => $request->title,
'content' => $request->content,
'author' => $request->author,
'category_id' => $request->category_id,
'is_published' => $request->has('is_published'),
]);
return redirect('/posts')->with('success', 'Post berhasil diupdate!');
}
public function destroy($id)
{
$post = Post::findOrFail($id);
$post->delete();
return redirect('/posts')->with('success', 'Post berhasil dihapus!');
}
}
EOF
Lalu tambah form search di view:
cat > ~/laravel.anekakebutuhan.com/resources/views/posts/index.blade.php << 'EOF'
<x-guest-layout>
<h1 class="text-2xl font-bold text-gray-800 mb-2">Daftar Post</h1>
<a href="/posts/create" style="display:inline-block;background:#4f46e5;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;margin-bottom:16px;text-decoration:none;">+ Buat Post</a>
{{-- Search Form --}}
<form method="GET" action="/posts" style="margin-bottom:16px;display:flex;gap:8px;">
@if($selectedCategory)
<input type="hidden" name="category" value="{{ $selectedCategory }}">
@endif
<input type="text" name="search" value="{{ $search ?? '' }}"
placeholder="Cari post..."
style="flex:1;border:1px solid #d1d5db;border-radius:6px;padding:8px 12px;font-size:14px;">
<button type="submit" style="background:#4f46e5;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;border:none;cursor:pointer;">Cari</button>
@if($search)
<a href="/posts{{ $selectedCategory ? '?category='.$selectedCategory : '' }}"
style="background:#e5e7eb;color:#374151;padding:8px 12px;border-radius:6px;font-size:14px;text-decoration:none;">✕</a>
@endif
</form>
{{-- Filter Kategori --}}
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px;">
<a href="/posts{{ $search ? '?search='.$search : '' }}" style="padding:6px 12px;border-radius:20px;font-size:13px;text-decoration:none;
{{ !$selectedCategory ? 'background:#4f46e5;color:#fff;' : 'background:#e5e7eb;color:#374151;' }}">
Semua
</a>
@foreach($categories as $category)
<a href="/posts?category={{ $category->slug }}{{ $search ? '&search='.$search : '' }}" style="padding:6px 12px;border-radius:20px;font-size:13px;text-decoration:none;
{{ $selectedCategory == $category->slug ? 'background:#4f46e5;color:#fff;' : 'background:#e5e7eb;color:#374151;' }}">
{{ $category->name }}
</a>
@endforeach
</div>
@if($search)
<p style="font-size:13px;color:#6b7280;margin-bottom:12px;">Hasil pencarian: <strong>"{{ $search }}"</strong> — {{ $posts->total() }} post ditemukan</p>
@endif
@if(session('success'))
<div style="background:#dcfce7;color:#15803d;padding:10px;border-radius:6px;margin-bottom:16px;">{{ session('success') }}</div>
@endif
@forelse($posts as $post)
<div style="background:#fff;padding:16px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px;">
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
<div>
<h2 style="font-size:18px;font-weight:600;">{{ $post->title }}</h2>
<p style="font-size:13px;color:#6b7280;">
oleh {{ $post->author }} · {{ $post->created_at->diffForHumans() }}
@if($post->category)
· <span style="background:#e0e7ff;color:#4f46e5;padding:1px 6px;border-radius:4px;">{{ $post->category->name }}</span>
@endif
@if(!$post->is_published)
<span style="background:#fef9c3;color:#a16207;padding:1px 6px;border-radius:4px;font-size:11px;">Draft</span>
@endif
</p>
<p style="color:#374151;margin-top:8px;">{{ Str::limit($post->content, 100) }}</p>
</div>
<div style="display:flex;gap:8px;margin-left:16px;">
<a href="/posts/{{ $post->id }}" style="color:#4f46e5;font-size:13px;">Lihat</a>
<a href="/posts/{{ $post->id }}/edit" style="color:#d97706;font-size:13px;">Edit</a>
<form method="POST" action="/posts/{{ $post->id }}" onsubmit="return confirm('Hapus post ini?')">
@csrf
@method('DELETE')
<button type="submit" style="color:#dc2626;font-size:13px;background:none;border:none;cursor:pointer;">Hapus</button>
</form>
</div>
</div>
</div>
@empty
<div style="text-align:center;color:#6b7280;padding:32px;">Tidak ada post ditemukan.</div>
@endforelse
<div style="margin-top:16px;">
{{ $posts->links() }}
</div>
</x-guest-layout>
EOF
Coba akses https://laravel.anekakebutuhan.com/posts — cari kata apapun di kolom search! 👀
A:
🎉 Search + Filter Category semuanya tampil sempurna!
Coba test search — ketik kata apapun di kolom "Cari post..." lalu klik Cari. Kabari hasilnya! 👀
A: 🎉 Search berfungsi sempurna!
Terlihat: - ✅ Keyword "ren" → 6 post ditemukan - ✅ Info hasil pencarian muncul - ✅ Tombol ✕ untuk reset search - ✅ Filter kategori tetap bisa dikombinasikan dengan search
Recap fitur yang sudah ada di app Laravel kamu:
| Fitur | Status |
|---|---|
| CRUD Post lengkap | ✅ |
| Kategori & Relasi | ✅ |
| Pagination | ✅ |
| Filter by Category | ✅ |
| Search | ✅ |
| Auth & Middleware | ✅ |
Ini sudah setara aplikasi blog yang cukup lengkap! 🚀
Mau lanjut ke mana?
- Category CRUD — kelola kategori dari browser
- Tampilan show post — perbaiki halaman detail post
- API endpoint — buat REST API untuk posts
- Istirahat dulu 😄
A: Buat CategoryController:
cd ~/laravel.anekakebutuhan.com
php artisan make:controller CategoryController
Kabari hasilnya! 👀
A: Isi CategoryController:
cat > ~/laravel.anekakebutuhan.com/app/Http/Controllers/CategoryController.php << 'EOF'
<?php
namespace App\Http\Controllers;
use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class CategoryController extends Controller
{
public function index()
{
$categories = Category::withCount('posts')->latest()->get();
return view('categories.index', compact('categories'));
}
public function create()
{
return view('categories.create');
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|min:3|unique:categories,name',
'description' => 'nullable',
]);
Category::create([
'name' => $request->name,
'slug' => Str::slug($request->name),
'description' => $request->description,
]);
return redirect('/categories')->with('success', 'Kategori berhasil dibuat!');
}
public function edit($id)
{
$category = Category::findOrFail($id);
return view('categories.edit', compact('category'));
}
public function update(Request $request, $id)
{
$category = Category::findOrFail($id);
$request->validate([
'name' => 'required|min:3|unique:categories,name,' . $id,
'description' => 'nullable',
]);
$category->update([
'name' => $request->name,
'slug' => Str::slug($request->name),
'description' => $request->description,
]);
return redirect('/categories')->with('success', 'Kategori berhasil diupdate!');
}
public function destroy($id)
{
$category = Category::findOrFail($id);
$category->delete();
return redirect('/categories')->with('success', 'Kategori berhasil dihapus!');
}
}
EOF
Tambah routes:
cat >> ~/laravel.anekakebutuhan.com/routes/web.php << 'EOF'
// Routes Category
Route::get('/categories', [App\Http\Controllers\CategoryController::class, 'index']);
Route::middleware('auth')->group(function () {
Route::resource('categories', App\Http\Controllers\CategoryController::class)
->except(['index', 'show']);
});
EOF
Buat views:!!!! errror kode, liat asli !!!
lanjut dulu:
Q: ok, saya test, berhasil/berfungsi A: 🎉 Category CRUD lengkap dan berfungsi!
Recap App Laravel yang Sudah Dibangun:
| Fitur | Status |
|---|---|
| CRUD Post | ✅ |
| CRUD Category | ✅ |
| Relasi Post ↔ Category | ✅ |
| Filter by Category | ✅ |
| Search | ✅ |
| Pagination | ✅ |
| Auth & Middleware | ✅ |
| Seeder & Factory | ✅ |
Ini sudah setara aplikasi blog yang production-ready! 🚀
Mau lanjut ke mana?
- REST API — buat API endpoint untuk posts
- Tampilan show post — perbaiki halaman detail post
- User profile — edit nama & password user
- Istirahat / wrap up hari ini 😄
Q: no 4 dulu, ingatkan nanti poin 1, 2 ,3
saya akan offline
A: Siap! Istirahat dulu, sudah banyak yang dikerjakan hari ini! 😄
Reminder untuk Sesi Berikutnya:
- REST API — buat API endpoint untuk posts
- Tampilan show post — perbaiki halaman detail post
- User profile — edit nama & password user
Sampai jumpa di sesi berikutnya! 🙏 A: Buat API endpoint untuk posts:
cd ~/laravel.anekakebutuhan.com
php artisan make:controller Api/PostController
Kabari hasilnya! 👀 Q: INFO Controller [app/Http/Controllers/Api/PostController.php] created successfully.
A: Isi API PostController:
cat > ~/laravel.anekakebutuhan.com/app/Http/Controllers/Api/PostController.php << 'EOF'
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(Request $request)
{
$query = Post::with('category')->latest();
if ($request->search) {
$query->where('title', 'like', "%{$request->search}%");
}
if ($request->category) {
$query->whereHas('category', function($q) use ($request) {
$q->where('slug', $request->category);
});
}
$posts = $query->paginate(10);
return response()->json([
'status' => 'ok',
'data' => $posts,
]);
}
public function show($id)
{
$post = Post::with('category')->findOrFail($id);
return response()->json([
'status' => 'ok',
'data' => $post,
]);
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
]);
$post = Post::create([
'title' => $request->title,
'content' => $request->content,
'author' => $request->author,
'category_id' => $request->category_id,
'is_published' => $request->is_published ?? false,
]);
return response()->json([
'status' => 'ok',
'message' => 'Post berhasil dibuat',
'data' => $post,
], 201);
}
public function update(Request $request, $id)
{
$post = Post::findOrFail($id);
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
]);
$post->update($request->all());
return response()->json([
'status' => 'ok',
'message' => 'Post berhasil diupdate',
'data' => $post,
]);
}
public function destroy($id)
{
$post = Post::findOrFail($id);
$post->delete();
return response()->json([
'status' => 'ok',
'message' => 'Post berhasil dihapus',
]);
}
}
EOF
Tambah routes API:
cat >> ~/laravel.anekakebutuhan.com/routes/api.php << 'EOF'
// API Posts
Route::apiResource('posts', App\Http\Controllers\Api\PostController::class);
EOF
Kabari hasilnya, lalu test akses:
https://laravel.anekakebutuhan.com/api/posts
👀
A: Tampilan posts sudah bagus, ada kategori "umum" baru juga!
Sekarang test API endpoint — buka browser atau gunakan curl:
# Test API via curl
curl https://laravel.anekakebutuhan.com/api/posts
Atau buka langsung di browser:
https://laravel.anekakebutuhan.com/api/posts
Kabari hasilnya! 👀
A: Ada 2 masalah:
- File Api/PostController.php tidak tersimpan dengan benar — terlihat dari log terminal ada
^C(interrupted) - Route API 404 — Laravel 11+ file
api.phpperlu diaktifkan dulu
Mari fix keduanya:
cd ~/laravel.anekakebutuhan.com
# 1. Aktifkan API routes di Laravel 11+
php artisan install:api
Kabari hasilnya — mungkin ada pertanyaan konfirmasi, jawab yes! 👀
A: Sanctum terinstall! Sekarang fix file Api/PostController yang tadi interrupted:
cat > ~/laravel.anekakebutuhan.com/app/Http/Controllers/Api/PostController.php << 'EOF'
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(Request $request)
{
$query = Post::with('category')->latest();
if ($request->search) {
$query->where('title', 'like', "%{$request->search}%");
}
if ($request->category) {
$query->whereHas('category', function($q) use ($request) {
$q->where('slug', $request->category);
});
}
$posts = $query->paginate(10);
return response()->json([
'status' => 'ok',
'data' => $posts,
]);
}
public function show($id)
{
$post = Post::with('category')->findOrFail($id);
return response()->json([
'status' => 'ok',
'data' => $post,
]);
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
]);
$post = Post::create([
'title' => $request->title,
'content' => $request->content,
'author' => $request->author,
'category_id' => $request->category_id,
'is_published' => $request->is_published ?? false,
]);
return response()->json([
'status' => 'ok',
'message' => 'Post berhasil dibuat',
'data' => $post,
], 201);
}
public function update(Request $request, $id)
{
$post = Post::findOrFail($id);
$request->validate([
'title' => 'required|min:3',
'content' => 'required|min:10',
'author' => 'required',
]);
$post->update($request->all());
return response()->json([
'status' => 'ok',
'message' => 'Post berhasil diupdate',
'data' => $post,
]);
}
public function destroy($id)
{
$post = Post::findOrFail($id);
$post->delete();
return response()->json([
'status' => 'ok',
'message' => 'Post berhasil dihapus',
]);
}
}
EOF
Coba akses lagi:
https://laravel.anekakebutuhan.com/api/posts
Kabari hasilnya! 👀
A:
Cek isi file routes/api.php:
cat ~/laravel.anekakebutuhan.com/routes/api.php
Kabari hasilnya! 👀
A: File api.php terlalu minimal — tidak ada header yang benar. Timpa dengan lengkap:
cat > ~/laravel.anekakebutuhan.com/routes/api.php << 'EOF'
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/test', function () {
return response()->json(['status' => 'API ok!']);
});
Route::apiResource('posts', App\Http\Controllers\Api\PostController::class);
EOF
Lalu test dulu endpoint sederhana:
https://laravel.anekakebutuhan.com/api/test
Kabari hasilnya! 👀
A:
Berarti API route belum terdaftar sama sekali. Cek apakah api.php sudah di-bootstrap:
cat ~/laravel.anekakebutuhan.com/bootstrap/app.php
Kabari isinya! 👀
A:
Ketemu! api.php belum didaftarkan di bootstrap/app.php. Tambahkan:
cat > ~/laravel.anekakebutuhan.com/bootstrap/app.php << 'EOF'
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();
EOF
Coba akses lagi https://laravel.anekakebutuhan.com/api/test! 👀
A: Cek apakah cache route perlu di-clear:
cd ~/laravel.anekakebutuhan.com
php artisan route:clear
php artisan cache:clear
php artisan route:list | grep api
Kabari hasilnya! 👀
A: Cek route list untuk api:
php artisan route:list | grep api
Kabari hasilnya! 👀
A:
Route API tidak terdaftar sama sekali. Cek apakah file api.php terbaca dengan benar:
cat ~/laravel.anekakebutuhan.com/routes/api.php
cat ~/laravel.anekakebutuhan.com/bootstrap/app.php | grep api
Kabari hasilnya! 👀
A: Kedua file tidak mengeluarkan output — berarti file kosong atau tidak terbaca. Cek:
ls -la ~/laravel.anekakebutuhan.com/routes/
ls -la ~/laravel.anekakebutuhan.com/bootstrap/app.php
Kabari hasilnya! 👀
A:
File api.php ada dan benar. Masalahnya bootstrap/app.php tidak mengandung kata api — berarti perubahan tadi tidak tersimpan. Cek isi lengkapnya:
cat ~/laravel.anekakebutuhan.com/bootstrap/app.php
Kabari hasilnya! 👀
A:
File bootstrap/app.php belum terupdate — baris api: tidak ada. Coba tulis ulang dengan cara berbeda:
cd ~/laravel.anekakebutuhan.com
php -r "
\$content = file_get_contents('bootstrap/app.php');
\$content = str_replace(
\"web: __DIR__.'/../routes/web.php',\",
\"web: __DIR__.'/../routes/web.php',\n api: __DIR__.'/../routes/api.php',\",
\$content
);
file_put_contents('bootstrap/app.php', \$content);
echo 'Done!';
"
# Verifikasi
cat bootstrap/app.php | grep api
Kabari hasilnya! 👀
A: Sekarang ada! Coba clear cache dan test lagi:
cd ~/laravel.anekakebutuhan.com
php artisan route:clear
php artisan cache:clear
# Cek route list
php artisan route:list | grep api
# Test di browser
curl https://laravel.anekakebutuhan.com/api/test
Kabari hasilnya! 👀
A: 🎉 API berjalan sempurna!
- ✅ Semua route API terdaftar
- ✅
curl /api/test→{"status":"API ok!"}
Sekarang test endpoint posts:
https://laravel.anekakebutuhan.com/api/posts
https://laravel.anekakebutuhan.com/api/posts/1
Kabari hasilnya! 👀
A: 🎉🎉🎉 REST API berjalan sempurna!
Terlihat:
- ✅ /api/posts — list semua post dengan pagination & relasi category
- ✅ /api/posts/1 — detail post dengan category lengkap (id, name, slug, description)
- ✅ Response format JSON yang bersih {status, data}
Status App Laravel Sekarang — Sangat Lengkap!
| Fitur | Status |
|---|---|
| CRUD Post & Category | ✅ |
| Relasi Eloquent | ✅ |
| Filter, Search, Pagination | ✅ |
| Auth & Middleware | ✅ |
| Seeder & Factory | ✅ |
| REST API | ✅ |
Reminder sisa yang belum dikerjakan: - No 2 — Tampilan show post - No 3 — User profile
Mau lanjut atau istirahat dulu? 😄
Q:
lanjut no 2
A:
Cek dulu isi show.blade.php yang ada sekarang:
cat ~/laravel.anekakebutuhan.com/resources/views/posts/show.blade.php
Kabari hasilnya! 👀
A: Tampilan masih sangat sederhana. Update dengan inline style yang lebih rapi:
cat > ~/laravel.anekakebutuhan.com/resources/views/posts/show.blade.php << 'EOF'
<x-guest-layout>
{{-- Breadcrumb --}}
<div style="margin-bottom:16px;">
<a href="/posts" style="color:#4f46e5;text-decoration:none;font-size:14px;">← Kembali ke Daftar Post</a>
</div>
{{-- Header Post --}}
<div style="background:#fff;padding:24px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px;">
{{-- Kategori --}}
@if($post->category)
<span style="background:#e0e7ff;color:#4f46e5;padding:3px 10px;border-radius:20px;font-size:12px;font-weight:500;">
{{ $post->category->name }}
</span>
@endif
{{-- Status --}}
@if(!$post->is_published)
<span style="background:#fef9c3;color:#a16207;padding:3px 10px;border-radius:20px;font-size:12px;font-weight:500;margin-left:4px;">
Draft
</span>
@endif
{{-- Judul --}}
<h1 style="font-size:24px;font-weight:700;color:#111827;margin-top:12px;margin-bottom:8px;">
{{ $post->title }}
</h1>
{{-- Meta --}}
<div style="font-size:13px;color:#6b7280;display:flex;gap:16px;flex-wrap:wrap;">
<span>✍️ {{ $post->author }}</span>
<span>📅 {{ $post->created_at->format('d M Y, H:i') }}</span>
<span>🕐 {{ $post->created_at->diffForHumans() }}</span>
@if($post->updated_at != $post->created_at)
<span>✏️ Diedit {{ $post->updated_at->diffForHumans() }}</span>
@endif
</div>
</div>
{{-- Konten --}}
<div style="background:#fff;padding:24px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px;">
<div style="font-size:15px;color:#374151;line-height:1.8;">
{!! nl2br(e($post->content)) !!}
</div>
</div>
{{-- Action Buttons --}}
@auth
<div style="display:flex;gap:8px;">
<a href="/posts/{{ $post->id }}/edit" style="background:#d97706;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;text-decoration:none;">✏️ Edit Post</a>
<form method="POST" action="/posts/{{ $post->id }}" onsubmit="return confirm('Hapus post ini?')">
@csrf
@method('DELETE')
<button type="submit" style="background:#dc2626;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;border:none;cursor:pointer;">🗑️ Hapus Post</button>
</form>
</div>
@endauth
</x-guest-layout>
EOF
Coba akses salah satu post:
https://laravel.anekakebutuhan.com/posts/1
Kabari hasilnya! 👀
A:
Ini masih tampilan API JSON — berarti browser masih di /api/posts/1.
Coba akses URL yang benar:
https://laravel.anekakebutuhan.com/posts/1
Tanpa /api/ — itu halaman web biasa! 👀
A: 🎉 Tampilan show post sangat bagus!
Terlihat: - ✅ Badge kategori "Kesehatan" - ✅ Judul besar dan bold - ✅ Meta info — author, tanggal, waktu relatif, info diedit - ✅ Konten dengan line spacing yang nyaman - ✅ Tombol Edit Post dan Hapus Post (hanya muncul kalau login)
Tinggal No 3 — User Profile yang belum dikerjakan.
Mau lanjut sekarang atau istirahat dulu? 😄
A: Buat ProfileController:
cd ~/laravel.anekakebutuhan.com
php artisan make:controller UserProfileController
Kabari hasilnya! 👀 A: Isi UserProfileController:
cat > ~/laravel.anekakebutuhan.com/app/Http/Controllers/UserProfileController.php << 'EOF'
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
class UserProfileController extends Controller
{
public function edit()
{
$user = Auth::user();
return view('user.profile', compact('user'));
}
public function updateProfile(Request $request)
{
$user = Auth::user();
$request->validate([
'name' => 'required|min:3',
'email' => 'required|email|unique:users,email,' . $user->id,
]);
$user->update([
'name' => $request->name,
'email' => $request->email,
]);
return redirect('/profile')->with('success', 'Profil berhasil diupdate!');
}
public function updatePassword(Request $request)
{
$request->validate([
'current_password' => 'required',
'password' => ['required', 'confirmed', Password::min(8)],
]);
$user = Auth::user();
if (!Hash::check($request->current_password, $user->password)) {
return back()->withErrors(['current_password' => 'Password saat ini salah!']);
}
$user->update([
'password' => Hash::make($request->password),
]);
return redirect('/profile')->with('success', 'Password berhasil diubah!');
}
}
EOF
Tambah routes:
cat >> ~/laravel.anekakebutuhan.com/routes/web.php << 'EOF'
// User Profile
Route::middleware('auth')->group(function () {
Route::get('/profile', [App\Http\Controllers\UserProfileController::class, 'edit']);
Route::put('/profile', [App\Http\Controllers\UserProfileController::class, 'updateProfile']);
Route::put('/profile/password', [App\Http\Controllers\UserProfileController::class, 'updatePassword']);
});
EOF
Buat view:
mkdir -p ~/laravel.anekakebutuhan.com/resources/views/user
cat > ~/laravel.anekakebutuhan.com/resources/views/user/profile.blade.php << 'EOF'
<x-guest-layout>
<h1 class="text-2xl font-bold text-gray-800 mb-4">Profil Saya</h1>
@if(session('success'))
<div style="background:#dcfce7;color:#15803d;padding:10px;border-radius:6px;margin-bottom:16px;">{{ session('success') }}</div>
@endif
{{-- Update Profile --}}
<div style="background:#fff;padding:24px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px;">
<h2 style="font-size:16px;font-weight:600;margin-bottom:16px;">Info Profil</h2>
<form method="POST" action="/profile">
@csrf
@method('PUT')
<div style="margin-bottom:16px;">
<label style="display:block;font-size:14px;font-weight:500;margin-bottom:4px;">Nama</label>
<input type="text" name="name" value="{{ old('name', $user->name) }}"
style="width:100%;border:1px solid #d1d5db;border-radius:6px;padding:8px 12px;font-size:14px;">
@error('name') <p style="color:#dc2626;font-size:12px;margin-top:4px;">{{ $message }}</p> @enderror
</div>
<div style="margin-bottom:16px;">
<label style="display:block;font-size:14px;font-weight:500;margin-bottom:4px;">Email</label>
<input type="email" name="email" value="{{ old('email', $user->email) }}"
style="width:100%;border:1px solid #d1d5db;border-radius:6px;padding:8px 12px;font-size:14px;">
@error('email') <p style="color:#dc2626;font-size:12px;margin-top:4px;">{{ $message }}</p> @enderror
</div>
<button type="submit" style="background:#4f46e5;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;border:none;cursor:pointer;">Update Profil</button>
</form>
</div>
{{-- Update Password --}}
<div style="background:#fff;padding:24px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1);">
<h2 style="font-size:16px;font-weight:600;margin-bottom:16px;">Ubah Password</h2>
<form method="POST" action="/profile/password">
@csrf
@method('PUT')
<div style="margin-bottom:16px;">
<label style="display:block;font-size:14px;font-weight:500;margin-bottom:4px;">Password Saat Ini</label>
<input type="password" name="current_password"
style="width:100%;border:1px solid #d1d5db;border-radius:6px;padding:8px 12px;font-size:14px;">
@error('current_password') <p style="color:#dc2626;font-size:12px;margin-top:4px;">{{ $message }}</p> @enderror
</div>
<div style="margin-bottom:16px;">
<label style="display:block;font-size:14px;font-weight:500;margin-bottom:4px;">Password Baru</label>
<input type="password" name="password"
style="width:100%;border:1px solid #d1d5db;border-radius:6px;padding:8px 12px;font-size:14px;">
@error('password') <p style="color:#dc2626;font-size:12px;margin-top:4px;">{{ $message }}</p> @enderror
</div>
<div style="margin-bottom:16px;">
<label style="display:block;font-size:14px;font-weight:500;margin-bottom:4px;">Konfirmasi Password Baru</label>
<input type="password" name="password_confirmation"
style="width:100%;border:1px solid #d1d5db;border-radius:6px;padding:8px 12px;font-size:14px;">
</div>
<button type="submit" style="background:#dc2626;color:#fff;padding:8px 16px;border-radius:6px;font-size:14px;border:none;cursor:pointer;">Ubah Password</button>
</form>
</div>
</x-guest-layout>
EOF
Coba akses https://laravel.anekakebutuhan.com/profile setelah login! 👀
A: 🎉 User Profile tampil sempurna!
Terlihat: - ✅ Info Profil — nama dan email sudah terisi otomatis - ✅ Tombol Update Profil - ✅ Form Ubah Password lengkap
Coba test kedua fungsi — update nama dan ubah password! 👀
A: Siap, selamat berangkat! 🙏
Recap Lengkap App Laravel yang Sudah Dibangun:
| Fitur | Status |
|---|---|
| CRUD Post & Category | ✅ |
| Relasi Eloquent | ✅ |
| Filter, Search, Pagination | ✅ |
| Auth & Middleware | ✅ |
| Seeder & Factory | ✅ |
| REST API | ✅ |
| Tampilan Show Post | ✅ |
| User Profile | ✅ |
Semua reminder sudah selesai dikerjakan! 🎉
Kalau ada yang ingin dilanjutkan setelah kembali, tinggal lanjutkan chat ini. Sampai jumpa! 🚀
A: Selamat datang kembali! 😄
Benar, yang sudah selesai: - ✅ Node.js + Express - ✅ Laravel + Breeze + MySQL - ✅ Python + Flask (sudah jalan, tapi belum dieksplorasi lebih lanjut)
Yang belum dieksplorasi: - ⏳ Python Flask — masih basic "Hello World", belum CRUD/API - ⏳ Ruby — sudah ada Ruby 3.0.7 tapi belum disentuh sama sekali
Mau mulai dari mana — Python Flask atau Ruby? 😄
A:
Flask 3.1.3 sudah terinstall. Sekarang kita eksplorasi lebih lanjut.
Mau mulai dari mana untuk Python Flask?
- REST API — buat API endpoint seperti di Laravel
- CRUD dengan SQLite — database sederhana tanpa setup MySQL
- Template Jinja2 — tampilan HTML dengan Flask
- Flask + MySQL — koneksi ke database MySQL
Yang mana? 😄
A: Install Flask-SQLAlchemy dan dependencies:
source ~/virtualenv/python.anekakebutuhan.com/3.9/bin/activate && cd ~/python.anekakebutuhan.com
pip install flask-sqlalchemy flask-marshmallow marshmallow-sqlalchemy
Kabari hasilnya! 👀 A: Sekarang buat REST API lengkap dengan Flask:
cat > ~/python.anekakebutuhan.com/app.py << 'EOF'
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import os
app = Flask(__name__)
# Konfigurasi SQLite
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Model
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
author = db.Column(db.String(100), default='Anonymous')
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content,
'author': self.author,
'created_at': self.created_at.isoformat(),
}
# Buat tabel
with app.app_context():
db.create_all()
# Routes
@app.route('/')
def index():
return jsonify({
'status': 'ok',
'message': 'Flask REST API',
'version': '1.0',
'endpoints': {
'GET /api/posts': 'List semua post',
'POST /api/posts': 'Buat post baru',
'GET /api/posts/<id>': 'Detail post',
'PUT /api/posts/<id>': 'Update post',
'DELETE /api/posts/<id>': 'Hapus post',
}
})
@app.route('/api/posts', methods=['GET'])
def get_posts():
search = request.args.get('search', '')
if search:
posts = Post.query.filter(Post.title.contains(search)).order_by(Post.created_at.desc()).all()
else:
posts = Post.query.order_by(Post.created_at.desc()).all()
return jsonify({
'status': 'ok',
'total': len(posts),
'data': [p.to_dict() for p in posts],
})
@app.route('/api/posts/<int:id>', methods=['GET'])
def get_post(id):
post = Post.query.get_or_404(id)
return jsonify({'status': 'ok', 'data': post.to_dict()})
@app.route('/api/posts', methods=['POST'])
def create_post():
data = request.get_json()
if not data or not data.get('title') or not data.get('content'):
return jsonify({'status': 'error', 'message': 'title dan content wajib diisi'}), 400
post = Post(
title = data['title'],
content = data['content'],
author = data.get('author', 'Anonymous'),
)
db.session.add(post)
db.session.commit()
return jsonify({'status': 'ok', 'message': 'Post berhasil dibuat', 'data': post.to_dict()}), 201
@app.route('/api/posts/<int:id>', methods=['PUT'])
def update_post(id):
post = Post.query.get_or_404(id)
data = request.get_json()
if not data:
return jsonify({'status': 'error', 'message': 'Data tidak valid'}), 400
post.title = data.get('title', post.title)
post.content = data.get('content', post.content)
post.author = data.get('author', post.author)
db.session.commit()
return jsonify({'status': 'ok', 'message': 'Post berhasil diupdate', 'data': post.to_dict()})
@app.route('/api/posts/<int:id>', methods=['DELETE'])
def delete_post(id):
post = Post.query.get_or_404(id)
db.session.delete(post)
db.session.commit()
return jsonify({'status': 'ok', 'message': 'Post berhasil dihapus'})
if __name__ == '__main__':
app.run()
EOF
Lalu RESTART di cPanel → Setup Python App → Restart.
Kemudian test via curl:
# Test root endpoint
curl https://python.anekakebutuhan.com/
# Buat post pertama
curl -X POST https://python.anekakebutuhan.com/api/posts \
-H "Content-Type: application/json" \
-d '{"title":"Post Pertama Flask","content":"Ini konten dari Flask REST API","author":"Budi"}'
Kabari hasilnya! 👀
A: 🎉 Flask REST API berjalan sempurna!
Terlihat:
- ✅ Root endpoint menampilkan daftar semua endpoint
- ✅ POST /api/posts — post berhasil dibuat dengan id:1
Sekarang test endpoint lainnya:
# Buat beberapa post lagi
curl -X POST https://python.anekakebutuhan.com/api/posts \
-H "Content-Type: application/json" \
-d '{"title":"Belajar Python Flask","content":"Flask adalah framework Python yang ringan dan fleksibel","author":"Siti"}'
curl -X POST https://python.anekakebutuhan.com/api/posts \
-H "Content-Type: application/json" \
-d '{"title":"SQLAlchemy ORM","content":"SQLAlchemy memudahkan interaksi dengan database di Python","author":"Budi"}'
# List semua post
curl https://python.anekakebutuhan.com/api/posts
# Detail post id 1
curl https://python.anekakebutuhan.com/api/posts/1
# Update post id 1
curl -X PUT https://python.anekakebutuhan.com/api/posts/1 \
-H "Content-Type: application/json" \
-d '{"title":"Post Pertama Flask - Updated"}'
# Search
curl https://python.anekakebutuhan.com/api/posts?search=python
Kabari hasilnya! 👀
A: 🎉 Semua endpoint Flask REST API berjalan sempurna!
Terlihat:
- ✅ POST — 3 post berhasil dibuat
- ✅ GET /api/posts — list semua 3 post
- ✅ GET /api/posts/1 — detail post
- ✅ PUT /api/posts/1 — title berhasil diupdate
- ✅ Search ?search=python — 1 post ditemukan (Belajar Python Flask)
Test terakhir — DELETE:
# Hapus post id 3
curl -X DELETE https://python.anekakebutuhan.com/api/posts/3
# Cek list post setelah dihapus
curl https://python.anekakebutuhan.com/api/posts
Kabari hasilnya! 👀
A: 🎉 Flask REST API CRUD lengkap berjalan sempurna!
- ✅ DELETE — post id 3 berhasil dihapus
- ✅ GET — list tinggal 2 post
Perbandingan Laravel vs Flask API:
| Fitur | Laravel | Flask |
|---|---|---|
| Database | MySQL | SQLite |
| ORM | Eloquent | SQLAlchemy |
| Routing | Resource Controller | @app.route |
| Validasi | $request->validate() |
Manual check |
| Response | response()->json() |
jsonify() |
Flask lebih minimalis — kode lebih sedikit tapi lebih manual. Laravel lebih structured — banyak helper tapi lebih verbose.
Mau lanjut eksplorasi Flask lebih jauh atau langsung ke Ruby? 😄 A: Mau eksplorasi Flask ke arah mana?
- Authentication — login/register dengan JWT token
- Blueprint — organisasi kode Flask yang lebih rapi (seperti module)
- Flask + MySQL — ganti SQLite ke MySQL
- Template Jinja2 — tampilan HTML seperti Blade di Laravel
- Error handling & middleware — 404, 500, CORS, dll
Yang mana? 😄
A: Install dependencies JWT:
source ~/virtualenv/python.anekakebutuhan.com/3.9/bin/activate && cd ~/python.anekakebutuhan.com
pip install flask-jwt-extended flask-bcrypt
Kabari hasilnya! 👀
A:
Berhasil! Sekarang update app.py dengan authentication JWT:
cat > ~/python.anekakebutuhan.com/app.py << 'EOF'
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
from flask_bcrypt import Bcrypt
from datetime import datetime, timedelta
import os
app = Flask(__name__)
# Konfigurasi
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['JWT_SECRET_KEY'] = 'flask-lab-secret-key-2026'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=24)
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
jwt = JWTManager(app)
# Models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
password = db.Column(db.String(200), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'email': self.email,
'created_at': self.created_at.isoformat(),
}
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
author = db.Column(db.String(100), default='Anonymous')
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content,
'author': self.author,
'user_id': self.user_id,
'created_at': self.created_at.isoformat(),
}
with app.app_context():
db.create_all()
# ==================
# AUTH ROUTES
# ==================
@app.route('/')
def index():
return jsonify({
'status': 'ok',
'message': 'Flask REST API with JWT Auth',
'endpoints': {
'POST /auth/register': 'Register user baru',
'POST /auth/login': 'Login & dapat token',
'GET /auth/me': 'Info user (perlu token)',
'GET /api/posts': 'List semua post',
'POST /api/posts': 'Buat post (perlu token)',
'PUT /api/posts/<id>':'Update post (perlu token)',
'DELETE /api/posts/<id>':'Hapus post (perlu token)',
}
})
@app.route('/auth/register', methods=['POST'])
def register():
data = request.get_json()
if not data or not data.get('email') or not data.get('password') or not data.get('name'):
return jsonify({'status': 'error', 'message': 'name, email, password wajib diisi'}), 400
if User.query.filter_by(email=data['email']).first():
return jsonify({'status': 'error', 'message': 'Email sudah terdaftar'}), 400
hashed = bcrypt.generate_password_hash(data['password']).decode('utf-8')
user = User(name=data['name'], email=data['email'], password=hashed)
db.session.add(user)
db.session.commit()
token = create_access_token(identity=str(user.id))
return jsonify({
'status': 'ok',
'message': 'Registrasi berhasil',
'token': token,
'user': user.to_dict(),
}), 201
@app.route('/auth/login', methods=['POST'])
def login():
data = request.get_json()
if not data or not data.get('email') or not data.get('password'):
return jsonify({'status': 'error', 'message': 'email dan password wajib diisi'}), 400
user = User.query.filter_by(email=data['email']).first()
if not user or not bcrypt.check_password_hash(user.password, data['password']):
return jsonify({'status': 'error', 'message': 'Email atau password salah'}), 401
token = create_access_token(identity=str(user.id))
return jsonify({
'status': 'ok',
'message': 'Login berhasil',
'token': token,
'user': user.to_dict(),
})
@app.route('/auth/me', methods=['GET'])
@jwt_required()
def me():
user_id = get_jwt_identity()
user = User.query.get(user_id)
return jsonify({'status': 'ok', 'user': user.to_dict()})
# ==================
# POST ROUTES
# ==================
@app.route('/api/posts', methods=['GET'])
def get_posts():
search = request.args.get('search', '')
if search:
posts = Post.query.filter(Post.title.contains(search)).order_by(Post.created_at.desc()).all()
else:
posts = Post.query.order_by(Post.created_at.desc()).all()
return jsonify({'status': 'ok', 'total': len(posts), 'data': [p.to_dict() for p in posts]})
@app.route('/api/posts/<int:id>', methods=['GET'])
def get_post(id):
post = Post.query.get_or_404(id)
return jsonify({'status': 'ok', 'data': post.to_dict()})
@app.route('/api/posts', methods=['POST'])
@jwt_required()
def create_post():
data = request.get_json()
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not data or not data.get('title') or not data.get('content'):
return jsonify({'status': 'error', 'message': 'title dan content wajib diisi'}), 400
post = Post(
title = data['title'],
content = data['content'],
author = user.name,
user_id = user_id,
)
db.session.add(post)
db.session.commit()
return jsonify({'status': 'ok', 'message': 'Post berhasil dibuat', 'data': post.to_dict()}), 201
@app.route('/api/posts/<int:id>', methods=['PUT'])
@jwt_required()
def update_post(id):
post = Post.query.get_or_404(id)
user_id = get_jwt_identity()
if str(post.user_id) != str(user_id):
return jsonify({'status': 'error', 'message': 'Tidak punya akses'}), 403
data = request.get_json()
post.title = data.get('title', post.title)
post.content = data.get('content', post.content)
db.session.commit()
return jsonify({'status': 'ok', 'message': 'Post berhasil diupdate', 'data': post.to_dict()})
@app.route('/api/posts/<int:id>', methods=['DELETE'])
@jwt_required()
def delete_post(id):
post = Post.query.get_or_404(id)
user_id = get_jwt_identity()
if str(post.user_id) != str(user_id):
return jsonify({'status': 'error', 'message': 'Tidak punya akses'}), 403
db.session.delete(post)
db.session.commit()
return jsonify({'status': 'ok', 'message': 'Post berhasil dihapus'})
if __name__ == '__main__':
app.run()
EOF
RESTART di cPanel, lalu test:
# Register user baru
curl -X POST https://python.anekakebutuhan.com/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Budi","email":"budi@test.com","password":"password123"}'
Kabari hasilnya! 👀
A: 🎉 JWT Authentication berhasil!
Register sukses dan dapat token JWT. Sekarang test login dan endpoint yang perlu token:
# Login
curl -X POST https://python.anekakebutuhan.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"budi@test.com","password":"password123"}'
Simpan token yang keluar, lalu kita test endpoint protected. Kabari hasilnya! 👀 A: Login berhasil! Sekarang test endpoint protected dengan token:
# Simpan token ke variable
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc3ODYzNDIyMCwianRpIjoiOTU2NzkzYTAtNjY1YS00ZjU2LWFkYWQtYzc5YzA0ZDkwMWQ5IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjEiLCJuYmYiOjE3Nzg2MzQyMjAsImNzcmYiOiIzNzhlYWM0Ni1mMTJjLTQ0MWItOTgxZS0yMzVjYzdkZmJmZDUiLCJleHAiOjE3Nzg3MjA2MjB9.r6Y2WesWODbj60hGBwaBoMq3ysAwbmQf36his1LMOPE"
# Cek info user
curl https://python.anekakebutuhan.com/auth/me \
-H "Authorization: Bearer $TOKEN"
# Buat post dengan token
curl -X POST https://python.anekakebutuhan.com/api/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title":"Post dengan JWT","content":"Post ini dibuat dengan autentikasi JWT token"}'
# Coba buat post TANPA token (harus ditolak)
curl -X POST https://python.anekakebutuhan.com/api/posts \
-H "Content-Type: application/json" \
-d '{"title":"Post tanpa token","content":"Ini harusnya ditolak"}'
Kabari hasilnya! 👀
A:
/auth/me berhasil! ✅ Dan request tanpa token ditolak dengan benar! ✅
Tapi ada 500 error saat buat post dengan token. Cek error log:
cat ~/python.anekakebutuhan.com/stderr.log
Kabari hasilnya! 👀
A:
Ketemu! Masalahnya — tabel post lama tidak punya kolom user_id. Solusinya hapus database lama dan buat ulang:
cd ~/python.anekakebutuhan.com
# Hapus database lama
rm -f database.db
# Restart di cPanel supaya database dibuat ulang dengan schema baru
Setelah restart, test lagi:
# Register ulang (database baru)
curl -X POST https://python.anekakebutuhan.com/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Budi","email":"budi@test.com","password":"password123"}'
Kabari hasilnya! 👀 A: Berhasil! Sekarang test buat post dengan token baru:
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc3ODYzNDM5NiwianRpIjoiZjk2NDE5YzAtMGI1Yy00NzI4LTg0YTgtNjMxYTE1M2QyYmI3IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjEiLCJuYmYiOjE3Nzg2MzQzOTYsImNzcmYiOiI2OTBiNGZhMy04NzU4LTQ2OWYtOWQxNy02NzZiZTUxOWFmY2IiLCJleHAiOjE3Nzg3MjA3OTZ9.WZaTjwsofQCbf-cdO2gJprnIozriR0Go_SCV0eQhp9k"
# Buat post dengan token
curl -X POST https://python.anekakebutuhan.com/api/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title":"Post dengan JWT","content":"Post ini dibuat dengan autentikasi JWT token"}'
# Cek info user
curl https://python.anekakebutuhan.com/auth/me \
-H "Authorization: Bearer $TOKEN"
Kabari hasilnya! 👀
A: 🎉 JWT Authentication + REST API berjalan sempurna!
Terlihat:
- ✅ Post berhasil dibuat dengan user_id: 1 — terhubung ke user yang login
- ✅ /auth/me mengembalikan info user dari token
Test terakhir — coba akses dengan token yang salah:
# Test token salah (harus ditolak)
curl -X POST https://python.anekakebutuhan.com/api/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer tokenpalsu123" \
-d '{"title":"Test","content":"Harusnya ditolak"}'
# Test hapus post milik orang lain
# Register user kedua dulu
curl -X POST https://python.anekakebutuhan.com/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Siti","email":"siti@test.com","password":"password123"}'
Kabari hasilnya! 👀
A: Hmm, email Siti sudah terdaftar dari database lama. Ganti email:
# Register Siti dengan email baru
curl -X POST https://python.anekakebutuhan.com/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Siti","email":"siti2@test.com","password":"password123"}'
Kabari token Siti — kita test apakah Siti bisa hapus post milik Budi! 👀
A:
Siti dapat token dengan id:3. Sekarang test apakah Siti bisa hapus post milik Budi (post id:1):
TOKEN_SITI="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc3ODY1NTc3MywianRpIjoiYjM4ZGMwNzEtNGI1ZC00ODg4LWJkZjktMGE3YzRlMjMxZmUyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjMiLCJuYmYiOjE3Nzg2NTU3NzMsImNzcmYiOiI5YjhlNThjYy1jOGM5LTQwZmMtYWYzMi1hMDJhN2ZkYzZkODAiLCJleHAiOjE3Nzg3NDIxNzN9.i_BNRTL0a0-f_qLg79Ie0Y0Cd_RgQkO_LbdRA-PwaaY"
# Siti coba hapus post milik Budi (harus ditolak 403)
curl -X DELETE https://python.anekakebutuhan.com/api/posts/1 \
-H "Authorization: Bearer $TOKEN_SITI"
# Siti buat postnya sendiri
curl -X POST https://python.anekakebutuhan.com/api/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN_SITI" \
-d '{"title":"Post milik Siti","content":"Ini post yang dibuat oleh Siti"}'
Kabari hasilnya! 👀
A: 🎉 JWT Authentication + Authorization berjalan sempurna!
Terlihat:
- ✅ Token palsu → "Not enough segments" — ditolak
- ✅ Siti coba hapus post Budi → "Tidak punya akses" (403) — ditolak!
- ✅ Siti buat post sendiri → berhasil dengan user_id: 3
Recap Flask JWT yang sudah dikuasai:
| Fitur | Status |
|---|---|
| Register + Hash password (bcrypt) | ✅ |
| Login + JWT token | ✅ |
Protected endpoint @jwt_required() |
✅ |
| Authorization (cek ownership) | ✅ |
| Token invalid → ditolak | ✅ |
| Token tidak ada → ditolak | ✅ |
Mau lanjut eksplorasi Flask ke: 1. Blueprint — organisasi kode lebih rapi 2. Flask + MySQL — ganti SQLite ke MySQL 3. Template Jinja2 — tampilan HTML 4. Lanjut ke Ruby 😄
A: Blueprint adalah cara mengorganisasi kode Flask menjadi modul-modul terpisah — mirip seperti module/package di Laravel.
Pertama, buat struktur folder:
cd ~/python.anekakebutuhan.com
# Buat struktur folder Blueprint
mkdir -p app/auth
mkdir -p app/posts
mkdir -p app/models
# Buat file __init__.py
touch app/__init__.py
touch app/auth/__init__.py
touch app/posts/__init__.py
touch app/models/__init__.py
Kabari hasilnya! 👀
A:
Normal — mkdir dan touch memang tidak mengeluarkan output kalau berhasil. Verifikasi struktur folder:
find ~/python.anekakebutuhan.com/app -type f
Kabari hasilnya! 👀
A: Struktur folder sudah benar! Sekarang buat file-file Blueprint:
# 1. Model
cat > ~/python.anekakebutuhan.com/app/models/__init__.py << 'EOF'
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
password = db.Column(db.String(200), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
posts = db.relationship('Post', backref='user', lazy=True)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'email': self.email,
'created_at': self.created_at.isoformat(),
}
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
author = db.Column(db.String(100), default='Anonymous')
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content,
'author': self.author,
'user_id': self.user_id,
'created_at': self.created_at.isoformat(),
}
EOF
# 2. Auth Blueprint
cat > ~/python.anekakebutuhan.com/app/auth/__init__.py << 'EOF'
from flask import Blueprint, jsonify, request
from flask_jwt_extended import create_access_token
from flask_bcrypt import Bcrypt
from app.models import db, User
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
bcrypt = Bcrypt()
@auth_bp.route('/register', methods=['POST'])
def register():
data = request.get_json()
if not data or not data.get('email') or not data.get('password') or not data.get('name'):
return jsonify({'status': 'error', 'message': 'name, email, password wajib diisi'}), 400
if User.query.filter_by(email=data['email']).first():
return jsonify({'status': 'error', 'message': 'Email sudah terdaftar'}), 400
hashed = bcrypt.generate_password_hash(data['password']).decode('utf-8')
user = User(name=data['name'], email=data['email'], password=hashed)
db.session.add(user)
db.session.commit()
token = create_access_token(identity=str(user.id))
return jsonify({'status': 'ok', 'message': 'Registrasi berhasil', 'token': token, 'user': user.to_dict()}), 201
@auth_bp.route('/login', methods=['POST'])
def login():
data = request.get_json()
if not data or not data.get('email') or not data.get('password'):
return jsonify({'status': 'error', 'message': 'email dan password wajib diisi'}), 400
user = User.query.filter_by(email=data['email']).first()
if not user or not bcrypt.check_password_hash(user.password, data['password']):
return jsonify({'status': 'error', 'message': 'Email atau password salah'}), 401
token = create_access_token(identity=str(user.id))
return jsonify({'status': 'ok', 'message': 'Login berhasil', 'token': token, 'user': user.to_dict()})
EOF
# 3. Posts Blueprint
cat > ~/python.anekakebutuhan.com/app/posts/__init__.py << 'EOF'
from flask import Blueprint, jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from app.models import db, Post, User
posts_bp = Blueprint('posts', __name__, url_prefix='/api/posts')
@posts_bp.route('', methods=['GET'])
def get_posts():
search = request.args.get('search', '')
if search:
posts = Post.query.filter(Post.title.contains(search)).order_by(Post.created_at.desc()).all()
else:
posts = Post.query.order_by(Post.created_at.desc()).all()
return jsonify({'status': 'ok', 'total': len(posts), 'data': [p.to_dict() for p in posts]})
@posts_bp.route('/<int:id>', methods=['GET'])
def get_post(id):
post = Post.query.get_or_404(id)
return jsonify({'status': 'ok', 'data': post.to_dict()})
@posts_bp.route('', methods=['POST'])
@jwt_required()
def create_post():
data = request.get_json()
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not data or not data.get('title') or not data.get('content'):
return jsonify({'status': 'error', 'message': 'title dan content wajib diisi'}), 400
post = Post(title=data['title'], content=data['content'], author=user.name, user_id=user_id)
db.session.add(post)
db.session.commit()
return jsonify({'status': 'ok', 'message': 'Post berhasil dibuat', 'data': post.to_dict()}), 201
@posts_bp.route('/<int:id>', methods=['PUT'])
@jwt_required()
def update_post(id):
post = Post.query.get_or_404(id)
user_id = get_jwt_identity()
if str(post.user_id) != str(user_id):
return jsonify({'status': 'error', 'message': 'Tidak punya akses'}), 403
data = request.get_json()
post.title = data.get('title', post.title)
post.content = data.get('content', post.content)
db.session.commit()
return jsonify({'status': 'ok', 'message': 'Post berhasil diupdate', 'data': post.to_dict()})
@posts_bp.route('/<int:id>', methods=['DELETE'])
@jwt_required()
def delete_post(id):
post = Post.query.get_or_404(id)
user_id = get_jwt_identity()
if str(post.user_id) != str(user_id):
return jsonify({'status': 'error', 'message': 'Tidak punya akses'}), 403
db.session.delete(post)
db.session.commit()
return jsonify({'status': 'ok', 'message': 'Post berhasil dihapus'})
EOF
# 4. App factory __init__.py
cat > ~/python.anekakebutuhan.com/app/__init__.py << 'EOF'
from flask import Flask, jsonify
from flask_jwt_extended import JWTManager
from flask_bcrypt import Bcrypt
from datetime import timedelta
import os
from app.models import db
from app.auth import auth_bp, bcrypt
from app.posts import posts_bp
def create_app():
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, '..', 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['JWT_SECRET_KEY'] = 'flask-lab-secret-key-2026-32bytes!!'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=24)
db.init_app(app)
bcrypt.init_app(app)
JWTManager(app)
app.register_blueprint(auth_bp)
app.register_blueprint(posts_bp)
@app.route('/')
def index():
return jsonify({
'status': 'ok',
'message': 'Flask REST API with Blueprint',
'modules': ['auth', 'posts'],
})
with app.app_context():
db.create_all()
return app
EOF
# 5. Update app.py utama
cat > ~/python.anekakebutuhan.com/app.py << 'EOF'
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run()
EOF
RESTART di cPanel, lalu test:
curl https://python.anekakebutuhan.com/
Kabari hasilnya! 👀
A:
Hmm, masih response dari app.py lama! Berarti file app.py tidak terupdate dengan benar. Cek:
cat ~/python.anekakebutuhan.com/app.py
Kabari hasilnya! 👀
A:
File app.py sudah benar! Berarti cPanel belum restart dengan benar. Coba restart lagi dari cPanel → Setup Python App → klik Restart, tunggu beberapa detik, lalu test lagi:
curl https://python.anekakebutuhan.com/
Kabari hasilnya! 👀
A: 🎉 Blueprint berhasil!
{"message":"Flask REST API with Blueprint","modules":["auth","posts"],"status":"ok"}
Sekarang test semua endpoint Blueprint:
# Register
curl -X POST https://python.anekakebutuhan.com/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Budi","email":"budi@test.com","password":"password123"}'
Kabari hasilnya! 👀
A: Database lama masih ada! Hapus dan buat ulang:
rm ~/python.anekakebutuhan.com/database.db
Restart di cPanel, lalu register ulang:
curl -X POST https://python.anekakebutuhan.com/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Budi","email":"budi@test.com","password":"password123"}'
Kabari hasilnya! 👀
A: 🎉 Register berhasil! Sekarang test semua endpoint sekaligus:
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc3ODY1NjU3MywianRpIjoiOWUyNjI3ZDktNTIwZi00MDhkLWIyYWMtZjkxYmZiNDdiNzE2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjEiLCJuYmYiOjE3Nzg2NTY1NzMsImNzcmYiOiI3ODU1MDljNy0zZGE5LTQ1Y2MtYmQ5Zi02OTkwZmZmYmUwMzQiLCJleHAiOjE3Nzg3NDI5NzN9.Zd_OtJbjCQVkRvmH09ahOeVJWSmlpFBrc4ZzuBlcAXk"
# Login
curl -X POST https://python.anekakebutuhan.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"budi@test.com","password":"password123"}'
# Buat post
curl -X POST https://python.anekakebutuhan.com/api/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title":"Post Blueprint","content":"Ini post dari Blueprint Flask"}'
# List posts
curl https://python.anekakebutuhan.com/api/posts
Kabari hasilnya! 👀
A: 🎉 Flask Blueprint berjalan sempurna!
Semua endpoint berfungsi:
- ✅ Login → dapat token baru
- ✅ POST /api/posts → post dibuat dengan user_id
- ✅ GET /api/posts → list post
Perbandingan struktur sebelum vs sesudah Blueprint:
Sebelum — semua dalam 1 file app.py:
app.py (200+ baris)
Sesudah — terorganisir per modul:
app.py ← entry point
app/
__init__.py ← factory & config
models/ ← database models
auth/ ← register, login
posts/ ← CRUD posts
Mirip seperti Laravel yang punya Controllers, Models, Routes terpisah! 😄
Mau lanjut ke: 1. Flask + MySQL — ganti SQLite ke MySQL 2. Template Jinja2 — tampilan HTML 3. Lanjut ke Ruby
A: Kita koneksikan Flask ke MySQL. Buat database dulu di cPanel:
- cPanel → MySQL Databases
- Buat database:
anekakeb_flaskdb - Buat user:
anekakeb_flaskuser - Password: terserah
- Add user to database → All Privileges
Kabari kalau sudah selesai! 👀
A: Install MySQL connector untuk Python:
source ~/virtualenv/python.anekakebutuhan.com/3.9/bin/activate && cd ~/python.anekakebutuhan.com
pip install pymysql cryptography
Kabari hasilnya! 👀
A:
Berhasil! Sekarang update konfigurasi database di app/__init__.py:
cat > ~/python.anekakebutuhan.com/app/__init__.py << 'EOF'
from flask import Flask, jsonify
from flask_jwt_extended import JWTManager
from flask_bcrypt import Bcrypt
from datetime import timedelta
import os
from app.models import db
from app.auth import auth_bp, bcrypt
from app.posts import posts_bp
def create_app():
app = Flask(__name__)
# Konfigurasi MySQL
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://anekakeb_flaskuser:PASSWORDANDA@localhost/anekakeb_flaskdb'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['JWT_SECRET_KEY'] = 'flask-lab-secret-key-2026-32bytes!!'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=24)
db.init_app(app)
bcrypt.init_app(app)
JWTManager(app)
app.register_blueprint(auth_bp)
app.register_blueprint(posts_bp)
@app.route('/')
def index():
return jsonify({
'status': 'ok',
'message': 'Flask REST API with Blueprint + MySQL',
'modules': ['auth', 'posts'],
})
with app.app_context():
db.create_all()
return app
EOF
Ganti PASSWORDANDA dengan password MySQL yang tadi dibuat. Kabari passwordnya (atau langsung ganti sendiri di terminal) lalu RESTART di cPanel! 👀
A: File tersimpan! Restart di cPanel lalu test:
curl https://python.anekakebutuhan.com/
Kabari hasilnya! 👀
A: 🎉 Flask + MySQL berhasil terhubung!
Sekarang test register dan buat post untuk memastikan data tersimpan ke MySQL:
# Register
curl -X POST https://python.anekakebutuhan.com/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Budi","email":"budi@test.com","password":"password123"}'
Kabari hasilnya! 👀
A: 🎉 Data tersimpan ke MySQL!
Perhatikan created_at sekarang format MySQL (2026-05-13T07:27:09) bukan SQLite. ✅
Verifikasi data di MySQL via terminal:
mysql -u anekakeb_flaskuser -p'!nggakPake1' anekakeb_flaskdb -e "SHOW TABLES; SELECT id, name, email, created_at FROM user;"
Kabari hasilnya! 👀
A: 🎉 Flask + MySQL berjalan sempurna!
Terlihat di MySQL:
- ✅ Tabel user dan post dibuat otomatis oleh SQLAlchemy
- ✅ Data Budi tersimpan dengan benar di MySQL
Recap Flask yang sudah dikuasai:
| Fitur | Status |
|---|---|
| REST API dasar | ✅ |
| JWT Authentication | ✅ |
| Authorization (ownership) | ✅ |
| Blueprint (modular code) | ✅ |
| SQLite | ✅ |
| MySQL | ✅ |
Flask sudah sangat lengkap! 🚀
🎉 Flask + MySQL berjalan sempurna!
Terlihat di MySQL:
- ✅ Tabel user dan post dibuat otomatis oleh SQLAlchemy
- ✅ Data Budi tersimpan dengan benar di MySQL
Recap Flask yang sudah dikuasai:
| Fitur | Status |
|---|---|
| REST API dasar | ✅ |
| JWT Authentication | ✅ |
| Authorization (ownership) | ✅ |
| Blueprint (modular code) | ✅ |
| SQLite | ✅ |
| MySQL | ✅ |
Flask sudah sangat lengkap! 🚀
Mau lanjut ke: 1. Template Jinja2 — tampilan HTML untuk Flask 2. Lanjut ke Ruby 😄
A: