Nội dung bài viết
- 1.File route nằm ở đâu?
- 2.Khai báo route cơ bản
- 3.Tham số route
- 3.1Tham số bắt buộc và tùy chọn
- 3.2Ràng buộc tham số bằng biểu thức chính quy
- 4.Đặt tên route
- 5.Nhóm route: middleware, prefix, name, controller
- 6.Resource route
- 7.Route model binding
- 7.1Tìm theo cột khác id (slug)
- 8.Route fallback và lỗi thường gặp
- 9.Kinh nghiệm từ Hozitech
Route trong Laravel là khai báo cho biết một URL và phương thức HTTP (GET, POST…) sẽ được xử lý bởi đoạn code nào, thường là một phương thức trong controller. Route web được viết trong routes/web.php, ví dụ Route::get('/posts', [PostController::class, 'index']). Đây là điểm vào đầu tiên của mọi request trong ứng dụng Laravel.
Bài viết cập nhật theo Laravel 13 (bản hiện hành, PHP 8.3+): khai báo route, tham số, ràng buộc, đặt tên, nhóm route, resource route và route model binding, kèm các lỗi hay gặp.
File route nằm ở đâu?
Thư mục routes chứa các file khai báo route, được nạp tự động theo cấu hình trong bootstrap/app.php (từ Laravel 11 thay cho RouteServiceProvider):
routes/web.php: route cho giao diện web, chạy qua nhóm middlewareweb(session, cookie, chống CSRF).routes/api.php: không có sẵn trong dự án mới; tạo bằng lệnhphp artisan install:api(lệnh này cài thêm Laravel Sanctum). Route trong file này tự có tiền tố/api.routes/console.php: lệnh Artisan dạng closure và lịch chạy tác vụ.
Xem toàn bộ route đang có bằng php artisan route:list. Vị trí các thư mục khác được giải thích trong bài cấu trúc thư mục Laravel.
Khai báo route cơ bản
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
Route::put('/posts/{post}', [PostController::class, 'update']);
Route::delete('/posts/{post}', [PostController::class, 'destroy']);
Route::match(['get', 'post'], '/lien-he', [ContactController::class, 'handle']);
Route::view('/gioi-thieu', 'pages.about'); // chỉ trả view
Route::redirect('/tin-tuc-cu', '/posts', 301); // chuyển hướng
Hãy dùng cú pháp mảng [PostController::class, 'index']. Kiểu chuỗi 'PostController@index' trong các bài cũ không chạy từ Laravel 8 trừ khi tự cấu hình namespace. Cách viết controller xem trong bài controller trong Laravel.
Form HTML chỉ gửi được GET và POST; để gọi route PUT, PATCH, DELETE, thêm @method('PUT') cùng @csrf trong form.
Tham số route
Tham số bắt buộc và tùy chọn
Tham số được đặt trong dấu ngoặc nhọn {} (không phải dấu hai chấm như một số framework khác) và truyền vào hàm theo thứ tự:
Route::get('/users/{id}', function (string $id) {
return 'User '.$id;
});
Route::get('/posts/{post}/comments/{comment}', function (string $postId, string $commentId) {
// ...
});
// Tham số tùy chọn: thêm dấu? và giá trị mặc định
Route::get('/tim-kiem/{tu_khoa?}', function (?string $tuKhoa = null) {
// ...
});
Ràng buộc tham số bằng biểu thức chính quy
Route::get('/users/{id}', [UserController::class, 'show'])
->where('id', '[0-9]+');
// Các helper có sẵn
Route::get('/users/{id}', [UserController::class, 'show'])->whereNumber('id');
Route::get('/danh-muc/{loai}', [CategoryController::class, 'show'])
->whereIn('loai', ['tin-tuc', 'huong-dan']);
URL không khớp ràng buộc sẽ nhận 404. Lưu ý: where() chỉ kiểm tra định dạng chuỗi, không phải route model binding.
Đặt tên route
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
// Sinh URL theo tên, không phụ thuộc đường dẫn thật
$url = route('posts.show', $post);
return redirect()->route('posts.show', $post);
Trong Blade: <a href="{{ route('posts.show', $post) }}">. Khi đổi URL, chỉ cần sửa một chỗ trong file route, mọi liên kết sinh từ tên route tự cập nhật.
Nhóm route: middleware, prefix, name, controller
use App\Http\Controllers\Admin\PostController as AdminPostController;
Route::middleware(['auth', 'verified'])
->prefix('admin')
->name('admin.')
->group(function () {
Route::get('/', [DashboardController::class, 'index'])->name('dashboard'); // /admin, admin.dashboard
Route::controller(AdminPostController::class)->group(function () {
Route::get('/posts', 'index')->name('posts.index'); // /admin/posts
Route::post('/posts', 'store')->name('posts.store');
});
});
middleware()áp middleware cho cả nhóm, xem bài middleware trong Laravel.prefix()thêm tiền tố URL;name()thêm tiền tố tên route (nhớ dấu chấm cuối).controller()cho phép chỉ ghi tên phương thức trong nhóm.
Resource route
Route::resource('posts', PostController::class); // 7 route CRUD
Route::resource('posts', PostController::class)->only(['index', 'show']);
Route::resource('posts', PostController::class)->except(['destroy']);
Route::apiResource('posts', Api\PostController::class); // bỏ create, edit
Một dòng Route::resource sinh 7 route: index, create, store, show, edit, update, destroy, với tên dạng posts.index. Bảng đầy đủ có trong bài controller. Route bổ sung như /posts/popular phải khai báo trước Route::resource.
Route model binding
Route model binding giúp nhận thẳng model thay vì tự gọi Post::findOrFail($id). Điều kiện là tên tham số trong URL phải trùng tên biến và biến được khai báo kiểu model:

