Toidicode.com

Toidicode.com

BASIC TO ADVANCE

Bài 4: Route trong Laravel 13 (Phần 2)

Ở bài trước chúng ta đã biết cách khai báo route, tham số và đặt tên cho route. Tiếp tục với route trong Laravel 13, bài này mình sẽ giới thiệu với mọi người về nhóm route, route model binding, fallback route, giới hạn số request (rate limit) và route cache. Các route projects của TaskFlow cũng sẽ được gom lại cho gọn.

1. Nhóm route (route group).

Các route của TaskFlow đều bắt đầu bằng /projects và có tên bắt đầu bằng projects.. Thay vì lặp lại những phần chung đó ở từng route, các bạn gom chúng vào một route group với cú pháp như sau:

Route::prefix($prefix)->name($namePrefix)->group(function () {
    // các route trong nhóm
});

Trong đó:

  • prefix($prefix) thêm $prefix vào trước URI của mọi route trong nhóm.
  • name($namePrefix) thêm $namePrefix vào trước tên của mọi route trong nhóm. Chuỗi được nối nguyên văn, nên các bạn nhớ thêm dấu . ở cuối.
  • group() nhận một closure, bên trong khai báo route như bình thường.

Không bắt buộc phải dùng cả hai. Ngoài ra các ràng buộc như whereNumber() ở bài trước cũng gọi được trên nhóm và áp dụng cho mọi route bên trong.

VD: Mình viết lại các route project của TaskFlow và thêm route danh sách task của một project.

- routes/web.php

<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
})->name('home');

Route::prefix('projects')
    ->name('projects.')
    ->whereNumber('id')
    ->group(function () {
        Route::get('/', function () {
            return [
                ['id' => 1, 'name' => 'Website toidicode.com'],
                ['id' => 2, 'name' => 'TaskFlow API'],
            ];
        })->name('index');

        Route::get('/{id}', function (string $id) {
            return 'Chi tiết project #'.$id;
        })->name('show');

        Route::get('/{id}/tasks', function (string $id) {
            return 'Danh sách task của project #'.$id;
        })->name('tasks.index');
    });

Route::redirect('/du-an', '/projects');
php artisan route:list --except-vendor

#  GET|HEAD       / ................................... home › routes/web.php:5
#  ANY            du-an ............... Illuminate\Routing › RedirectController
#  GET|HEAD       projects ................. projects.index › routes/web.php:13
#  GET|HEAD       projects/{id} ............. projects.show › routes/web.php:20
#  GET|HEAD       projects/{id}/tasks projects.tasks.index › routes/web.php:24
#
#                                                             Showing [5] routes

URI và tên route vẫn như cũ (projects.index, projects.show), nên những chỗ đã gọi route('projects.show', ['id' => 1]) không phải sửa gì. Route / trong nhóm trở thành /projects, Laravel tự xử lý dấu / giữa prefix và URI.

Group có thể lồng nhau: prefix và tên được nối tiếp, còn middleware và ràng buộc where được gộp lại.

Route::prefix('admin')->name('admin.')->group(function () {
    Route::prefix('reports')->name('reports.')->group(function () {
        Route::get('/tasks', function () {
            return route('admin.reports.tasks');
        })->name('tasks');
    });
});

// Truy cập /admin/reports/tasks
// Output: http://localhost:8000/admin/reports/tasks

Middleware cho nhóm route.

Middleware là các lớp xử lý chạy trước (hoặc sau) khi request đến route, VD kiểm tra đăng nhập hay giới hạn số request. Để gắn middleware cho cả nhóm, các bạn dùng middleware() trước group(), middleware chạy theo thứ tự trong mảng:

Route::middleware(['first', 'second'])->group(function () {
    // ...
});

Cách tạo middleware mình sẽ nói ở bài Middleware trong Laravel 13 (Bài 9). Trong bài này chúng ta sẽ dùng middleware throttle có sẵn ở phần 4.

2. Route model binding.

Ở route /projects/{id}, sau khi có id thì việc tiếp theo gần như luôn là tìm bản ghi tương ứng trong database. Route model binding giúp Laravel làm việc đó thay chúng ta: thay vì nhận id, closure nhận luôn đối tượng model.

Để dùng nó cần có model (Eloquent), phần mà chúng ta sẽ học ở Bài 15. Tuy nhiên project mới tạo đã có sẵn model App\Models\User và bảng users, nên mình dùng nó để ví dụ.

use App\Models\User;

Route::get('/users/{user}', function (User $user) {
    return 'Xin chào '.$user->name;
});

// Với bảng users có bản ghi id = 1, name = Vu Thanh Tai
// /users/1 => Xin chào Vu Thanh Tai
// /users/2 => 404

Vì biến $user có kiểu User và trùng tên với tham số {user}, Laravel sẽ tự tìm user có id bằng giá trị trên URI rồi truyền vào closure. Không tìm thấy thì trả về lỗi 404. Cách này gọi là implicit binding.

Lưu ý: Bảng users của TaskFlow hiện chưa có dữ liệu nên /users/1 sẽ trả về 404. Cách tạo dữ liệu mẫu có ở bài Seeder và Factory (Bài 13).

