1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[WIP] LaravelとVueJsをOpenAPIを介して結合する - Connect Laravel and VueJs by OpenAPI

Last updated at Posted at 2023-01-07

Environment

  • WSL2
  • Windows11
  • Docker
  • Laravel 9
  • Vue3
  • node v16

WIP

この記事は書きかけです。書いている途中に筆者が力尽きたので途中ですが投稿しています。更新予定は未定です。

Summary

LaravelにはInertiaというものがありますが、これはLaravel独自のライブラリであるので、VueJs側にLaravelのコードが入ることになります。マイクロサービス化を考慮すると、Laravel側はAPIとした方が、保守性としては高くなります。

  1. Laravelのバックエンドアプリケーションを作成します。Create Laravel backend.
  2. OpenAPI仕様書をLaravelから出力します。Generate OpenAPI from Laravel.
  3. VueJsのフロントエンドを生成します。Create VueJs frontend.
  4. OpenAPI generatorでコードを生成します。Generate client code by OpenAPI generator.
  5. レポジトリ層を実装します。Add repository layer.
  6. いい感じに結合します。Connect both. Needs Optimization.

Procedure

Create Laravel backend. バックエンド構築

  1. 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
    
    
  2. viteのsail upしているポートフォワーディングの設定を削除します。Delete port forwarding configuration for vite.

    # ...
            ports:
            - '${APP_PORT:-80}:80'
            # - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' <- remove this
    # ...
    
  3. マイグレーションを実行しておきます。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
    
    
  4. 毎回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]...
    
  5. 適当なテーブルとモデルを追加します。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
    
  6. 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 to FlightController::index().
    public function index()
    {
        return Flight::query()->paginate(10);
    }
    
    • GET /api/v1/flightの確認。Confirm GET /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":"&laquo; Previous","active":false},{"url":"http:\/\/localhost\/api\/v1\/flight?page=1","label":"1","active":true},{"url":null,"label":"Next &raquo;","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を用いて生成します。

  1. 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"
    
  2. OpenAPIの出力に必要なAttributeを記載していきます。Add PathItem attribute to controller.

    • タグをconfig/openapi.phpに記載します。これが後々効いて来ます。 Add tag to config/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');
        }
    }
    
    
  3. 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')

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?