2025.08.29
- 技術情報
- テックブログ
技術投稿│VueとLaravelを仲良く別居させてみた

自己紹介
ニックネーム:メッシュ
経験年数:6年
高等専門学校を卒業後、東京のシステム開発会社に2年半勤務。その後、2022年3月に株式会社アクロクレインに中途入社。
現在は主にPHPを使用したWeb系の開発を行っている。
はじめに
これまで自分が Laravel を使った開発では、基本的に Blade テンプレートや Laravel Mix を使ってフロントを作ることが多かったです。いわゆる「Laravel の中にフロントも同居している」構成ですね。
一方で、フロントエンドに Vue や React といった別のフレームワークを使うシステム構成があることは知っていました。ただ、それが「API を経由してバックエンドとやり取りするスタイル」だということまでは理解していませんでした。
自分はこれまでそういう構成の開発に携わったことがなかったので、今回は Laravel(バックエンド) と Vue.js(フロントエンド) を別居させて開発環境を構築してみようと思います。
この記事では、その過程で学んだことを整理しながら、単独構成(Laravel にフロントも同居)と分離構成(Laravel と Vue を分ける)の違いをまとめていきたいと思います。
今回の構成の概要
今回は バックエンド(Laravel) と フロントエンド(Vue.js) を それぞれ独立したプロジェクトとして用意し、API 経由でつなぐ構成を Docker 上に用意しました。
- Laravel(バックエンド)
役割:API サーバー
DB とやり取りをし、Vue からのリクエストに JSON を返す
ポートは9000
で待ち受け - Vue.js(フロントエンド)
役割:画面側(SPA)
Axios 経由で API にアクセス
ポートは5173
で待ち受け - MySQL(DB)
役割:Laravel から利用(例:messages
テーブル) - nginx(リバースプロキシ)
役割:外部からの HTTP を受けて Laravel(PHP-FPM)へ転送(ホスト:9000
→ nginx:80
)
構成図で表すとこんな感じになります👇

