Laravel 11 + Inertia.js + Vue 3 + TypeScriptでモダンなフルスタック環境を構築

# 概要
LaravelとInertia.jsを使えば、フロントエンドとバックエンドをシームレスに連携できます。本記事では、Laravel 11にVue 3とTypeScriptを組み合わせ、効率的なフルスタックアプリケーション開発環境を構築する方法を解説します。Dockerでの環境設定から、初期セットアップのコマンドや設定手順までを網羅します。

# 目的

この記事の目的は、Laravel 11とInertia.js、Vue 3、TypeScriptを組み合わせて、モダンなフルスタック開発環境を構築することです。  
具体的には以下を達成します：

1. **バックエンドとフロントエンドの統一**  
   - Laravelの堅牢なバックエンドと、Vue 3のモダンなフロントエンドをInertia.jsでシームレスに連携。
   - APIを介さず、シンプルな開発体験を実現。

2. **型安全なフロントエンド開発**  
   - TypeScriptを導入し、Vueコンポーネントに型安全性を付与。
   - 大規模プロジェクトでもエラーを未然に防ぐ仕組みを構築。

3. **モダンな開発体験の提供**  
   - 最新のフロントエンド技術を取り入れることで、開発効率とパフォーマンスを向上。
   - Viteを活用した高速なビルドプロセスの導入。

この構築を通じて、チーム全体で統一されたフルスタック環境を実現し、効率的な開発体制を構築します。


# 前提条件

この記事を進めるにあたり、以下の条件が満たされていることを確認してください：

