1
1

More than 3 years have passed since last update.

Laravel CRUD(API)

Last updated at Posted at 2020-08-30

はじめに

前回、Laravelを使ってCRUDを作成したので、今回はWeb API版のCRUDを作っていきます。
vanilla JSで簡単なSPAを作って実際にAPIを使ってみます。
認証やセキュリティ対策などは扱いません。

前回までのあらすじ
Laravelのインストール、初期設定、GitHubの使用
Laravel CRUD

テーブル、レコードの作成

作成するテーブル

id name telephone email created_at updated_at
ID 名前 電話番号 メールアドレス 作成日時 更新日時

テーブルの作成

modelとmigrationファイル作成

terminal
php artisan make:model Models/Member -m

-mオプションをつけて、migrationファイルも一緒に作成します。

下記の2ファイルが作成されます。

  • app\Models\Member.php
  • database\migrations\xxxx_xx_xx_xxxxxx_create_members_table.php

migrationファイルの編集

database\migrations\xxxx_xx_xx_xxxxxx_create_members_table.php
public function up()
  {
    Schema::create('members', function (Blueprint $table) {
      $table->id();
      $table->string('name', 20);
      $table->string('telephone', 13)->nullable()->unique();
      $table->string('email', 255)->nullable()->unique();
      $table->timestamps();
    });
  }

public function down()
  {
    Schema::dropIfExists('members');
  }

migrationの実行

terminal
php artisan migrate

membersテーブルが作成されます。

レコードの作成

seederファイルの作成

terminal
php artisan make:seeder MembersTableSeeder

database\seeds\MembersTableSeeder.phpが作成されます。

seederファイルの編集

database\seeds\MembersTableSeeder.php
public function run()
  {
    DB::table('members')->insert(
      [
        [
          'name'=>'山田',
          'telephone'=>'xxxx-xxxxx',
          'email'=>'yamada@example.com',
          'created_at'=>now(),
          'updated_at'=>now(),
        ],

        [
          'name'=>'鈴木',
          'telephone'=>'yyyy-yyyy',
          'email'=>'suzuki@example.com',
          'created_at'=>now(),
          'updated_at'=>now(),
        ],

      ]

    );

  }

実行するseederファイルの登録

database\seeds\DatabaseSeeder.php
public function run()
  {
    //追加
    $this->call(MembersTableSeeder::class);
  }

seederファイルの実行

terminal
php artisan db:seed

membersテーブルにレコードが入ります。

APIの作成

controllerの作成

controllerファイルの作成

terminal
php artisan make:controller MemberController --resource

app\Http\Controllers\MemberController.phpが作成されます。

controllerファイルの編集

createとeditは不要なので削除します。

app\Http\Controllers\MemberController.php
//追加
use App\Models\Member;

public function index()
  {
    //追加
    $members = Member::all();

    return $members->toArray();
  }

public function store(Request $request)
  {
    //追加
    $member=new Member;

    $member->name=$request->input('name');
    $member->telephone=$request->input('telephone');
    $member->email=$request->input('email');

    $member->save();
  }

public function show($id)
  {
    //追加
    $member = Member::find($id);

    return $member->toArray();
  }

public function update(Request $request, $id)
  {
    //追加
    $member=Member::find($id);

    $member->name=$request->input('name');
    $member->telephone=$request->input('telephone');
    $member->email=$request->input('email');

    $member->save();
  }

public function destroy($id)
  {
    //追加
    $member=Member::find($id);

    $member->delete();
  }      

ルーティング

Route::resouceでCRUDに使うアクション7つの設定が行えますが、createとeditは使用しないので、除外します。

routes\api.php
//追加
Route::resource('member', 'MemberController')
  ->except([
    'create', 'edit',
  ]);

ルーティングの確認

terminal
php artisan route:list

動作確認の準備