use App\Models\Post;
// ĐÚNG: {post} trùng $post, có type-hint Post → Laravel tự tìm theo id, không có thì 404
Route::get('/posts/{post}', function (Post $post) {
return view('posts.show', compact('post'));
});
// KHÔNG bind: {id} khác tên $post → Laravel không biết đổi {id} thành model
Route::get('/posts/{id}', function (Post $post) {
// $post là model rỗng, không phải bài viết có id trong URL
});
Tìm theo cột khác id (slug)
// Chỉ cho route này
Route::get('/posts/{post:slug}', [PostController::class, 'show']);
// Cho mọi route của model Post — Laravel 13: attribute trên model
use Illuminate\Database\Eloquent\Attributes\RouteKey;
#[RouteKey('slug')]
class Post extends Model
{
}
// Laravel 12 trở về trước (vẫn dùng được): ghi đè getRouteKeyName()
public function getRouteKeyName(): string
{
return 'slug';
}
Với route lồng nhau như /users/{user}/posts/{post:slug}, Laravel tự giới hạn chỉ tìm bài viết thuộc người dùng đó (dựa vào quan hệ posts trên model User). Về khai báo quan hệ, xem bài model trong Laravel.
Route fallback và lỗi thường gặp
// Đặt cuối routes/web.php: chạy khi không route nào khớp
Route::fallback(function () {
return response()->view('errors.404', [], 404);
});
- 404 dù đã khai báo route: route khác được khai báo trước đã "bắt" URL (ví dụ
/posts/{post}đứng trước/posts/popular), hoặc ràng buộcwhere()không khớp. - 405 Method Not Allowed: gửi POST vào route chỉ khai báo GET, hoặc form thiếu
@method('PUT'). - 419 Page Expired: form gửi vào route web thiếu
@csrf. - Route mới không có tác dụng trên server: route đã được cache; chạy lại
php artisan route:cache(hoặcroute:clear) sau khi triển khai.
Kinh nghiệm từ Hozitech
Khi làm lại website cho khách hàng, Hozitech thường giữ nguyên URL cũ hoặc khai báo chuyển hướng 301 từ URL cũ sang URL mới ngay trong file route để không mất thứ hạng tìm kiếm. Route trang quản trị luôn gom trong một nhóm có prefix('admin'), name('admin.') và middleware auth, tránh việc quên bảo vệ một route lẻ.
Nếu bạn cần một website có cấu trúc URL thân thiện SEO và được quản lý bài bản, có thể tham khảo dịch vụ thiết kế website của Hozitech.
Về tác giả
Founder
Xin chào mình là Hiền hiện đang là Founder của Hozitech. Mình là kỹ sư phần mềm có hơn 9+ năm kinh nghiệm trong lĩnh vực thiết kế website, ứng dụng di động... Gần đây mình đang nghiên cứu thêm về lĩnh vực marketing. Mình rất vui khi được chia sẽ những kiến thức và kinh nghiệm của mình đến mọi người!
Câu hỏi thường gặp