- Laravel 13 + Inertia.js + React: พัฒนาเว็บแบบ Modern Monolith
- เทคโนโลยีหลักใน Stack
- Modern Monolith คืออะไร
- จุดเด่นของ Laravel + Inertia + React
- การสร้างโปรเจกต์ใหม่
- โครงสร้างโปรเจกต์ที่ควรรู้
- ตัวอย่างระบบจัดการรายวิชา
- แนวทางจัดโครงสร้างสำหรับระบบขนาดกลางและใหญ่
- ความสามารถของ Inertia ที่ควรศึกษาเพิ่มเติม
- การทดสอบระบบ
- แนวทางสำหรับ Production
- เหมาะกับระบบประเภทใด
- เมื่อใดควรแยก Laravel API และ React Frontend
- สรุป
- แหล่งอ้างอิง
#Laravel 13 + Inertia.js + React: พัฒนาเว็บแบบ Modern Monolith
การพัฒนาเว็บแอปสมัยใหม่มักเริ่มต้นด้วยคำถามว่า ควรสร้าง Laravel เป็น Backend แล้วแยก React Frontend ออกเป็นคนละโปรเจกต์หรือไม่ แม้แนวทางดังกล่าวจะเหมาะกับบางระบบ แต่ก็มาพร้อมภาระเพิ่มเติม เช่น การออกแบบ REST API การจัดการ Token การตั้งค่า CORS การทำ Validation ซ้ำ และการดูแล Deployment หลายส่วน
Laravel 13 + Inertia.js + React เสนอแนวทางอีกแบบหนึ่งที่เรียกว่า Modern Monolith โดยยังคงใช้ Laravel เป็นศูนย์กลางของระบบ ทั้ง Routing, Authentication, Authorization, Validation และฐานข้อมูล ขณะที่ React รับผิดชอบการสร้างส่วนติดต่อผู้ใช้แบบโต้ตอบ
Inertia.js ทำหน้าที่เป็นสะพานเชื่อมระหว่าง Laravel และ React ทำให้สามารถพัฒนาเว็บที่ให้ประสบการณ์ใกล้เคียง Single Page Application หรือ SPA ได้ โดยไม่จำเป็นต้องสร้าง REST API สำหรับทุกหน้าของระบบ
#เทคโนโลยีหลักใน Stack
React Starter Kit ของ Laravel 13 ใช้เทคโนโลยีสำคัญดังนี้
| เทคโนโลยี | หน้าที่ |
|---|---|
| Laravel 13 | Backend, Routing, Authentication, Validation และ Database |
| Inertia.js 3 | เชื่อม Controller ของ Laravel กับ React Page |
| React 19 | สร้างส่วนติดต่อผู้ใช้แบบ Component |
| TypeScript | เพิ่ม Type Safety ให้โค้ดฝั่ง Frontend |
| Tailwind CSS 4 | จัดรูปแบบ UI ด้วย Utility Classes |
| shadcn/ui | ชุด UI Component ที่ปรับแต่งได้ |
| Vite | Build Tool และ Development Server |
จุดสำคัญคือโค้ด Backend และ Frontend ยังคงอยู่ในโปรเจกต์เดียวกัน นักพัฒนาจึงสามารถใช้ความสามารถของ Laravel ได้โดยตรง พร้อมกับสร้าง UI ด้วย React
#Modern Monolith คืออะไร
Modern Monolith คือสถาปัตยกรรมที่รวม Backend และ Frontend ไว้ในแอปพลิเคชันเดียว แต่ยังคงใช้เทคโนโลยี Frontend สมัยใหม่ เช่น React, Vue หรือ Svelte
Browser
│
▼
Laravel Route
│
▼
Controller
│
▼
Inertia::render()
│
▼
React Page Component
เมื่อผู้ใช้เปิดหน้าเว็บ Laravel จะรับ Request ผ่าน Route และ Controller จากนั้น Controller จะเรียก Inertia::render() เพื่อส่งชื่อ Page Component พร้อมข้อมูลในรูปแบบ Props ไปยัง React
return Inertia::render('courses/index', [
'courses' => $courses,
]);
Inertia จะจับคู่คำสั่งดังกล่าวกับไฟล์ React:
resources/js/pages/courses/index.tsx
ข้อมูลในตัวแปร courses จะถูกส่งไปเป็น Props ของ React Component โดยตรง
#จุดเด่นของ Laravel + Inertia + React
#1. ไม่ต้องสร้าง REST API สำหรับทุกหน้า
ระบบ CRUD ภายในองค์กรจำนวนมากไม่จำเป็นต้องแยก Backend API และ Frontend SPA ออกจากกัน การใช้ Inertia ช่วยให้ Controller ส่งข้อมูลไปยัง React Page ได้โดยตรง
อย่างไรก็ตาม ระบบยังสามารถสร้าง API เพิ่มเติมได้เมื่อต้องรองรับ Mobile Application, Third-party Integration หรือ Client ภายนอก
#2. ใช้ Authentication ของ Laravel ได้ทันที
Laravel Starter Kit เตรียมระบบที่เกี่ยวข้องกับ Authentication มาให้ เช่น Login, Registration, Password Reset, Email Verification, Two-Factor Authentication และ Rate Limiting
แอปพลิเคชันใช้ Session Authentication และ CSRF Protection ของ Laravel จึงไม่จำเป็นต้องจัดการ Access Token สำหรับการทำงานภายในเว็บแอปทั่วไป
#3. ใช้ Validation ชุดเดียว
กฎ Validation ยังคงเขียนที่ Laravel ผ่าน Controller หรือ Form Request เมื่อ Validation ไม่ผ่าน ข้อผิดพลาดจะถูกส่งกลับมายัง Inertia Form และเข้าถึงได้จาก React
{form.errors.name && (
<p className="text-sm text-red-600">
{form.errors.name}
</p>
)}
แนวทางนี้ช่วยลดการเขียนกฎ Validation ซ้ำระหว่าง Backend และ Frontend
#4. ใช้ Authorization ของ Laravel ได้เต็มรูปแบบ
ระบบสามารถใช้ Middleware, Gate และ Policy เพื่อควบคุมสิทธิ์ได้ตามมาตรฐานของ Laravel
Route::middleware(['auth', 'verified'])->group(function () {
Route::resource('courses', CourseController::class);
});
#5. ได้ประสบการณ์แบบ SPA
เมื่อเปลี่ยนหน้าผ่าน Inertia ระบบจะไม่โหลดเอกสาร HTML ใหม่ทั้งหมด แต่จะร้องขอข้อมูลของ Page ถัดไปและปรับ React Component ที่แสดงผล ทำให้การใช้งานลื่นไหลใกล้เคียง SPA
#การสร้างโปรเจกต์ใหม่
#1. ติดตั้ง Laravel Installer
composer global require laravel/installer
#2. สร้างโปรเจกต์
laravel new course-management
ระหว่างการติดตั้งให้เลือก React Starter Kit และกำหนดฐานข้อมูลกับ Testing Framework ตามความเหมาะสม
#3. ติดตั้ง Frontend Dependencies
cd course-management
npm install
npm run build
#4. เปิด Development Server
composer run dev
จากนั้นเปิดเว็บเบราว์เซอร์ที่ http://localhost:8000
#โครงสร้างโปรเจกต์ที่ควรรู้
app/
├── Actions/
├── Http/
│ ├── Controllers/
│ ├── Middleware/
│ └── Requests/
├── Models/
└── Policies/
resources/js/
├── components/
├── hooks/
├── layouts/
├── lib/
├── pages/
└── types/
routes/
├── web.php
└── console.php
| Directory | หน้าที่ |
|---|---|
app/Http/Controllers |
รับ Request และประสานการทำงานของระบบ |
app/Http/Requests |
จัดการ Validation และ Authorization ของฟอร์ม |
app/Models |
จัดการข้อมูลผ่าน Eloquent ORM |
app/Policies |
กำหนดสิทธิ์การเข้าถึงข้อมูล |
resources/js/pages |
React Page ที่ถูกเรียกผ่าน Inertia |
resources/js/components |
Component ที่ใช้ซ้ำในหลายหน้า |
resources/js/layouts |
Layout หลัก เช่น Sidebar หรือ Header |
resources/js/types |
TypeScript Type และ Interface |
routes/web.php |
กำหนด Web Route ของระบบ |
#ตัวอย่างระบบจัดการรายวิชา
#1. Model และ Migration
php artisan make:model Course -m
Schema::create('courses', function (Blueprint $table) {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->unsignedTinyInteger('credits')->default(3);
$table->timestamps();
});
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Course extends Model
{
protected $fillable = [
'code',
'name',
'credits',
];
}
php artisan migrate
#2. Route
ไฟล์ routes/web.php
<?php
use App\Http\Controllers\CourseController;
use Illuminate\Support\Facades\Route;
Route::middleware(['auth', 'verified'])->group(function () {
Route::resource('courses', CourseController::class);
});
#3. Form Request
php artisan make:request StoreCourseRequest
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreCourseRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'code' => ['required', 'string', 'max:20', 'unique:courses,code'],
'name' => ['required', 'string', 'max:255'],
'credits' => ['required', 'integer', 'between:1,12'],
];
}
}
#4. Controller
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreCourseRequest;
use App\Models\Course;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class CourseController extends Controller
{
public function index(Request $request): Response
{
$courses = Course::query()
->when(
$request->filled('search'),
fn ($query) => $query->where(
fn ($query) => $query
->where('code', 'like', '%'.$request->string('search').'%')
->orWhere('name', 'like', '%'.$request->string('search').'%')
)
)
->latest()
->paginate(10)
->withQueryString();
return Inertia::render('courses/index', [
'courses' => $courses,
'filters' => [
'search' => $request->string('search')->toString(),
],
]);
}
public function create(): Response
{
return Inertia::render('courses/create');
}
public function store(StoreCourseRequest $request): RedirectResponse
{
Course::create($request->validated());
return to_route('courses.index')
->with('success', 'เพิ่มรายวิชาเรียบร้อยแล้ว');
}
}
#5. TypeScript Types
ไฟล์ resources/js/types/course.ts
export interface Course {
id: number;
code: string;
name: string;
credits: number;
created_at: string;
updated_at: string;
}
export interface PaginatedData<T> {
data: T[];
current_page: number;
last_page: number;
per_page: number;
total: number;
}
#6. React Page สำหรับแสดงรายการ
ไฟล์ resources/js/pages/courses/index.tsx
import { Head, Link } from '@inertiajs/react';
import type { Course, PaginatedData } from '@/types/course';
interface CourseIndexProps {
courses: PaginatedData<Course>;
}
export default function CourseIndex({ courses }: CourseIndexProps) {
return (
<>
<Head title="รายวิชา" />
<main className="mx-auto max-w-6xl space-y-6 p-6">
<header className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-semibold">รายวิชา</h1>
<p className="text-sm text-muted-foreground">
จัดการข้อมูลรายวิชาในหลักสูตร
</p>
</div>
<Link
href="/courses/create"
className="rounded-md bg-primary px-4 py-2 text-primary-foreground"
>
เพิ่มรายวิชา
</Link>
</header>
<div className="overflow-hidden rounded-lg border">
<table className="w-full">
<thead className="bg-muted">
<tr>
<th className="p-3 text-left">รหัส</th>
<th className="p-3 text-left">ชื่อรายวิชา</th>
<th className="p-3 text-left">หน่วยกิต</th>
</tr>
</thead>
<tbody>
{courses.data.map((course) => (
<tr key={course.id} className="border-t">
<td className="p-3">{course.code}</td>
<td className="p-3">{course.name}</td>
<td className="p-3">{course.credits}</td>
</tr>
))}
</tbody>
</table>
</div>
</main>
</>
);
}
#7. React Form ด้วย useForm
ไฟล์ resources/js/pages/courses/create.tsx
import { FormEvent } from 'react';
import { Head, useForm } from '@inertiajs/react';
interface CourseForm {
code: string;
name: string;
credits: number;
}
export default function CreateCourse() {
const form = useForm<CourseForm>({
code: '',
name: '',
credits: 3,
});
function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
form.post('/courses', {
preserveScroll: true,
});
}
return (
<>
<Head title="เพิ่มรายวิชา" />
<form onSubmit={submit} className="mx-auto max-w-xl space-y-4 p-6">
<div className="space-y-2">
<label htmlFor="code">รหัสรายวิชา</label>
<input
id="code"
value={form.data.code}
onChange={(event) => form.setData('code', event.target.value)}
className="w-full rounded-md border p-2"
/>
{form.errors.code && (
<p className="text-sm text-red-600">{form.errors.code}</p>
)}
</div>
<div className="space-y-2">
<label htmlFor="name">ชื่อรายวิชา</label>
<input
id="name"
value={form.data.name}
onChange={(event) => form.setData('name', event.target.value)}
className="w-full rounded-md border p-2"
/>
{form.errors.name && (
<p className="text-sm text-red-600">{form.errors.name}</p>
)}
</div>
<button
type="submit"
disabled={form.processing}
className="rounded-md bg-primary px-4 py-2 text-primary-foreground disabled:opacity-50"
>
{form.processing ? 'กำลังบันทึก...' : 'บันทึก'}
</button>
</form>
</>
);
}
useForm ช่วยจัดการ State ของฟอร์ม การส่ง Request สถานะกำลังประมวลผล และ Validation Error ที่ Laravel ส่งกลับมา
#แนวทางจัดโครงสร้างสำหรับระบบขนาดกลางและใหญ่
resources/js/
├── components/
│ ├── common/
│ ├── courses/
│ ├── students/
│ └── internships/
├── layouts/
│ ├── app-layout.tsx
│ └── auth-layout.tsx
├── pages/
│ ├── courses/
│ ├── students/
│ ├── internships/
│ └── dashboard.tsx
├── types/
└── lib/
หลักการสำคัญคือ
- Page Component ควรรับผิดชอบการจัดวางหน้าจอและประสานข้อมูล
- UI ที่ใช้ซ้ำควรแยกเป็น Component
- Business Logic สำคัญควรอยู่ใน Service หรือ Action ฝั่ง Laravel
- Validation ควรแยกเป็น Form Request
- Authorization ควรใช้ Policy แทนการเขียนเงื่อนไขกระจายใน Controller
- Query ที่ซับซ้อนควรแยกเป็น Query Object, Scope หรือ Service ตามความเหมาะสม
#ความสามารถของ Inertia ที่ควรศึกษาเพิ่มเติม
- Partial Reloads โหลดเฉพาะ Props ที่ต้องการเมื่อข้อมูลบางส่วนของหน้าเปลี่ยนแปลง
- Deferred Props เลื่อนการโหลดข้อมูลที่ยังไม่จำเป็นต่อการแสดงผลครั้งแรก
- Prefetching โหลดข้อมูลของหน้าที่ผู้ใช้อาจเปิดล่วงหน้า
- Polling เรียกข้อมูลซ้ำตามช่วงเวลา
- Server-Side Rendering สร้าง HTML เริ่มต้นจาก Server ก่อนส่งไปยัง Browser
คำสั่งสำหรับ SSR:
npm run build:ssr
composer dev:ssr
#การทดสอบระบบ
Stack นี้ควรมีการทดสอบทั้ง Backend และ End-to-End
#Backend Testing
ใช้ Pest หรือ PHPUnit ทดสอบ Route, Middleware, Validation, Policy, Controller, Database และ Business Logic
it('allows an authenticated user to create a course', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->post(route('courses.store'), [
'code' => 'SE101',
'name' => 'Introduction to Software Engineering',
'credits' => 3,
]);
$response->assertRedirect(route('courses.index'));
$this->assertDatabaseHas('courses', [
'code' => 'SE101',
]);
});
#End-to-End Testing
ใช้ Playwright ทดสอบกระบวนการที่ผู้ใช้ดำเนินการจริง เช่น Login การเพิ่ม แก้ไข และลบข้อมูล การค้นหา การตรวจสอบสิทธิ์ตาม Role และ Validation Message
#แนวทางสำหรับ Production
Laravel 13
├── Inertia.js 3
├── React 19
├── TypeScript
├── Tailwind CSS 4
├── shadcn/ui
├── PostgreSQL
├── Redis
├── Laravel Horizon
├── Laravel Reverb
├── Pest
├── Playwright
└── Docker Compose
ข้อควรดำเนินการก่อน Deploy ได้แก่
- กำหนด
APP_ENV=production - ปิด
APP_DEBUG - ใช้ HTTPS
- Cache Configuration และ Route
- Build Frontend Assets สำหรับ Production
- ใช้ Queue Worker สำหรับงาน Background
- ตั้งค่า Scheduler
- จัดเก็บ Secret ผ่าน Environment Variables
- สำรองฐานข้อมูล
- ใช้ Log Aggregation และ Monitoring
- ตรวจสอบ Authorization ทุก Module
composer install --no-dev --optimize-autoloader
npm ci
npm run build
php artisan config:cache
php artisan route:cache
php artisan view:cache
#เหมาะกับระบบประเภทใด
- ระบบสารสนเทศมหาวิทยาลัย
- ระบบจัดการหลักสูตร
- ระบบฝึกงานและสหกิจศึกษา
- ระบบบริหารโครงงาน
- ระบบงานวิจัย
- Admin Dashboard
- ระบบ CRM และ ERP ภายในองค์กร
- SaaS Application
- ระบบ CRUD ขนาดกลางถึงขนาดใหญ่
- ระบบที่ต้องมี Role และ Permission หลายระดับ
#เมื่อใดควรแยก Laravel API และ React Frontend
แม้ Modern Monolith จะลดความซับซ้อนได้มาก แต่การแยก API อาจเหมาะกว่าเมื่อระบบมี Mobile Application มี Frontend หลายระบบ ต้องเปิด API ให้ Partner ต้อง Deploy Backend และ Frontend แยกกัน หรือต้องรองรับ Public API Versioning
การเลือกสถาปัตยกรรมควรพิจารณาจากผู้ใช้ระบบ รูปแบบ Deployment ขนาดทีม และช่องทางที่ต้องเข้าถึงข้อมูล ไม่ควรเลือกแยก API เพียงเพราะเป็นแนวทางที่นิยม
#สรุป
Laravel 13 + Inertia.js + React เป็น Stack ที่ผสานจุดแข็งของ Laravel และ React เข้าด้วยกัน Laravel รับผิดชอบ Routing, Authentication, Authorization, Validation และ Business Logic ส่วน React รับผิดชอบการสร้าง UI ที่ยืดหยุ่นและตอบสนองต่อผู้ใช้
Inertia.js ช่วยลดช่องว่างระหว่าง Backend และ Frontend ทำให้สามารถสร้างเว็บแอปแบบ SPA ภายในโปรเจกต์เดียว โดยไม่ต้องสร้าง REST API สำหรับทุกหน้า
สำหรับระบบสารสนเทศ Dashboard Admin Panel และเว็บแอป CRUD ที่ต้องการพัฒนาอย่างรวดเร็วแต่ยังคงโครงสร้างที่ดูแลได้ง่าย Stack นี้เป็นทางเลือกที่เหมาะสมอย่างยิ่ง
#แหล่งอ้างอิง
- Laravel 13.x Documentation: Starter Kits
- Laravel 13.x Documentation: Frontend
- Laravel 13.x Documentation: Installation
- Laravel 13.x Documentation: Vite
- Inertia.js Documentation: Getting Started
- Inertia.js Documentation: How It Works