Khởi động nhanh — Chạy Laravel trong 5 phút
Sáu tháng trước tôi đã đưa một sản phẩm SaaS chạy trên Laravel lên production. Trước đó tôi còn do dự — tiếng xấu của PHP là ngôn ngữ mà người ta dùng rồi bỏ khiến tôi không chắc. Sau khi chạy dưới tải thực tế, những nghi ngờ đó đã tan biến. Đây là cấu hình chính xác đã hoạt động.
Bắt đầu với Docker để môi trường dev khớp với production ngay từ ngày đầu:
# Cài Laravel qua Composer
composer create-project laravel/laravel my-api
cd my-api
# Hoặc dùng Laravel installer
composer global require laravel/installer
laravel new my-api --git
Trước khi viết bất kỳ controller nào, đặt file docker-compose.yml này vào thư mục gốc project:
version: '3.8'
services:
app:
build: .
ports:
- "8000:8000"
volumes:
- .:/var/www/html
depends_on:
- db
environment:
DB_HOST: db
DB_DATABASE: myapp
DB_USERNAME: root
DB_PASSWORD: secret
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
volumes:
- db_data:/var/lib/mysql
redis:
image: redis:alpine
volumes:
db_data:
Thêm một Dockerfile tối giản:
FROM php:8.2-fpm-alpine
RUN apk add --no-cache \
nginx \
curl \
git \
&& docker-php-ext-install pdo pdo_mysql redis
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY . .
RUN composer install --no-dev --optimize-autoloader
EXPOSE 8000
CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]
Khởi động:
docker-compose up -d
docker-compose exec app php artisan migrate
Bạn đã có một ứng dụng Laravel đang chạy với MySQL và Redis — đúng stack tôi đang dùng trên production.
Đi sâu hơn — Xây dựng RESTful API
Resource Controllers và Routes
Resource routing của Laravel loại bỏ code lặp lại. Một lệnh duy nhất tạo ra toàn bộ scaffold CRUD:
php artisan make:model Post -mcr
# -m = migration, -c = controller, -r = resource controller
Đăng ký resource trong routes/api.php:
<?php
use App\Http\Controllers\PostController;
Route::apiResource('posts', PostController::class);
// Tạo ra: GET /posts, POST /posts, GET /posts/{id},
// PUT /posts/{id}, DELETE /posts/{id}
Bên trong app/Http/Controllers/PostController.php, phương thức index với phân trang trông như sau:
public function index()
{
$posts = Post::with('author')
->latest()
->paginate(15);
return response()->json([
'data' => $posts->items(),
'meta' => [
'current_page' => $posts->currentPage(),
'last_page' => $posts->lastPage(),
'per_page' => $posts->perPage(),
'total' => $posts->total(),
]
]);
}
Xác thực người dùng với Laravel Sanctum
Để xác thực API dựa trên token, Sanctum là công cụ phù hợp — nhẹ hơn Passport, không có sự phức tạp của OAuth:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
Thêm trait HasApiTokens vào model User của bạn:
<?php
namespace App\Models;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens;
}
Tạo một AuthController:
php artisan make:controller AuthController
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function register(Request $request)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
]);
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
return response()->json([
'token' => $user->createToken('api')->plainTextToken,
'user' => $user,
], 201);
}
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['Thông tin đăng nhập không hợp lệ.'],
]);
}
return response()->json([
'token' => $user->createToken('api')->plainTextToken,
]);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Đã đăng xuất']);
}
}
Kết nối các route xác thực trong routes/api.php:
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::post('/logout', [AuthController::class, 'logout']);
Route::apiResource('posts', PostController::class);
});
Sử dụng nâng cao — Queues, API Resources và Rate Limiting
Biến đổi Response với API Resources
Eloquent model thuần tạo ra response lộ các cột bạn không muốn client thấy. API Resources đóng vai trò là lớp biến đổi dữ liệu — kết hợp với OpenAPI Generator để tự động sinh client code:
php artisan make:resource PostResource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'author' => $this->whenLoaded('author', fn() => [
'name' => $this->author->name,
'email' => $this->author->email,
]),
'created_at' => $this->created_at->toIso8601String(),
];
}
}
Background Jobs với Redis Queues
Ngay khi bạn thêm thông báo email hay webhook bên thứ ba, hãy chuyển chúng ra khỏi chu kỳ request. Với Redis đã có sẵn trong compose stack:
php artisan make:job SendWelcomeEmail
// Trong phương thức register, thay thế lệnh gọi mail trực tiếp:
SendWelcomeEmail::dispatch($user)->onQueue('emails');
// Khởi động worker bên trong container
php artisan queue:work redis --queue=emails --tries=3
Tôi đã áp dụng cách này trên production và kết quả luôn ổn định — ngay cả khi có traffic đột biến với hơn 200 lượt đăng ký cùng lúc, không một email chào mừng nào bị mất.
Giới hạn tốc độ (Rate Limiting)
Middleware throttle của Laravel bảo vệ các endpoint chỉ với hai dòng code, thay vì phải tự xây dựng một rate limiter theo thuật toán Token Bucket từ đầu:
// Trong RouteServiceProvider::boot()
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// Áp dụng cho nhóm route
Route::middleware(['auth:sanctum', 'throttle:api'])->group(...);
Mẹo thực chiến sau sáu tháng chạy Production
Đồng bộ môi trường
Nguyên nhân lớn nhất gây bất ngờ trên production là sự khác biệt môi trường. Luôn build Docker image và chạy local trước khi đẩy lên — đừng bao giờ chỉ dựa vào php artisan serve. Thêm đoạn này vào CI pipeline:
docker build -t my-api:test .
docker run --rm my-api:test php artisan test
Tối ưu hóa Database Query
Dùng ->with() tích cực để tránh vấn đề N+1 query. Cài Laravel Debugbar trong quá trình phát triển để phát hiện chúng trước khi lên staging:
composer require barryvdh/laravel-debugbar --dev
Với production, bật query logging trên staging và tìm các query chạy quá 100ms:
DB::listen(function ($query) {
if ($query->time > 100) {
Log::warning('Query chậm', [
'sql' => $query->sql,
'time' => $query->time,
]);
}
});
Endpoint kiểm tra sức khỏe (Health Check)
Docker và load balancer cần một endpoint health check. Thêm một endpoint kiểm tra các dependency thực tế:
Route::get('/health', function () {
DB::connection()->getPdo(); // ném exception nếu DB bị down
return response()->json(['status' => 'ok']);
});
Không để lộ Secrets trong Images
Đừng bao giờ nhúng file .env vào Docker image. Sử dụng Docker secrets hoặc biến môi trường được inject lúc chạy:
# docker-compose.yml — tham chiếu file env bên ngoài
env_file:
- .env.production
Sự kết hợp giữa cú pháp biểu cảm của Laravel, xác thực token đơn giản của Sanctum, và môi trường tái tạo được của Docker đã loại bỏ hầu hết rào cản trong phát triển API — kết hợp thêm Cache-Control và ETags là bước tối ưu tiếp theo khi cần giảm độ trễ ở tầng HTTP. Stack này đã trưởng thành, tài liệu xuất sắc, và cộng đồng đã giải quyết hầu hết mọi vấn đề bạn có thể gặp. Điều khiến tôi ngạc nhiên nhất sau sáu tháng là tôi hiếm khi phải vật lộn với framework — nó hầu như không cản trở gì.