Muốn tìm theo cột khác id, các bạn ghi tên cột sau dấu : trong tham số:

Route::get('/members/{user:email}', function (User $user) {
    return $user->name;
});

// /members/[email protected] => Vu Thanh Tai

Khi đã có model Project, route của TaskFlow sẽ đổi {id} thành {project} và closure nhận Project $project. Tạm thời chúng ta vẫn dùng {id}.

3. Fallback route.

Khi không có route nào khớp với request, Laravel trả về trang 404 mặc định. Nếu muốn tự xử lý trường hợp này, các bạn khai báo một route với Route::fallback().

VD: Thêm fallback route vào cuối file routes/web.php của TaskFlow.

Route::fallback(function () {
    return response('Trang bạn tìm không tồn tại.', 404);
});

// /khong-co     => Trang bạn tìm không tồn tại. (status 404)
// /projects/abc => Trang bạn tìm không tồn tại. (status 404)

Hàm response() tạo response với nội dung và status code (xem thêm ở bài Request và Response, Bài 8). Các bạn để ý /projects/abc không thỏa mãn whereNumber('id') nên cũng rơi vào fallback.

Lưu ý: Fallback route chỉ nhận method GET và luôn được xét sau cùng, nhưng mình vẫn đặt nó ở cuối file cho dễ đọc. Lỗi 404 do route model binding không tìm thấy bản ghi thì không đi qua fallback. Trang 404 tùy biến cho toàn ứng dụng sẽ có ở Bài 11.

4. Giới hạn số request (rate limiting).

Rate limiting giới hạn số request mà một người dùng (thường xác định theo IP hoặc user) được gửi đến route trong một khoảng thời gian, giúp chống spam và giảm tải cho server. Đầu tiên, các bạn định nghĩa một rate limiter trong method boot của AppServiceProvider.

VD: Mỗi IP chỉ được gửi 60 request mỗi phút đến các route project.

- app/Providers/AppServiceProvider.php

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('projects', function (Request $request) {
        return Limit::perMinute(60)->by($request->ip());
    });
}

Trong đó:

  • 'projects' là tên của rate limiter, đặt tùy ý.
  • Limit::perMinute(60) cho phép 60 request mỗi phút. Ngoài ra còn có perSecond(), perHour(), perDay() và Limit::none() (không giới hạn).
  • by() chọn giá trị để đếm riêng, ở đây là IP. Sau khi có đăng nhập, các bạn có thể dùng $request->user()?->id ?: $request->ip() để đếm theo user.

Sau đó gắn rate limiter vào nhóm route bằng middleware throttle:tên-limiter:

- routes/web.php

Route::prefix('projects')
    ->name('projects.')
    ->whereNumber('id')
    ->middleware('throttle:projects')
    ->group(function () {
        // ... các route như ở phần 1
    });

Lúc này mỗi response có thêm header cho biết giới hạn và số request còn lại. Số lượng được tính chung cho cả ba route trong nhóm, khi vượt quá Laravel trả về lỗi 429 Too Many Requests kèm header Retry-After (số giây phải chờ):

# Request 1:  200, X-RateLimit-Limit: 60, X-RateLimit-Remaining: 59
# Request 60: 200, X-RateLimit-Limit: 60, X-RateLimit-Remaining: 0
# Request 61: 429, X-RateLimit-Remaining: 0, Retry-After: 52

Với những trường hợp đơn giản, các bạn có thể ghi thẳng số request và số phút mà không cần định nghĩa limiter: ->middleware('throttle:3,1') cho phép 3 request mỗi phút.

Lưu ý: Bộ đếm được lưu trong cache của ứng dụng. TaskFlow đang dùng CACHE_STORE=database mặc định nên không cần cấu hình gì thêm.

5. Route cache.

Khi deploy lên production, các bạn nên cache route để Laravel không phải đăng ký lại toàn bộ route ở mỗi request:

php artisan route:cache   # tạo file bootstrap/cache/routes-v7.php
php artisan route:clear   # xóa route cache

Chú ý: Khi đã có route cache, Laravel đọc route từ file cache chứ không đọc routes/web.php nữa, nên route mới thêm sẽ không có tác dụng cho đến khi chạy lại route:cache. Vì vậy chỉ nên chạy lệnh này khi deploy (Bài 32), không dùng ở local.

6. Lời kết.

Như vậy chúng ta đã tìm hiểu xong phần 2 của route trong Laravel 13: nhóm route, route model binding, fallback route, rate limiting và route cache. Các bạn có thể xem thêm tại tài liệu chính thức. Đến đây các closure trong routes/web.php đã bắt đầu dài ra, bài sau mình sẽ giới thiệu về controller trong Laravel 13 để chuyển phần xử lý đó ra khỏi file route.

Đăng ký nhận tin.

Chúng tôi chỉ gửi tối đa 2 lần trên 1 tháng. Tuyên bố không spam mail!

Vũ Thanh Tài

About author
The best way to learn is to share
Xem tất cả bài đăng

0 Comments

Bài viết chưa có ai bình luận, hãy là người đầu tiên đi bạn!

Bình luận

Captcha