1. **Docker環境の構築が完了していること**  
   - [Laravel + Vue3 チーム開発を効率化！統一環境構築と品質向上の完全ガイド](https://qiita.com/ukei2021/items/4b2f047c72dd17c571c7)
   - [DockerでPHP 8.4/Nginx + Node 22 + MySQL環境を構築する方法](#) を参考に、Docker環境が整っていること。

2. **プロジェクトのディレクトリ構成が以下のようになっていること**  
   - `src`ディレクトリが存在し、Dockerコンテナ内でマウントされている。

#### 前回終わり時点のディレクトリの状態
```
rootdir
  ├─ docker
  │    └─ dockerの設定ファイル構築
  └─ src
       └─ dockerのテスト的なファイルを置いた
```



# 具体的な手順

Laravel 11 + Inertia.js + Vue 3 + TypeScriptのフルスタック環境を構築する手順です。


## 準備

まず、Docker環境を停止し、`src`ディレクトリを退避させて、新たに空の`src`を作成します。
```
$ cd docker
$ docker-compose down -v   
$ mv ../src ../src.bk
$ mkdir ../src
```
作業後のディレクトリ構成は以下のようになります：
```
rootdir
  ├─ docker
  │    └─ dockerの設定ファイル構築
  ├─ src
  │    └─ 空
  └─ src.bk
       └─ dockerのテスト的なファイルを置いた
```

## バックエンドのセットアップ
### コンテナに入る
```
$ docker-compose up --build -d backend
$ docker exec -it backend-container /bin/bash
```
### Laravelのインストール
以下のコマンドを実行してLaravelをインストールします：
```
$ composer create-project laravel/laravel .
```

インストールが完了すると、srcディレクトリの中身が以下のようになります：
```
src
  ├─ app
  ├─ bootstrap
  ├─ config
  ├─ database
  ├─ lang
  ├─ public
  ├─ resources
  ├─ routes
  ├─ storage
  ├─ tests
  ├─ vendor
  ├─ .env
  ├─ .env.example
  ├─ artisan
  ├─ composer.json
  ├─ composer.lock
  ├─ package.json
  ├─ phpunit.xml
  ├─ README.md
  └─ webpack.mix.js
```

### Inertia.jsのインストール
Inertia.jsをインストールします：
```
$ composer require inertiajs/inertia-laravel
```
以下のコマンドでInertia.jsのミドルウェアを作成します：
```
$ php artisan inertia:middleware
```
作成されたHandleInertiaRequests.phpを確認します：
```
$ ls -l app/Http/Middleware/HandleInertiaRequests.php 
-rw-r--r-- 1 root root 894 Dec 29 11:02 app/Http/Middleware/HandleInertiaRequests.php
```
確認ができたら、コンテナから抜けます：
```
$ exit
```


## フロントエンドのセットアップ
### コンテナに入る
```
$ docker-compose up --build -d frontend
$ docker exec -it frontend-container /bin/bash
```
### Vue 3とTypeScriptのインストール
以下のコマンドを実行して、Vue 3とTypeScriptをインストールします：

```
$ npm install @inertiajs/vue3 vue
$ npm install --save-dev typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser vue-tsc @vitejs/plugin-vue vite
```
インストールされたパッケージを確認します：
```
$ npm list @inertiajs/vue3 vue
$ npm list --dev typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser 
```

確認後、コンテナから抜けます：
```
$ exit
```

## ソースコードの編集
以下のコードをそれぞれのファイルに追加または修正してください：

#### **`resources/js/app.ts`**
以下のコードを`resources/js/app.ts`に記述してください。  
もし`app.js`が既に存在する場合は、`app.ts`にリネームしてください。
```app.ts
import './bootstrap';

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
import { DefineComponent } from 'vue';

createInertiaApp({
  resolve: (name: string): DefineComponent => {
    const pages = import.meta.glob<DefineComponent>('./Pages/**/*.vue', { eager: true });
    const page = pages[`./Pages/${name}.vue`];
    if (!page) {
      throw new Error(`Page not found: ${name}`);
    }
    return page;
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el);
  },
});
```

#### **`resources/views/app.blade.php`**
```html:app.blade.php
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Laravel + Inertia</title>
        @vite(['resources/css/app.css', 'resources/js/app.ts'])
    </head>

    <body>
        @inertia
    </body>
</html>
```

#### **`routes/web.php`**

```web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\SampleController;

Route::get('/sample', [SampleController::class, 'sample']);
```

#### **`rapp/Http/Controllers/SampleController.php`**

```SampleController.php
<?php

namespace App\Http\Controllers;

use Inertia\Inertia;
use Inertia\Response;
use App\Services\Contracts\SampleServiceInterface;

class SampleController extends Controller
{
    function __construct(
        
    )
    {}

    public function sample(): Response
    {
        $message = "Hello World";
        return Inertia::render('SamplePage', [
            'message' => $message,
        ]);
    }
}
```

#### **`resources/js/Pages/SamplePage.vue`**

```SamplePage.vue
<template>
    <div class="container mt-5">
        <h1 class="text-primary">{{ message }}</h1>
        <button class="btn btn-success" @click="checkType">Click Me</button>
    </div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';

export default defineComponent({
    name: 'SamplePage',
    props: {
        message: {
            type: String as PropType<string>,
            required: true,
        },
    },
    methods: {
        checkType() {
            const value: number = 1;
            console.log(value);
            const unusedVar = 'This is not used';
            console.log(unusedVar);
        },
    },
});
</script>
<style scoped>

</style>
```

#### **`vite.config.ts`**

```vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    logLevel: 'error',
    plugins: [
        vue(),
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.ts',
            ],
            refresh: true,
        }),
    ],
    server: {
        host: '0.0.0.0',
        port: 5173,
        hmr: {
            overlay: true,
        },
    },
    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'resources/js'),
        },
    },
});

```

#### **`tsconfig.json`**

```tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "jsx": "preserve",
    "types": ["vite/client"],
    "baseUrl": ".",
    "paths": {
      "@/*": ["resources/js/*"]
    }
  },
  "include": [
    "resources/**/*.ts",
    "resources/**/*.d.ts",
    "resources/**/*.tsx",
    "resources/**/*.vue"
  ],
  "exclude": ["node_modules"]
}
```

#### **`app/Http/Middleware/HandleInertiaRequests.php`**

```HandleInertiaRequests.php
<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    /**
     * The root template that's loaded on the first page visit.
     *
     * @see https://inertiajs.com/server-side-setup#root-template
     *
     * @var string
     */
    protected $rootView = 'app';

    /**
     * Determines the current asset version.
     *
     * @see https://inertiajs.com/asset-versioning
     */
    public function version(Request $request): ?string
    {
        return parent::version($request);
    }

    /**
     * Define the props that are shared by default.
     *
     * @see https://inertiajs.com/shared-data
     *
     * @return array<string, mixed>
     */
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            //
        ]);
    }
}

```

これで環境が整いました！
次はアプリケーションの実行やデバッグに進みます。

# 確認

フロントエンドの変更を反映させるため、`frontend-container`を再起動して動作を確認します。

### コンテナの再起動

以下のコマンドで`frontend-container`を再起動します：
```
$ docker restart frontend-container
```

### 動作確認
以下のURLにアクセスして、アプリケーションが正常に動作するか確認してください：

#### URL:
[http://localhost/sample](http://localhost/sample)

#### 期待される画面:
以下のような画面が表示されれば成功です：
![スクリーンショット 2025-01-04 16.46.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032988/fc804a5b-614d-9242-b3c7-3fa759b32353.png)


# まとめ

ここまでで、Laravel 11とInertia.js、Vue 3、TypeScriptを使ったモダンなフルスタック開発環境を構築しました。この手順を通じて、以下のポイントを押さえることができました：

### **LaravelとVueの連携**

- Laravelの堅牢なバックエンドと、Vue 3のモダンなフロントエンドをシームレスに統合。
- Inertia.jsを活用して、APIレスでの効率的な開発を実現。

### **型安全なフロントエンドの構築**

- TypeScriptを導入し、Vueコンポーネントに型安全性を付与。
- 大規模なプロジェクトにも対応可能な開発環境を整備。

### **開発体験の向上**

- Viteを活用して、高速なビルドプロセスと開発サーバーを構築。
- フロントエンドとバックエンドの変更がリアルタイムに反映される快適な開発環境を提供。

この環境を活用することで、チーム全体で統一された開発フローを確立し、効率的なモダンアプリケーションの構築が可能になります。

---

### 次の記事の紹介

ここまでで基盤となるフルスタック環境が整いました。次の記事では、**Laravel 11 + Vue 3プロジェクトにSassとBootstrap 5を導入する方法**を解説します。

### **次の記事はこちら**  
[Laravel 11 + Vue 3プロジェクトにSassとBootstrap 5を導入する方法](https://qiita.com/ukei2021/items/2a0916ffc9c6ef900cb4)

この記事では、以下のポイントを解説予定です：

- **Bootstrap 5の導入とカスタマイズ**  
  フロントエンドのUIフレームワークとしてBootstrap 5を導入し、必要に応じたカスタマイズ方法を学びます。
  
- **Sassによるスタイル管理**  
  Sassを使用して、効率的でモジュール化されたスタイルの記述方法を解説します。
  
- **Vueコンポーネントとスタイルの統合**  
  Vue 3のコンポーネント設計に合わせたスタイルの適用方法を実践的に学びます。

これにより、プロジェクト全体で統一感のあるデザインを実現し、効率的なスタイル管理が可能になります。

引き続き良かったら読んでください！


### 関連記事
- [Laravel + Vue3 チーム開発を効率化！統一環境構築と品質向上の完全ガイド](https://qiita.com/ukei2021/items/4b2f047c72dd17c571c7)
- [DockerでPHP 8.4/Nginx + Node 22 + MySQL環境を構築する方法](https://qiita.com/ukei2021/items/49eb61e4db221346dfe0)
- [Laravel 11 + Vue 3プロジェクトにSassとBootstrap 5を導入する方法](https://qiita.com/ukei2021/items/2a0916ffc9c6ef900cb4)
- [Laravelでテストコード込みのサービス・リポジトリパターンを実装する方法](https://qiita.com/ukei2021/items/53d3776f580cfd3a074b)
- [DevContainerを使いVSCodeでPHPStan・ESLint・TypeScriptを統合して静的解析をエラー検知する方法](https://qiita.com/ukei2021/items/d3a91b0a34188529b6f6)
- [Gitコミット時に自動チェック！HuskyでESLint・PHPStan・ユニットテストを効率化する方法](https://qiita.com/ukei2021/items/592b50cbe04aaeae575f)