作成したAPIの動作確認のために簡単なSPAを作ります。

  • バリデーションはHTML5の簡易バリデーションのみです。
  • ブラウザの履歴操作(進む、戻るなど)対応まではしません。

viewの作成

viewファイルを作成します。

resources\views\spa\spa.blade.phpを新規作成します。

viewファイルを編集します。

resources\views\spa\spa.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>簡易SPA</title>
  <script src="\js\app.js" async></script>
</head>
<body>

</body>
</html>

ルーティング

/spaにブラウザでアクセスしたときにresources\views\spa\spa.blade.phpを使うように設定します。

routes\web.php
Route::group(['prefix'=>'spa'], function () {
    Route::view('', 'spa/spa');
});

ルーティングの確認

terminal
php artisan route:list

SPAの作成

resources\js\app.jsを編集してindex、create、show、editに相当する画面を作成します。

resources\js\app.js
//操作しているIDを管理する変数currentIDの定義
let currentId = '';


//ルーティング[一覧表示]
const goIndexAnker = document.createElement('a');
goIndexAnker.innerHTML = '一覧表示に戻る';
goIndexAnker.href = '#';

goIndexAnker.addEventListener('click', (e) => {
  e.preventDefault();
  document.body.innerHTML = '';

  //操作しているIDの初期化
  currentId = '';
  getMembers();

  //URLの表示を/spaに変更
  window.history.pushState(null, "", "/spa");
});


//ルーティング[詳細表示画面に戻る]
const goDetailAnker = document.createElement('a');
goDetailAnker.innerHTML = '詳細表示に戻る';
goDetailAnker.href = '#';

goDetailAnker.addEventListener('click', (e) => {
  e.preventDefault();
  document.body.innerHTML = '';
  getMember();

  //URLの表示を/spa/{id}に変更
  window.history.pushState(null, "", "/spa/" + currentId);
});


//ルーティング[編集画面]
const goEditAnker = document.createElement('a');
goEditAnker.innerHTML = '編集する';
goEditAnker.href = '#';

goEditAnker.addEventListener('click', (e) => {
  e.preventDefault();
  document.body.innerHTML = '';
  getEditForm();

  //URLの表示を/spa/{id}/editに変更
  window.history.pushState(null, "", "/spa/" + currentId + "/edit");
});


//ルーティング[新規作成画面]
const goCreateAnker = document.createElement('a');
goCreateAnker.innerHTML = '新規登録';
goCreateAnker.href = '#';

goCreateAnker.addEventListener('click', (e) => {
  e.preventDefault();
  document.body.innerHTML = '';
  createMember();

  //URLの表示を/spa/createに変更
  window.history.pushState(null, "", "/spa/create");
});


//一覧表示画面作成
const getMembers = () => {

  //値の取得
  fetch('http://127.0.0.1:8000/api/member/')
    .then(response => {
    if (response.ok) {
      return response.text();
      } else {
        return Promise.reject(new Error('エラー'));
      }
   })
     .then(response => {

        //要素の作成
        var responseJson = JSON.parse(response);
        const table = document.createElement('table');
        table.createTHead();
        table.tHead.insertRow();
        table.tHead.rows[0].insertCell().innerHTML = '名前';
        table.tHead.rows[0].insertCell().innerHTML = '電話番号';
        table.tHead.rows[0].insertCell().innerHTML = 'メールアドレス';
        table.tHead.rows[0].insertCell().innerHTML = '詳細';
        table.createTBody();

        Object.keys(responseJson).forEach(function (key, index) {
          table.tBodies[0].insertRow();
          table.tBodies[0].rows[index].insertCell().innerHTML = responseJson[key]['name'];
          table.tBodies[0].rows[index].insertCell().innerHTML = responseJson[key]['telephone'];
          table.tBodies[0].rows[index].insertCell().innerHTML = responseJson[key]['email'];

          //ルーティング[詳細表示画面に進む]
          const goDetailAnker = document.createElement('a');
          goDetailAnker.href = '#';
          goDetailAnker.innerHTML = '詳細';
          goDetailAnker.id = responseJson[key]['id'];

          table.tBodies[0].rows[index].insertCell().appendChild(goDetailAnker);

          goDetailAnker.addEventListener('click', (e) => {
            e.preventDefault();
            currentId = e.target.id;
            document.body.innerHTML = '';
            getMember();

            //URLの表示を/spa/{id}に変更
            window.history.pushState(null, "", "/spa/" + currentId);
          })

        });

        //テーブルを配置
        document.body.appendChild(table);

      });

  document.body.appendChild(goCreateAnker);

};


