Environment
- WSL2
- Windows11
- Docker
- Laravel 9
- Vue3
- node v16
WIP
この記事は書きかけです。書いている途中に筆者が力尽きたので途中ですが投稿しています。更新予定は未定です。
Summary
LaravelにはInertiaというものがありますが、これはLaravel独自のライブラリであるので、VueJs側にLaravelのコードが入ることになります。マイクロサービス化を考慮すると、Laravel側はAPIとした方が、保守性としては高くなります。
- Laravelのバックエンドアプリケーションを作成します。Create Laravel backend.
- OpenAPI仕様書をLaravelから出力します。Generate OpenAPI from Laravel.
- VueJsのフロントエンドを生成します。Create VueJs frontend.
- OpenAPI generatorでコードを生成します。Generate client code by OpenAPI generator.
- レポジトリ層を実装します。Add repository layer.
- いい感じに結合します。Connect both. Needs Optimization.
Procedure
Create Laravel backend. バックエンド構築
-
Laravelのバックエンドアプリケーションを作成します。Create Laravel App [ref: https://readouble.com/laravel/9.x/ja/installation.html]
mkdir laravel-vuejs-app cd laravel-vuejs-app curl -s "https://laravel.build/backend" | bash cd backend
-
viteのsail upしているポートフォワーディングの設定を削除します。Delete port forwarding configuration for vite.
# ... ports: - '${APP_PORT:-80}:80' # - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' <- remove this # ...
-
マイグレーションを実行しておきます。Run migration.
vendor/bin/sail up -d ~/backend$ vendor/bin/sail artisan migrate INFO Preparing database. Creating migration table ............................................................... 24ms DONE INFO Running migrations. 2014_10_12_000000_create_users_table ................................................... 34ms DONE 2014_10_12_100000_create_password_resets_table ......................................... 38ms DONE 2019_08_19_000000_create_failed_jobs_table ............................................. 32ms DONE 2019_12_14_000001_create_personal_access_tokens_table .................................. 64ms DONE
-
毎回
vendor/bin/sail
するのは面倒なので、エイリアスを設定します。 Set alias if you want.$ nano ~/.bashrc # Add the following alias sail='[ -f sail ] && sh sail || sh vendor/bin/sail' # Finish $ source ~/.bashrc sail --help Laravel Sail Usage: sail COMMAND [options] [arguments]...
-
適当なテーブルとモデルを追加します。Add example table and model.
sail artisan make:migration CreateFlights # Edit up() method # This is identical to <https://laravel.com/docs/9.x/migrations#migration-structure> public function up() { Schema::create('flights', function (Blueprint $table) { $table->id(); $table->string('name'); # add $table->string('airline'); # add $table->timestamps(); }); } # And run migration again. sail artisan migrate
-
APIの作成。Create API endpoints.
- Controllerの作成。Generate controller.
sail artisan make:controller FlightController --resource
-
/routes/api.php
にAPIの作成。Create route to/routes/api.php
Route::prefix('v1')->group(function () { Route::prefix('flight')->group(function () { Route::get('/', [FlightController::class, 'index']); Route::get('/{id}', [FlightController::class, 'show']); Route::post('/', [FlightController::class, 'create']); Route::put('/{id}', [FlightController::class, 'update']); Route::delete('/{id}', [FlightController::class, 'destroy']); }); });
-
FlightController::index()
にactionの記載。Write controller action toFlightController::index()
.
public function index() { return Flight::query()->paginate(10); }
-
GET /api/v1/flight
の確認。ConfirmGET /api/v1/flight
$ curl localhost/api/v1/flight {"current_page":1,"data":[],"first_page_url":"http:\/\/localhost\/api\/v1\/flight?page=1","from":null,"last_page":1,"last_page_url":"http:\/\/localhost\/api\/v1\/flight?page=1","links":[{"url":null,"label":"« Previous","active":false},{"url":"http:\/\/localhost\/api\/v1\/flight?page=1","label":"1","active":true},{"url":null,"label":"Next »","active":false}],"next_page_url":null,"path":"http:\/\/localhost\/api\/v1\/flight","per_page":10,"prev_page_url":null,"to":null,"total":0}
Generate OpenAPI from Laravel. OpenAPI仕様書の出力
Generate OpenAPI by laravel-openapi https://vyuldashev.github.io/laravel-openapi/.
laravel-openapiを用いて生成します。
-
Introduce laravel-openapi. laravel-openapiの導入
sail composer require vyuldashev/laravel-openapi # Export provider. sail php artisan vendor:publish --provider="Vyuldashev\LaravelOpenApi\OpenApiServiceProvider" --tag="openapi-config"
-
OpenAPIの出力に必要なAttributeを記載していきます。Add
PathItem
attribute to controller.- タグを
config/openapi.php
に記載します。これが後々効いて来ます。 Add tag toconfig/openapi.php
.
[ # ... 'tags' => [ [ 'name' => 'flight', 'description' => 'Flight controller action.' ] ] # ... ]
- FlightController.php
#[OpenApi\PathItem] class FlightController extends Controller { #[OpenApi\Operation(tags: ['flight'], method: 'GET')] #[OpenApi\Response(factory: ListFlightResponse::class)] public function index() { return Flight::query()->paginate(10); } # ... }
- Schemaを生成し、curlで確認したレスポンスの内容を記載します。Generate and fill up schema.
# Auto generate schema of model. sail artisan openapi:make-schema Flight -m Flight
use Vyuldashev\LaravelOpenApi\Contracts\Reusable; # Add this class FlightSchema extends SchemaFactory implements Reusable { #... }
# Create response. sail artisan openapi:make-response ListFlight
use GoldSpecDigital\ObjectOrientedOAS\Objects\MediaType; use GoldSpecDigital\ObjectOrientedOAS\Objects\Response; use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema; use Vyuldashev\LaravelOpenApi\Factories\ResponseFactory; class ListFlightResponse extends ResponseFactory { public function build(): Response { $response = Schema::object()->properties( Schema::integer('current_page'), Schema::array('data'), Schema::string('first_page_url'), Schema::integer('from')->nullable(), Schema::integer('to')->nullable(), Schema::string('last_page_url'), Schema::array('links')->items( Schema::object()->properties( Schema::string('url')->nullable(), Schema::string('label'), Schema::boolean('active') ) ), Schema::string('next_page_url')->nullable(), Schema::string('path'), Schema::integer('per_page'), Schema::string('prev_page_url')->nullable(), ); return Response::ok() ->content(MediaType::json()->schema($response)) ->description('Successful response'); } }
- タグを
-
OpenAPIを出力します。Export OpenAPI.
sail artisan openapi:generate > api.json
{ "openapi": "3.0.2", "info": { "title": "Laravel", "version": "1.0.0" }, "servers": [ { "url": "http:\/\/localhost" } ], "paths": { "\/api\/v1\/flight": { "get": { "tags": [ "flight" ], "summary": "Display a listing of the resource.", "responses": { "200": { "description": "Successful response", "content": { "application\/json": { "schema": { "type": "object", "properties": { "current_page": { "type": "integer" }, "data": { "type": "array" }, "first_page_url": { "type": "string" }, "from": { "type": "integer", "nullable": true }, "to": { "type": "integer", "nullable": true }, "last_page_url": { "type": "string" }, "links": { "type": "array", "items": { "type": "object", "properties": { "url": { "type": "string", "nullable": true }, "label": { "type": "string" }, "active": { "type": "boolean" } } } }, "next_page_url": { "type": "string", "nullable": true }, "path": { "type": "string" }, "per_page": { "type": "integer" }, "prev_page_url": { "type": "string", "nullable": true } } } } } } } } } }, "tags": [ { "name": "flight", "description": "Flight controller action." } ] }
Create VueJs frontend. VueJsフロントエンドの構築
VueJsの例 https://ja.vitejs.dev/guide/ に沿ってひな形を生成します。Craete frontend application according to https://ja.vitejs.dev/guide/.
$ npm create vite@latest
Need to install the following packages:
create-vite@latest
Ok to proceed? (y)
✔ Project name: … frontend
✔ Select a framework: › Vue
✔ Select a variant: › TypeScript
Scaffolding project in /home/kuroki/laravel-vuejs-app/frontend...
Done. Now run:
cd frontend
npm install
npm run dev
npm notice
npm notice New major version of npm available! 8.1.2 -> 9.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.2.0
npm notice Run npm install -g npm@9.2.0 to update!
npm notice
$ cd frontend
$ npm install
Generate client code by OpenAPI generator. OpenAPI generatorによるコードの出力
typescript-fetch https://openapi-generator.tech/docs/generators/typescript-fetch で生成します。Generate client code by typescript-fetch option.
mkdir src/api
cp ../backend/api.json ./api.json
docker run --rm -v "${PWD}:/local" \
-u `id -u`:`id -g` \
openapitools/openapi-generator-cli generate \
-i /local/api.json \
-g typescript-fetch \
-o /local/src/api
--additional-properties withInterfaces=true
Add repository layer
respotioryの定義を作成し、グローバル変数としてマウントします。
src/repository.ts
import { FlightApi } from "./api";
interface AppRepository {
flight: FlightApi
}
const getAppRespository = () => {
flight: new FlightApi()
}
export const repository = getAppRespository();
src/typing/repository.d.ts
import AppRepository from './repository'
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$repository: AppRepository;
}
}
src\main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { repository } from './repository';
const app = createApp(App);
app.config.globalProperties.$repository = repository; // 追加
app.mount('#app')