Laravel 12 起手式:從 Composer 到第一個 Route 的十分鐘
本篇是「PHP/Laravel 完全指南」系列的第 2 / 15 篇。你可以從系列總覽開始閱讀,也可以直接接著看本文。
Laravel 是 PHP 生態系中最受歡迎的框架,沒有之一。它的設計哲學很簡單:讓開發者把時間花在業務邏輯上,而不是重複造輪子。從路由、資料庫、認證、佇列到排程任務,Laravel 全部幫你準備好了——而且 API 設計得優雅到你會覺得寫程式是一種享受。
但在享受之前,你得先讓它跑起來。好消息是,Laravel 的安裝流程已經簡化到一行指令就搞定。
版本說明(2026 年): Laravel 已於 2026-03-17 推出 Laravel 13(最新版,最低需求提升至 PHP 8.3+,主打 AI-native 工作流、JSON:API resources、向量/語意搜尋等)。本章內容以 Laravel 12 為基準撰寫,整本書的範例與目錄結構導覽都以 12 為準;如果你用
composer create-project或laravel new預設安裝,現在很可能裝到的是 Laravel 13。兩版的起手式(安裝、Route、Blade、.env)幾乎一致,本章內容仍然適用;差別主要在 Laravel 13 要求 PHP 8.3+,且部分模型/控制器等可改用新的 PHP attribute 寫法。想完全照本章操作,可在composer create-project後面指定版本:composer create-project laravel/laravel:"^12.0" jiu-hao-mai。
這一章我們要做的事情很明確:裝好 Laravel、搞懂目錄結構、寫出第一個 Route 和 View。十分鐘之後,你的瀏覽器上會出現「揪好買」的首頁——這是我們整本書會一起打造的台灣團購平台專案。
不用擔心目錄裡那一堆資料夾看起來很嚇人。每個資料夾都有它明確的職責,我會一個一個帶你認識。學框架最怕的就是「知其然不知其所以然」,所以我們不只要讓它跑起來,還要搞清楚每一步發生了什麼事。
安裝 Laravel 12:一行指令搞定
確認你已經裝好 PHP 8.2+ 和 Composer(上一章 PHP 快速入門 有教),然後打開終端機:
composer create-project laravel/laravel jiu-hao-mai
cd jiu-hao-mai
php artisan serve
三行指令,打開瀏覽器訪問 http://localhost:8000,你就會看到 Laravel 的預設歡迎頁面。
專案命名: 我們把專案取名叫
jiu-hao-mai(揪好買的拼音),這是整本書會持續開發的團購平台。
另一種安裝方式:Laravel Installer
Laravel 也提供了官方安裝器,它可以在建立專案時互動式地選擇前端堆疊和 Starter Kit:
# 先安裝 Laravel Installer(只需要做一次)
composer global require laravel/installer
# 用 installer 建立專案
laravel new jiu-hao-mai
Installer 會問你幾個問題:要不要 Starter Kit?前端用 React、Vue、Livewire 還是 Svelte?(Laravel 12 用全新的 Starter Kit 取代了舊版的 Breeze 和 Jetstream。)我們這本書在第六章才會加入認證系統,所以現在先選 No starter kit,保持乾淨。
兩種方式的差異
composer create-project | laravel new | |
|---|---|---|
| 需要先安裝 installer | 不用 | 要 |
| 互動式選擇 | 沒有 | 有(Starter Kit、測試框架等) |
| 適合場景 | 快速建立純淨專案 | 需要一步到位設定完整堆疊 |
| CI/CD 環境 | 比較適合 | 較不適合(互動式) |
兩種方式建出來的專案結構完全一樣,選你喜歡的就好。
預設資料庫:SQLite
從 Laravel 11 開始,新建專案預設使用 SQLite 作為資料庫——不需要安裝 MySQL,開箱即用。專案根目錄會有一個 database/database.sqlite 檔案,零設定就能開始開發。等到要部署正式環境時,再換成 MySQL 或 PostgreSQL 也很容易(改個 .env 設定就好)。
目錄結構導覽:每個資料夾在幹嘛
cd jiu-hao-mai 之後,你會看到這樣的目錄結構:
jiu-hao-mai/
├── app/ # 🧠 你的應用程式核心
│ ├── Http/
│ │ └── Controllers/ # Controller(處理 HTTP 請求的邏輯)
│ ├── Models/ # Eloquent Model(資料庫對應的 PHP 類別)
│ └── Providers/ # Service Provider(應用程式啟動設定)
├── bootstrap/ # 框架啟動程式(通常不需要動)
│ └── app.php # 應用程式設定與 Middleware 註冊
├── config/ # 設定檔(database, mail, cache 等)
├── database/
│ ├── factories/ # Model Factory(測試資料產生器)
│ ├── migrations/ # Migration(資料表版本控制)
│ ├── seeders/ # Seeder(填充測試資料)
│ └── database.sqlite # 預設的 SQLite 資料庫
├── public/ # 唯一對外公開的目錄(index.php 在這)
├── resources/
│ ├── css/ # CSS 原始檔
│ ├── js/ # JavaScript 原始檔
│ └── views/ # Blade 模板(HTML)
├── routes/
│ ├── console.php # Artisan 自訂指令與排程
│ └── web.php # 🌐 網頁路由(最常編輯的檔案之一)
│ # 注意:api.php 預設不存在,需要時執行 php artisan install:api
├── storage/ # 日誌、快取、上傳檔案
├── tests/ # 測試程式碼
├── .env # 環境變數(不進版控)
├── .env.example # 環境變數範本(進版控)
├── artisan # Artisan CLI 入口
├── composer.json # PHP 相依套件
└── package.json # 前端相依套件
Laravel 11+ 的精簡骨架: 如果你看過舊版 Laravel 的教學,可能會好奇
app/Http/Kernel.php和app/Console/Kernel.php去哪了?從 Laravel 11 開始,框架採用了精簡的應用程式骨架——Kernel 的設定被移到bootstrap/app.php,Middleware 的註冊也在那裡。少了很多「看了不知道要幹嘛」的檔案,新手友善度大幅提升。
你最常碰的五個位置
| 位置 | 用途 | 頻率 |
|---|---|---|
routes/web.php | 定義 URL 路由 | 每天 |
app/Http/Controllers/ | 處理請求邏輯 | 每天 |
app/Models/ | 資料庫 Model | 經常 |
resources/views/ | Blade 模板(HTML) | 經常 |
database/migrations/ | 資料表結構變更 | 每次改資料庫 |
其他目錄在你需要的時候自然會碰到,現在不用記。
與其他框架的對照
如果你從其他框架轉過來,這張表幫你快速對應:
| 概念 | Laravel | Express (Node.js) | Django (Python) | Rails (Ruby) |
|---|---|---|---|---|
| 路由 | routes/web.php | app.js / router files | urls.py | config/routes.rb |
| 控制器 | app/Http/Controllers/ | route handlers | views.py | app/controllers/ |
| 模板 | resources/views/ (Blade) | views/ (EJS/Pug) | templates/ | app/views/ (ERB) |
| ORM Model | app/Models/ (Eloquent) | (Prisma/Sequelize) | models.py | app/models/ |
| 資料庫遷移 | database/migrations/ | (Prisma/Knex) | migrations/ | db/migrate/ |
| 設定 | config/ + .env | .env | settings.py | config/ |
php artisan:你的 Laravel 瑞士刀
artisan 是 Laravel 的命令列工具,它能幫你做幾乎所有事情——從產生程式碼到管理資料庫、從清快取到啟動開發伺服器。
# 查看所有可用指令
php artisan list
# 啟動開發伺服器
php artisan serve
# 產生 Controller
php artisan make:controller ProductController
# 產生 Model(順便建 migration 和 factory)
php artisan make:model Product -mf
# 執行資料庫 migration
php artisan migrate
# 列出所有已註冊的路由
php artisan route:list
# 清除各種快取
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
# 進入互動式 PHP Shell(像 Node.js 的 REPL 或 Python 的 interactive shell)
php artisan tinker
Tinker:你的即時測試場
tinker 是 Laravel 內建的互動式 REPL,讓你可以直接操作 Model、測試邏輯、查詢資料庫——不用啟動瀏覽器:
php artisan tinker
>>> $user = new App\Models\User;
=> App\Models\User {#1234}
>>> config('app.name')
=> "Laravel"
>>> now()->format('Y-m-d')
=> "2026-06-08"
>>> exit
JS 開發者的類比:
tinker就像 Node.js 的nodeinteractive shell,但它自動載入了整個 Laravel 應用程式。Python 開發者可以想像成python manage.py shell。
make 指令:程式碼產生器
make 系列指令是你最常用的 artisan 指令。它們按照 Laravel 的慣例幫你產生檔案,省掉手動建立和複製貼上:
php artisan make:controller # Controller
php artisan make:model # Eloquent Model
php artisan make:migration # 資料庫遷移
php artisan make:middleware # Middleware
php artisan make:request # Form Request(表單驗證)
php artisan make:policy # 授權 Policy
php artisan make:command # 自訂 Artisan 指令
php artisan make:event # Event
php artisan make:listener # Event Listener
php artisan make:job # Queue Job
php artisan make:mail # Mail
php artisan make:notification # Notification
php artisan make:test # 測試
不用全背,需要的時候 php artisan list make 查就好。
Route 基礎:URL 對應到程式碼
打開 routes/web.php,你會看到預設內容:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
一行就把根路徑 / 對應到 welcome 這個 Blade view。這就是 Laravel 路由的核心概念:定義 URL → 指定處理邏輯 → 回傳 Response。
基本路由寫法
// GET 請求
Route::get('/about', function () {
return view('about');
});
// POST 請求
Route::post('/contact', function () {
// 處理表單提交
return redirect('/thank-you');
});
// 帶參數的路由
Route::get('/products/{id}', function (string $id) {
return "商品編號:{$id}";
});
// 可選參數
Route::get('/products/{category?}', function (?string $category = null) {
return $category ? "分類:{$category}" : '所有商品';
});
路由搭配 Controller
在真實專案中,我們不會把邏輯寫在路由檔裡(那會變成一坨義大利麵)。取而代之,我們用 Controller 來處理:
php artisan make:controller PageController
這會在 app/Http/Controllers/ 產生一個檔案:
<?php
namespace App\Http\Controllers;
class PageController extends Controller
{
public function home()
{
return view('home');
}
public function about()
{
return view('about');
}
}
然後在路由裡指向 Controller:
use App\Http\Controllers\PageController;
Route::get('/', [PageController::class, 'home']);
Route::get('/about', [PageController::class, 'about']);
Express 開發者注意: Laravel 的路由語法是
Route::get('/path', [Controller::class, 'method'])。第二個參數不是 callback,而是一個陣列[類別, 方法名]。這跟 Express 的router.get('/path', controller.method)概念一樣,語法不同。
查看所有路由
php artisan route:list
GET|HEAD / ...................................................... PageController@home
GET|HEAD /about ................................................. PageController@about
這個指令會列出所有已註冊的路由、HTTP 方法和對應的 Controller,debug 時超級好用。
Blade 初體驗:你的第一個 View
Blade 是 Laravel 的模板引擎。如果你用過 EJS(Express)、Jinja2(Python)或 ERB(Rails),概念完全一樣——在 HTML 裡嵌入動態資料。
Blade 檔案放在 resources/views/,副檔名是 .blade.php。
基本語法
<!-- resources/views/home.blade.php -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<title>{{ $title }}</title>
</head>
<body>
<h1>{{ $title }}</h1>
<p>{{ $description }}</p>
{{-- 這是 Blade 註解,不會輸出到 HTML --}}
@if($products->count() > 0)
<ul>
@foreach($products as $product)
<li>{{ $product->name }} - ${{ $product->price }}</li>
@endforeach
</ul>
@else
<p>目前沒有商品</p>
@endif
</body>
</html>
Blade 語法速查
| 語法 | 用途 | 類比 |
|---|---|---|
{{ $var }} | 輸出並自動 HTML 跳脫 | EJS 的 <%= var %> |
{!! $html !!} | 輸出原始 HTML(不跳脫) | EJS 的 <%- html %> |
@if / @else / @endif | 條件判斷 | Jinja2 的 {% if %} |
@foreach / @endforeach | 迴圈 | Jinja2 的 {% for %} |
@extends('layout') | 繼承版面 | Jinja2 的 {% extends %} |
@section / @yield | 定義/填充區塊 | Jinja2 的 {% block %} |
{{-- 註解 --}} | Blade 註解 | <!-- --> 但不輸出 |
從 Controller 傳資料給 View
// 在 Controller 裡
public function home()
{
return view('home', [
'title' => '揪好買 JiuHaoMai',
'description' => '台灣最有溫度的團購平台',
]);
}
view() 的第二個參數是一個陣列,key 會變成 View 裡的變數名。'title' => '揪好買' 在 Blade 裡就是 $title。
也可以用 compact() 語法糖:
public function home()
{
$title = '揪好買 JiuHaoMai';
$description = '台灣最有溫度的團購平台';
return view('home', compact('title', 'description'));
}
Layout 機制:避免重複的 HTML
每個頁面都寫一遍 <html><head><body> 很蠢。Blade 的 Layout 機制幫你解決這個問題:
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@yield('title', '揪好買') | 揪好買 JiuHaoMai</title>
</head>
<body>
<nav>
<a href="/">首頁</a>
<a href="/about">關於我們</a>
</nav>
<main>@yield('content')</main>
<footer>
<p>© 2026 揪好買 JiuHaoMai</p>
</footer>
</body>
</html>
<!-- resources/views/home.blade.php -->
@extends('layouts.app')
@section('title', '首頁')
@section('content')
<h1>歡迎來到揪好買</h1>
<p>台灣最有溫度的團購平台</p>
@endsection
@extends 指定要繼承哪個 layout,@section 填入 layout 裡 @yield 留下的空位。概念跟 Django 的 template inheritance 一模一樣。
.env 設定:環境變數管理
專案根目錄的 .env 檔案是 Laravel 的環境設定中心。所有敏感資訊(資料庫密碼、API Key、第三方服務密鑰)都放在這裡,而不是寫死在程式碼中。
# .env(節錄重要設定)
APP_NAME="揪好買"
APP_ENV=local
APP_KEY=base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
APP_DEBUG=true
APP_URL=http://localhost:8000
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=jiu_hao_mai
# DB_USERNAME=root
# DB_PASSWORD=
MAIL_MAILER=log
在程式碼中讀取環境變數
// 方法一:env() 函式(只在 config 檔中使用)
// config/app.php
'name' => env('APP_NAME', '揪好買'),
// 方法二:config() 函式(在應用程式中使用)
$appName = config('app.name'); // '揪好買'
重要規則: 永遠不要在 Controller 或 Model 裡直接呼叫
env()。原因是 Laravel 在 production 會快取 config,env()會回傳null。正確做法是在config/*.php裡用env()讀取,然後在程式碼裡用config()存取。這個坑我保證你遲早會踩到,所以現在就記住。
.env vs .env.example
| 檔案 | 進版控 | 用途 |
|---|---|---|
.env | ❌ 不進 | 真正的環境變數(含密碼) |
.env.example | ✅ 進 | 環境變數範本(不含真實值) |
團隊開發時,新成員 clone 專案後會複製 .env.example 成 .env,然後填入自己的設定。
cp .env.example .env
php artisan key:generate # 產生 APP_KEY
與其他框架的比較
| 框架 | 環境變數 | 設定系統 |
|---|---|---|
| Laravel | .env + config/*.php | config('key') |
| Express | .env + dotenv 套件 | process.env.KEY |
| Django | settings.py + django-environ | settings.KEY |
Laravel 的 .env + config 兩層架構看起來多一步,但好處是 config 可以快取(php artisan config:cache),production 下效能更好。
揪好買專案啟動:建立首頁
理論講完了,讓我們動手把「揪好買」的首頁做出來。
Step 1:建立 Controller
php artisan make:controller PageController
編輯 app/Http/Controllers/PageController.php:
<?php
namespace App\Http\Controllers;
class PageController extends Controller
{
public function home()
{
return view('home', [
'title' => '揪好買 JiuHaoMai',
'description' => '台灣最有溫度的團購平台——找好物、揪好友、一起買更划算!',
'features' => [
['icon' => '🛒', 'title' => '輕鬆開團', 'desc' => '三步驟建立團購,分享連結就能揪人'],
['icon' => '👥', 'title' => '揪團省更多', 'desc' => '人數越多折扣越大,好康大家一起享'],
['icon' => '🔔', 'title' => '到貨通知', 'desc' => '成團、付款、出貨,每一步都即時通知你'],
],
]);
}
}
Step 2:建立 Layout
建立 resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@yield('title', '揪好買') | 揪好買 JiuHaoMai</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, 'Noto Sans TC', sans-serif;
color: #1a1a2e;
}
nav {
background: #16213e;
padding: 1rem 2rem;
}
nav a {
color: #e2e2e2;
text-decoration: none;
margin-right: 1.5rem;
font-weight: 500;
}
nav a:hover {
color: #0f9b8e;
}
.brand {
font-size: 1.25rem;
font-weight: 700;
color: #0f9b8e;
}
main {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
footer {
text-align: center;
padding: 2rem;
color: #666;
font-size: 0.875rem;
}
</style>
</head>
<body>
<nav>
<a href="/" class="brand">🛒 揪好買</a>
<a href="/about">關於我們</a>
</nav>
<main>@yield('content')</main>
<footer>
<p>© 2026 揪好買 JiuHaoMai — 用 Laravel 12 打造</p>
</footer>
</body>
</html>
Step 3:建立首頁 View
建立 resources/views/home.blade.php:
@extends('layouts.app')
@section('title', '首頁')
@section('content')
<div style="text-align: center; padding: 3rem 0;">
<h1 style="font-size: 2.5rem; margin-bottom: 0.5rem;">{{ $title }}</h1>
<p style="font-size: 1.25rem; color: #666;">{{ $description }}</p>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; margin-top: 2rem;">
@foreach($features as $feature)
<div style="text-align: center; padding: 2rem; border: 1px solid #eee; border-radius: 12px;">
<div style="font-size: 2.5rem;">{{ $feature['icon'] }}</div>
<h3 style="margin: 0.75rem 0 0.5rem;">{{ $feature['title'] }}</h3>
<p style="color: #666; font-size: 0.9rem;">{{ $feature['desc'] }}</p>
</div>
@endforeach
</div>
<div style="text-align: center; margin-top: 3rem;">
<p style="color: #999;">🚧 更多功能開發中——跟著這本書一起打造!</p>
</div>
@endsection
Step 4:設定路由
編輯 routes/web.php:
<?php
use App\Http\Controllers\PageController;
use Illuminate\Support\Facades\Route;
Route::get('/', [PageController::class, 'home']);
Step 5:啟動!
php artisan serve
打開 http://localhost:8000,你應該會看到:
- 頂部導航列,有「揪好買」品牌名和連結
- 大標題「揪好買 JiuHaoMai」
- 三個功能特色卡片:輕鬆開團、揪團省更多、到貨通知
- 底部版權資訊
恭喜,你的第一個 Laravel 應用正在運行了。
剛才發生了什麼?
讓我們追蹤一下這個 request 的旅程(下一章會更深入):
- 瀏覽器發送
GET /到localhost:8000 - Laravel 的
public/index.php接收 request - 路由系統查
routes/web.php,找到GET /對應PageController@home PageController::home()執行,準備資料,呼叫view('home', [...])- Blade 引擎渲染
resources/views/home.blade.php(套用 layout) - 最終 HTML 回傳給瀏覽器
這六步就是 Laravel 處理每一個 HTTP request 的基本流程。下一章我們會深入 Request Lifecycle,了解 Middleware 和 Service Container 如何在這個流程中扮演角色。
小結:十分鐘後你已經會什麼了
讓我們盤點一下這一章學到的東西:
- 安裝 Laravel——
composer create-project或laravel new,一行搞定 - 目錄結構——知道
routes/、app/Http/Controllers/、resources/views/、database/各自的職責 - Artisan CLI——
serve、make:controller、route:list、tinker - 路由系統——在
routes/web.php定義 URL,對應到 Closure 或 Controller - Blade 模板——
{{ }}輸出資料、@if/@foreach控制流程、@extends/@yield版面繼承 - 環境設定——
.env存放敏感資訊,config()讀取設定,永遠不在 Controller 裡直接呼叫env() - 實作——「揪好買」首頁已經跑起來了
下一章,我們要揭開 Laravel 的「魔法」面紗——Request Lifecycle、Service Container 和 Middleware。聽起來很抽象,但理解這些概念之後,你才能真正駕馭這個框架,而不只是「它 work 了但我不知道為什麼」。