//新規作成画面描画
const createMember = () => {

  //一覧表示への切り替え
  document.body.appendChild(goIndexAnker);

  //form作成
  const createForm = document.createElement('form');
  createForm.id = 'creatForm';
  document.body.appendChild(createForm);

  const nameLabel = document.createElement('label');
  nameLabel.innerHTML = '名前';
  nameLabel.htmlFor = 'nameInput';
  createForm.appendChild(nameLabel);


  const nameInput = document.createElement('input');
  nameInput.name = 'name';
  nameInput.type = 'text';
  nameInput.maxLength = 20;
  nameInput.id = 'nameInput';
  nameInput.required = 'required';
  createForm.appendChild(nameInput);


  const telephoneLabel = document.createElement('label');
  telephoneLabel.innerHTML = '電話番号';
  telephoneLabel.htmlFor = 'telephoneInput';
  createForm.appendChild(telephoneLabel);

  const telephoneInput = document.createElement('input');
  telephoneInput.name = 'telephone';
  telephoneInput.type = 'tel';
  telephoneInput.maxLength = 13;
  telephoneInput.id = 'telephoneInput';
  createForm.appendChild(telephoneInput);


  const emailLabel = document.createElement('label');
  emailLabel.innerHTML = 'メールアドレス';
  emailLabel.htmlFor = 'emailInput';
  createForm.appendChild(emailLabel);

  const emailInput = document.createElement('input');
  emailInput.name = 'email';
  emailInput.type = 'email';
  emailInput.maxLength = 255;
  emailInput.id = 'emailInput';
  createForm.appendChild(emailInput);

  //登録ボタン作成
  const storeInput = document.createElement('input');
  storeInput.value = '登録';
  storeInput.type = 'submit';
  createForm.appendChild(storeInput);

  //フォーム送信時
  createForm.addEventListener('submit', () => {

    //登録処理(store)に入力データを送る
    let formData = new FormData(document.getElementById('creatForm'));

    fetch('/api/member', {
      method: 'POST',
      body: formData,
    })
      .then((response) => {
        return response.text();
      })

    //一覧表示への切り替え処理
    document.body.innerHTML = '';
    getMembers();

    //URLの表示を/spaに変更
    window.history.pushState(null, "", "/spa");

  }, false);

};


//詳細表示画面描画メソッド
const getMember = () => {

  //一覧表示への切り替えリンク配置
  document.body.appendChild(goIndexAnker);

  //変更フォームへの切り替えリンク配置
  document.body.appendChild(goEditAnker);

  //値の取得
  fetch('http://127.0.0.1:8000/api/member/' + currentId)
    .then(response => {
      if (response.ok) {
        return response.text();
      } else {
        return Promise.reject(new Error('エラー'));
      }
    })
    .then(response => {
      const responseJson = JSON.parse(response);

      //要素の作成
      const displayName = document.createElement('p');
      displayName.innerHTML = '名前:' + responseJson['name'];
      document.body.appendChild(displayName);

      const displayTelephone = document.createElement('p');
      displayTelephone.innerHTML = '電話番号:' + responseJson['telephone'];
      document.body.appendChild(displayTelephone);

      const displayEmail = document.createElement('p');
      displayEmail.innerHTML = 'メールアドレス:' + responseJson['email'];
      document.body.appendChild(displayEmail);

      //削除フォーム作成
      const destroyForm = document.createElement('form');
      destroyForm.id = 'destroyForm';
      document.body.appendChild(destroyForm);

      //削除ボタン作成
      const destroyInput = document.createElement('input');
      destroyInput.value = '削除';
      destroyInput.type = 'submit';
      destroyForm.appendChild(destroyInput);

      //フォーム送信時の処理
      destroyInput.addEventListener('click', () => {

        fetch('/api/member/' + currentId, {
          method: 'DELETE',
          body: {},
        })
          .then((response) => {
            return response.text();
          });

        //一覧表示への切り替え
        document.body.innerHTML = '';
        getMembers();

      }, false);
    });
};