このようにフロントとバックを分けることで、ローカルでもフロントとバックが別々に動いてやり取りする流れを確認できるようにしました。
Dockerによる開発環境の構築
ディレクトリ構成
project-root/
├─ docker/
│ ├─ php/ # PHP-FPM(Laravel)用Dockerfile等
│ ├─ nginx/
│ │ └─ default.conf # nginxの仮想ホスト設定
│ └─ db/
│ ├─ data/ # MySQLの永続化データ
│ ├─ my.cnf
│ └─ sql/ # 初期SQL(必要なら)
├─ backend/ # Laravel本体(/var/www にマウント)
├─ frontend/ # Vue(Vue CLI + yarn)
└─ compose.yml
docker-compose(compose.yml)
今回の構成は app(PHP-FPM/Laravel)、nginx、db(MySQL 8.0)、vue-app(Vue CLI) の4サービスです。
※補足
今回の構成では Vue CLI を利用しましたが、Vue 3以降では Viteへの移行が推奨されています。
本記事では手元の環境に合わせて Vue CLI を使いましたが、今後新しくプロジェクトを始める際には Vite の利用を検討すると良いでしょう。
services:
app:
container_name: app
build: ./docker/php
volumes:
- ./backend:/var/www
nginx:
image: nginx
container_name: nginx
ports:
- "9000:80" # ホスト9000 → nginx 80(Laravelの入口)
volumes:
- ./backend:/var/www
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
working_dir: /var/www
depends_on:
- app
db:
image: mysql:8.0
container_name: db
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: database
MYSQL_USER: db-user
MYSQL_PASSWORD: db-pass
TZ: "Asia/Tokyo"
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- ./docker/db/data:/var/lib/mysql
- ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
- ./docker/db/sql:/docker-entrypoint-initdb.d
ports:
- "3306:3306"
vue-app:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "5173:8080" # Vue CLI dev サーバ(コンテナ8080)→ ホスト5173
volumes:
- ./frontend/app:/src
tty: true
environment:
- NODE_ENV=development
nginx の仮想ホスト例(docker/nginx/default.conf)
server {
listen 80;
root /var/www/app/public;
index index.php;
location / {
root /var/www/app/public;
index index.php;
# try_files $uri $uri/ /index.php$query_string;
try_files $uri $uri/ /index.php?$query_string;
}
location ~ .php$ {
try_files $uri =501;
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
プロジェクト作成
- コンテナ起動(初回はビルド付き)
docker compose up -d --build
- Laravelプロジェクトを作成
docker compose exec app bash composer create-project laravel/laravel app php artisan install:api
- Laravel APIをインストール
php artisan install:api
- Vueプロジェクトを作成
docker compose exec vue-app bash vue create .
Laravel の .env(抜粋)
APP_URL=http://localhost:9000
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=database
DB_USERNAME=db-user
DB_PASSWORD=db-pass
SANCTUM_STATEFUL_DOMAINS=localhost:5173
Vue CLI のプロキシ設定(frontend/vue.config.js)
Vue → /api
リクエストを nginx に中継します。Docker内ホスト名は nginx
を使います。
const { defineConfig } = require('@vue/cli-service')
module.exports = {
devServer: {
host: "0.0.0.0",
proxy: {
"^/api": {
target: "http://nginx:80", // Docker上のLaravel
changeOrigin: true,
pathRewrite: { "^/api": "/api" }
}
}
}
}
起動手順
- Vue 側の開発サーバーを起動
docker compose exec vue-app yarn serve
動作確認
- フロント:
http://localhost:5173
→「Welcome to Your Vue.js App」というページが表示されたら成功。 - バック:
http://localhost:9000
→Laravelの初期ページが表示されたら成功。
以上で、ローカルでフロントとバックが別々に動いてやり取りする流れを再現できる準備が整います。
LaravelでのAPI実装
今回のバックエンドは Laravel 11 を使用しました。役割はシンプルで、メッセージを取得(GET)するAPI と 新しく登録(POST)するAPI を用意します。
モデルとマイグレーション
メッセージを保存するためのテーブルを用意します。
php artisan make:model Message -m
生成されたマイグレーション database/migrations/xxxx_xx_xx_create_messages_table.php
を以下のように修正。
public function up(): void
{
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->string('text');
$table->timestamps();
});
}
その後、マイグレーションを実行します。
php artisan migrate:fresh
コントローラー
app/Http/Controllers/Api/MessageController.php
を作成します。
Eloquent モデルを使って DB とやり取りします。
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Message;
use Illuminate\Http\Request;
class MessageController extends Controller
{
public function index()
{
return Message::orderBy('created_at', 'asc')->get();
}
public function store(Request $request)
{
$message = Message::create(['text' => $request->input('text')]);
return response()->json($message, 201);
}
}
ルーティング
API 専用のルートファイル routes/api.php
に以下を定義します。
<?php
use App\Http\Controllers\Api\MessageController;
use Illuminate\Support\Facades\Route;
Route::get('/messages', [MessageController::class, 'index']);
Route::post('/messages', [MessageController::class, 'store']);
/api/messages [GET]
… メッセージ一覧を取得/api/messages [POST]
… メッセージを新規追加
定義後に php artisan route:clear
コマンドでキャッシュクリア行います。
php artisan route:list
コマンドで GET|POST api/messages
のルートが表示されていたら設定完了です。
CORS設定
フロントエンドとバックエンドが別ドメイン(今回の場合 http://localhost:5173 と http://localhost:9000)で動作する場合、そのままではブラウザのセキュリティ制限により API リクエストがブロックされます。これを解決するために CORS (Cross-Origin Resource Sharing) 設定が必要です。backend/app/config/cors.php
に以下を定義します。
<?php
return [
'paths' => ['api/*'],
'allowed_origins' => ['http://localhost:5173'],
'allowed_methods' => ['*'],
'allowed_headers' => ['*'],
];
Vueでのフロントエンド実装
axiosをインストール
docker compose exec vue-app yarn add axios
コマンドでaxiosをインストールします。
コンポーネント構成(最小)
簡単にするため、画面は 1 枚に集約します。src/components/MessageBoard.vue
を作り、App.vue から読み込みます。
src/components/MessageBoard.vue
<template>
<div>
<h2>メッセージ一覧</h2>
<ul>
<li v-for="msg in messages" :key="msg.id">{{ msg.text }}</li>
</ul>
<h3>新しいメッセージ</h3>
<input v-model="newMessage" placeholder="メッセージを入力" />
<button @click="sendMessage">送信</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const messages = ref([])
const newMessage = ref('')
const fetchMessages = async () => {
const res = await axios.get('/api/messages')
messages.value = res.data
}
const sendMessage = async () => {
if (!newMessage.value.trim()) return
await axios.post('/api/messages', { text: newMessage.value })
newMessage.value = ''
await fetchMessages()
}
onMounted(fetchMessages)
</script>
src/App.vue
<template>
<MessageBoard />
</template>
<script>
import MessageBoard from './components/MessageBoard.vue'
export default { components: { MessageBoard } }
</script>
GET/POSTで試すシンプルなデモ
http://localhost:5173
を開く- 一覧が表示(空でもOK)。Network タブで初回の
GET /api/messages
が 200 で返ることを確認
- 入力欄にテキストを入れて「送信」
- 送信時に
POST /api/messages
が 201、直後の再取得GET /api/messages
が 200 で更新される
- 送信したテキストがDBに保存される
ここまででmessages の 登録(POST)→ 一覧取得(GET) が通ることを確認できました。
それぞれのメリット・デメリット
✅単独構成のメリット
- 学習コストが低い
Laravelだけで画面・ロジック・DB操作が完結
単一プロジェクトなので把握しやすい - 開発が早い(最短距離)
セットアップが一括で済む
API設計やCORSなどを考える必要がない - 単体リポジトリ・単体デプロイ
ソース管理・ビルド・デプロイが一元化
「とりあえず動くものを素早くつくる」時に最適 - デバッグ・トラブル対応がしやすい
画面からDBまでが1つのフローでつながっているため「どこで止まっているか」が把握しやすい
ログやステータスの追跡が一方向で済む
❌単独構成のデメリット
- 役割の分離がしにくい
フロントとバックが密結合
デザイナー/フロントエンド専門が入りにくい - UIの刷新が難しい
Blade中心だとリッチなUI(SPA化)に限界あり
VueやReactを部分的に組み込むのは可能だが煩雑 - 拡張・保守がしづらくなる
機能追加のたびに同じプロジェクトに影響が出る
開発規模が大きくなるとコードの見通しが悪くなる - 他システムとの連携に弱い
REST APIを意識した構成になっていないと再利用性が低い
スマホアプリや外部連携時に手戻りが出やすい
✅分離構成のメリット
- フロントとバックで完全に役割を分離できる
UI(Vue)とロジック(Laravel)が明確に分かれる
例:デザイナーがVueを触っても、APIに影響を与えない - 並行開発・分業がしやすくなる
複数人でも効率的に作業できる
→「フロント担当が画面開発中、バック側はAPI整備」 - 技術選定の自由度が高くなる
フロント:Vue / React / Nuxt などに柔軟に切り替え可能
バック:Laravel以外にもNode.jsやGoなどを使える - スケーラビリティが高い
将来的な「スマホアプリ」などにもAPIがそのまま流用できる
❌分離構成のデメリット
- 学習コストが上がる
VueとLaravelの2つの技術セットを学ぶ必要がある
通信・非同期処理・エラー処理など覚える範囲が広がる - 通信の考慮が必要(CORSやセキュリティ)
CORS問題・認証の取り扱いなど、知識が必要になる
ローカル環境でもURLやポートの管理が煩雑になることがある - プロジェクト構成が複雑になりやすい
2つのリポジトリ/ビルド/起動コマンドなどが増える
設定ミス・連携不具合など、環境依存トラブルが発生しやすくなる - 小規模開発ではオーバーエンジニアリングになる可能性
開発者少数、納期短い、仕様安定しているなら単独構成の方が早い場合も
どちらが良いかではなく“使い分け”が大事
あくまで“どちらが優れているか”ではなく、“どういうチーム・案件に向いているか”が大切です。
それぞれがどのようなプロジェクトに適しているか比較表を作成してみました。
比較項目 | 単独構成 | 分離構成 |
---|---|---|
チーム規模 | 1~5人(少人数) | 5人以上(役割分担あり) |
プロジェクト規模 | 小~中規模 | 中~大規模 |
適した開発体制 | 全員がフルスタック(兼任) | フロント・バックで分業 |
特徴 | シンプルで早く作れる | 柔軟・拡張性が高い |
向いている例 | 管理画面・社内ツール | 一般ユーザ向けWebサービス、SPA、外部連携あり |
おわりに
今回、Laravel と Vue を「同居」ではなく「別居」させる形で開発環境を構築してみました。普段は一体型で開発することが多かったので、今回試してみることで仕組みやメリット・デメリットをよく理解することができました。
もちろん、どちらの構成にも一長一短があります。小規模な開発や素早く作りたいときには一体型の構成が便利ですし、長期的に機能を拡張したりフロントの自由度を高めたいなら分離構成が有効です。重要なのは「どちらの方が優れている」ではなく、「プロジェクトの性質やチームの状況に応じてどう選ぶか」だと思います。
今回の学びをまとめると:
- やってみることで分離構成の仕組みを理解できた
- 使い分けの判断ができるようになった
- 普段意識していなかったバックとフロントの境界を意識できるようになった
ということです。
この記事が、同じように「フロントとバックを分けた構成ってどうなんだろう?」と気になっている方にとって、少しでも参考になれば幸いです。