Sau khi hoàn thành module Categories, chúng ta sẽ xây dựng chức năng quan trọng nhất của Blog CMS: quản lý bài viết (Posts).
Khác với Category chỉ có vài trường dữ liệu, Post sẽ liên kết với nhiều bảng khác nhau như Category, User, đồng thời hỗ trợ upload hình ảnh, kiểm tra dữ liệu đầu vào và hiển thị danh sách bài viết chuyên nghiệp.
Đây cũng là module có số lượng kiến thức nhiều nhất trong toàn bộ dự án.
Mục tiêu bài học
Sau bài này bạn sẽ thực hiện được:
CRUD bài viết
Upload ảnh đại diện
Chọn Category
Gán tác giả (User)
Validation dữ liệu
Hiển thị Relationship
Tự động tạo slug
Phân trang dữ liệu
Kết quả sau bài học
Danh sách bài viết sẽ hiển thị như sau:
| Ảnh | Tiêu đề | Category | Tác giả | Ngày đăng | Thao tác |
|---|---|---|---|---|---|
| ✔ | Laravel 12 mới có gì? | Laravel | Admin | 07/08/2026 | Edit / Delete |
1. Cấu trúc Database
Bảng posts
| Field | Kiểu |
|---|---|
| id | bigint |
| user_id | foreignId |
| category_id | foreignId |
| title | string |
| slug | string |
| image | string |
| excerpt | text |
| content | longText |
| created_at | timestamp |
Quan hệ
User
│
├──────< Posts >────── Category
Một User có nhiều Post.
Một Category có nhiều Post.
Một Post chỉ thuộc một User và một Category.
2. Relationship
User.php
public function posts()
{
return $this->hasMany(Post::class);
}
Category.php
public function posts()
{
return $this->hasMany(Post::class);
}
Post.php
public function category()
{
return $this->belongsTo(Category::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
Đây là ba relationship được sử dụng xuyên suốt dự án.
3. Resource Controller
Tạo controller
php artisan make:controller PostController --resource
Đăng ký route
Route::resource('posts', PostController::class);
Laravel sẽ tạo sẵn:
index
create
store
show
edit
update
destroy
4. Hiển thị danh sách bài viết
Controller
public function index()
{
$posts = Post::with(['category','user'])
->latest()
->paginate(10);
return view('posts.index', compact('posts'));
}
Điểm quan trọng
with()
Laravel sẽ eager loading.
Không xảy ra lỗi N+1 Query.
5. Hiển thị Relationship
Trong Blade
{{ $post->category->name }}
{{ $post->user->name }}
Không cần viết Query JOIN.
Đó chính là sức mạnh của Eloquent ORM.
6. Form tạo bài viết
Form sẽ có:
Tiêu đề
Slug
Category
Hình ảnh
Mô tả ngắn
Nội dung
Category
<select name="category_id">
@foreach($categories as $category)
<option value="{{ $category->id }}">
{{ $category->name }}
</option>
@endforeach
</select>
Laravel sẽ lấy toàn bộ Category để người dùng lựa chọn.
7. Validation
$request->validate([
'title'=>'required|max:255',
'slug'=>'required|unique:posts',
'category_id'=>'required|exists:categories,id',
'image'=>'nullable|image|max:2048',
'content'=>'required'
]);
Laravel sẽ kiểm tra:
tiêu đề bắt buộc
slug không được trùng
category phải tồn tại
chỉ nhận ảnh
dung lượng tối đa
nội dung không được rỗng
8. Upload hình ảnh
Form
enctype="multipart/form-data"
Controller
$image = null;
if($request->hasFile('image')){
$image = $request->file('image')
->store('posts','public');
}
Kết quả
storage/app/public/posts/
abc123.jpg
xyz456.png
Laravel sẽ tự sinh tên file ngẫu nhiên để tránh trùng.
9. Lưu dữ liệu
Post::create([
'user_id'=>auth()->id(),
'category_id'=>$request->category_id,
'title'=>$request->title,
'slug'=>$request->slug,
'image'=>$image,
'excerpt'=>$request->excerpt,
'content'=>$request->content
]);
Tác giả sẽ được lấy từ
auth()->id()
Người dùng không thể giả mạo tác giả bài viết.
10. Hiển thị ảnh
Blade
<img src="{{ asset('storage/'.$post->image) }}">
Đừng quên tạo symbolic link
php artisan storage:link
Sau khi chạy lệnh này, Laravel sẽ tạo
public/storage
trỏ tới
storage/app/public
11. Cập nhật bài viết
Nếu người dùng chọn ảnh mới
Storage::disk('public')->delete($post->image);
sau đó upload lại.
Nếu không chọn ảnh
Laravel vẫn giữ ảnh cũ.
Đây là cách xử lý được sử dụng trong hầu hết các CMS hiện nay.
12. Xóa bài viết
Khi xóa
Storage::disk('public')->delete($post->image);
$post->delete();
Không nên để file ảnh bị "mồ côi" trong thư mục Storage.
13. Giao diện quản trị
Danh sách nên hiển thị
Thumbnail
Tiêu đề
Danh mục
Tác giả
Ngày tạo
Trạng thái
Nút Edit
Nút Delete
Có thể kết hợp:
Tailwind CSS
DataTables.net
Badge màu
Icon Heroicons
Xác nhận trước khi xóa
Đây cũng là giao diện mà chúng ta sẽ sử dụng xuyên suốt các module còn lại.
14. Kết quả đạt được
Sau bài học này, Blog CMS đã có module quản lý bài viết hoàn chỉnh.
✔ Thêm bài viết
✔ Sửa bài viết
✔ Xóa bài viết
✔ Upload ảnh
✔ Relationship User
✔ Relationship Category
✔ Validation
✔ Hiển thị ảnh
✔ Phân trang
✔ Quản lý tác giả
Đây là một module CRUD thực tế mà bạn sẽ gặp trong hầu hết các hệ thống quản trị nội dung sử dụng Laravel.
Tổng kết
Trong bài học này chúng ta đã kết hợp rất nhiều kiến thức đã học trước đó:
Resource Controller để xây dựng CRUD nhanh chóng.
Eloquent Relationship để liên kết Post với Category và User.
Validation để đảm bảo dữ liệu hợp lệ.
Storage để quản lý hình ảnh.
Authentication để tự động gán tác giả cho bài viết.
Pagination giúp danh sách bài viết dễ theo dõi khi số lượng tăng lên.
Đến thời điểm này, dự án Blog CMS đã gần hoàn chỉnh và có cấu trúc tương tự nhiều hệ thống quản trị nội dung được sử dụng trong thực tế.
Bài tiếp theo
Ở Bài 28 — CRUD Users, chúng ta sẽ xây dựng module quản lý người dùng dành cho quản trị viên:
Danh sách User
Chỉnh sửa thông tin
Thay đổi Role (Admin/User)
Khóa hoặc mở khóa tài khoản
Đặt lại mật khẩu
Sau bài này, Blog CMS sẽ có đầy đủ ba module quản trị cốt lõi: Categories, Posts và Users.




Không có nhận xét nào:
Đăng nhận xét