//更新画面表示
const getEditForm = () => {

  //一覧表示への切り替えリンク配置
  document.body.appendChild(goIndexAnker);

  //詳細表示への切り替えリンク配置
  document.body.appendChild(goDetailAnker);

  //値の取得
  fetch('http://127.0.0.1:8000/api/member/' + currentId)
    .then(response => {
      if (response.ok) {
        return response.text();
      } else {
        return Promise.reject(new Error('エラー'));
      }
    })
      .then(response => {
        const responseJson = JSON.parse(response);

        const editForm = document.createElement('form');
        editForm.id = 'editForm';
        document.body.appendChild(editForm);


        const nameLabel = document.createElement('label');
        nameLabel.innerHTML = '名前';
        nameLabel.htmlFor = 'nameInput';
        editForm.appendChild(nameLabel);


        const nameInput = document.createElement('input');
        nameInput.name = 'name';
        nameInput.type = 'text';
        nameInput.maxLength = 20;
        nameInput.id = 'nameInput';
        nameInput.required = 'required';
        nameInput.value = responseJson['name'];
        editForm.appendChild(nameInput);

        const telephoneLabel = document.createElement('label');
        telephoneLabel.innerHTML = '電話番号';
        telephoneLabel.htmlFor = 'telephoneInput';
        editForm.appendChild(telephoneLabel);

        const telephoneInput = document.createElement('input');
        telephoneInput.name = 'telephone';
        telephoneInput.type = 'tel';
        telephoneInput.maxLength = 13;
        telephoneInput.id = 'telephoneInput';
        telephoneInput.value = responseJson['telephone'];
        editForm.appendChild(telephoneInput);

        const emailLabel = document.createElement('label');
        emailLabel.innerHTML = 'メールアドレス';
        emailLabel.htmlFor = 'emailInput';
        editForm.appendChild(emailLabel);

        const emailInput = document.createElement('input');
        emailInput.name = 'email';
        emailInput.type = 'email';
        emailInput.maxLength = 255;
        emailInput.id = 'emailInput';
        emailInput.value = responseJson['email'];
        editForm.appendChild(emailInput);


        //更新ボタン作成
        const updateInput = document.createElement('input');
        updateInput.value = '更新';
        updateInput.type = 'submit';
        editForm.appendChild(updateInput);

        //フォーム送信時の処理
        editForm.addEventListener('submit', (e) => {

          let formData = new FormData(document.getElementById('editForm'));

          fetch('/api/member/' + currentId, {
            headers: { "X-HTTP-Method-Override": "PATCH" },
            method: 'POST',
            body: formData,
          })
            .then((response) => {
              return response.text();
            })

          //画面を一覧表示に切り替え
          document.body.innerHTML = '';
          getMembers();

          //URLの表示を/spaに変更
          window.history.pushState(null, "", "/spa");
        });

      }, false);

};


//一覧表示を初期表示にします。
getMembers();

動作確認

JavaScriptのコンパイル

resources\js\app.jsをコンパイルします。

terminal
 npm run dev

簡易サーバーの起動

terminal
php artisan serve

ブラウザで/spaにアクセスして、動作確認をします。
Ctrl+Cで簡易サーバーを終了します。

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