LumenとAjaxで非同期のRESTアプリケーションを作ってみた

ajaxの勉強がてら、APIを叩きCRUDを実行するRESTなアプリケーションを作ってみました！

# 構成
Lumen5.6
MySQL
Bootstrap
ajax
Javascript(jQuery)
なんかを使って作ってみました。

# 作ったアプリケーション

https://nijitter.herokuapp.com/

githubにてソース管理をし、heroku（無料プラン）でデプロイしました。
今は見た目がショボくて残念な感じですがのちに色々リッチな感じに仕上げたいです。。！

リポジトリは[こちら](https://github.com/yuka-12/app)

# このアプリケーションでできること

- 新規投稿機能（Create）
- 過去記事の取得、表示（Read）
- 過去記事の編集（Update）
- 過去記事の削除（Delete）

# まずは全体像

## View

```index.php
<!DOCTYPE HTML>
<html lang="ja">
<head>
  <title>nijitter</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="css/app.css" rel="stylesheet">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
  <script src="bootstrap/js/bootstrap.min.js"></script>
  <script type="text/javascript" src="js/app.js"></script>
</head>
<body>
  <div class="center-block">
  <div class="title m-3 p-3 text-center">
    <h1>NIJITTER</h1>
  </div>
  <div class="container text-center">
  <div class="form col-sm-5 center-block">
    <form id="form" method="post" accept-charset="utf-8" return false>
    <div class="form-group">
      <label for="name">Name:</label>
      <input type="text" id="name" class="form-control" name="name"><br>
    </div>
    <div class="form-group">
      <label for="text">Text:</label>
      <textarea id="text" class="form-control" name="text"></textarea>
    </div>
    </form>
    <input type="button" id="ajax" class="btn btn-dark" value="送信">
  </div>
  <br>
  <div class="result"></div>
  <br>
  <div class="posts"></div>
</div>
</div>
</body>
</html>

```

```app.js

       function get(){
         return $.ajax({
           url: './get',
           type: 'GET'
         })
       }
           get().done(function(result) {
             // HTMLを初期化
             $(".posts").html("");

             //HTMLを生成
             $(result).each(function(){
             $(
             '<h6>'+ this.name + '</h6>'+
             '<span>' + this.text + '</span><br>' +
             '<form method="post" action="" accept-charset="utf-8" return false>' +
             '<div class="rapper-form col-sm-5 center-block"><input type="button" class="update-button btn btn-dark" value="編集"> ' +
             '<input type="button" id="delete-' + this.id + '" class="delete btn btn-danger" name="' + this.id + '" value="削除"><br><br>' +
             '<div class="update-form">' +
             '<label for="name">Name:</label>' +
             '<input type="text" class="update-name form-control" name="name" value="' + this.name +'"><br>' +
             '<label for="text">Text:</label>' +
             '<textarea class="update-text form-control" name="text">' + this.text + '</textarea><br>' +
             '<input type="button" id="update-' + this.id + '" class="update btn btn-dark" name="' + this.id + '" value="保存">' +
             '</div></form><br></div>'

           ).appendTo('.posts');
           });

           $('.update-form').hide();

            }).fail(function(result) {
                console.log(result);
            });


       $(function(){
           $('#ajax').on('click',function(){
               $.ajax({
                   url:'./post',
                   type:'POST',
                   data:{
                       'name':$('#name').val(),
                       'text':$('#text').val()
                   }
               })
               .done(function(){
                   location.reload();
               })
           });

           $(document).on('click', '.delete',function(){
             var deleteConfirm = confirm('削除してよろしいでしょうか？');

             if(deleteConfirm == true) {

              $.ajax({
                  url:'./delete',
                  type:'POST',
                  data:{
                      'id':$(this).attr("name")
                  }
              }).done(function(){
                location.reload();
              })
            } else {
              (function(e) {
                e.preventDefault()
              });
            }
            
          });

           $(document).on('click', '.update',function(){

               $.ajax({
                 url:'./update',
                 type:'POST',
                 data:{
                   'id':$(this).attr("name"),
                   'name':$(this).parent().find('.update-name').val(),
                   'text':$(this).parent().find('.update-text').val()
                 }
                   }).done(function(){
                       location.reload();
                   })
           });

           $(document).on('click', '.update-button',function(){
             $(this).parent().find('.update-form').addClass('open');
             $('.open').slideToggle(300);
             $(this).parent().find('.update-form').removeClass('open');
           });


       });

```

## ルーティング

```web.php
<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/


$router->get('/', function () use ($router) {
    return view('index');
});

$router->get('/get', 'AppController@get');
$router->post('/post', 'AppController@post');
$router->post('/delete', 'AppController@delete');
$router->post('/update', 'AppController@update');

```

## コントローラー（API）

```AppController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Post;

class AppController extends Controller
{

  public function get()
  {
    $result = Post::orderBy('id','desc')->get()->toArray();
    return $result;

  }

  public function post(Request $request)
  {
    if ($request['name'] && $request['text']) {
    $name = $request['name'];
    $text = $request['text'];

    $post = new Post();
    $post->name = $name;
    $post->text = $text;
    $post->save();

  }

  }

  public function update(Request $request)
  {
    if ($request['id'] && $request['name'] && $request['text']) {
    $id = $request['id'];
    $name = $request['name'];
    $text = $request['text'];

    $post = Post::where('id', $id)->first();
    $post->name = $name;
    $post->text = $text;
    $post->save();

  }

  }

  public function delete(Request $request)
  {
      Post::where('id',$request->id)->delete();
  }
}

```


# 機能ごとにみてくと、、

### 過去記事の取得、表示（Read）

```index.php
  <div class="posts"></div>
```

```app.js
       function get(){
         return $.ajax({
           url: './get',
           type: 'GET'
         })
       }
           get().done(function(result) {
             // HTMLを初期化
             $(".posts").html("");

             //HTMLを生成
             $(result).each(function(){
             $(
             '<h6>'+ this.name + '</h6>'+
             '<span>' + this.text + '</span><br>' +
             '<form method="post" action="" accept-charset="utf-8" return false>' +
             '<div class="rapper-form col-sm-5 center-block"><input type="button" class="update-button btn btn-dark" value="編集"> ' +
             '<input type="button" id="delete-' + this.id + '" class="delete btn btn-danger" name="' + this.id + '" value="削除"><br><br>' +
             '<div class="update-form">' +
             '<label for="name">Name:</label>' +
             '<input type="text" class="update-name form-control" name="name" value="' + this.name +'"><br>' +
             '<label for="text">Text:</label>' +
             '<textarea class="update-text form-control" name="text">' + this.text + '</textarea><br>' +
             '<input type="button" id="update-' + this.id + '" class="update btn btn-dark" name="' + this.id + '" value="保存">' +
             '</div></form><br></div>'

           ).appendTo('.posts');
           });

           $('.update-form').hide();

            }).fail(function(result) {
                console.log(result);
            });

```

```web.php
$router->get('/', function () use ($router) {
    return view('index');
});
$router->get('/get', 'AppController@get');
```

```AppController.php
  public function get()
  {
    $result = Post::orderBy('id','desc')->get()->toArray();
    return $result;

  }
```


まずページを開いたらGETによるajax通信が実行され、コントローラーのgetメソッドが呼び出されます。
そのgetメソッドの中で過去記事データをDBから取得し、ビューに返します。
ビューでは返ってきたデータをjsでhtmlに生成し、表示します。
また、記事ごとに編集機能、削除機能をつけたかったのでそれ用のボタンも一緒に生成し、htmlの`<div class="posts"></div>`の中に表示しています。
編集ボタンを押すと出てくる編集フォームは一旦ページを出した瞬間は非表示にしたいので、hide()で隠してます。

### 新規投稿機能（Create）

```index.php
  <div class="form col-sm-5 center-block">
    <form id="form" method="post" accept-charset="utf-8" return false>
    <div class="form-group">
      <label for="name">Name:</label>
      <input type="text" id="name" class="form-control" name="name"><br>
    </div>
    <div class="form-group">
      <label for="text">Text:</label>
      <textarea id="text" class="form-control" name="text"></textarea>
    </div>
    </form>
    <input type="button" id="ajax" class="btn btn-dark" value="送信">
  </div>

```

```app.js
       $(function(){
           $('#ajax').on('click',function(){
               $.ajax({
                   url:'./post',
                   type:'POST',
                   data:{
                       'name':$('#name').val(),
                       'text':$('#text').val()
                   }
               })
               .done(function(){
                   location.reload();
               })
           });

```

```web.php
$router->post('/post', 'AppController@post');
```

```AppController.php
  public function post(Request $request)
  {
    if ($request['name'] && $request['text']) {
    $name = $request['name'];
    $text = $request['text'];

    $post = new Post();
    $post->name = $name;
    $post->text = $text;
    $post->save();

  }

  }

```

ビューで名前とテキストを入力し送信ボタンを押すと、POSTによるajax通信が実行され、コントローラーのpostメソッドが呼び出されビューで入力した内容が渡されます。
コントローラー側では、入力したデータをEloquentを利用しpostsテーブルに保存しています。

### 過去記事の編集（Update）

```index.php
  <div class="posts"></div>
```

```app.js
       function get(){
         return $.ajax({
           url: './get',
           type: 'GET'
         })
       }
           get().done(function(result) {
             // HTMLを初期化
             $(".posts").html("");

             //HTMLを生成
             $(result).each(function(){
             $(
             '<h6>'+ this.name + '</h6>'+
             '<span>' + this.text + '</span><br>' +
             '<form method="post" action="" accept-charset="utf-8" return false>' +
             '<div class="rapper-form col-sm-5 center-block"><input type="button" class="update-button btn btn-dark" value="編集"> ' +
             '<input type="button" id="delete-' + this.id + '" class="delete btn btn-danger" name="' + this.id + '" value="削除"><br><br>' +
             '<div class="update-form">' +
             '<label for="name">Name:</label>' +
             '<input type="text" class="update-name form-control" name="name" value="' + this.name +'"><br>' +
             '<label for="text">Text:</label>' +
             '<textarea class="update-text form-control" name="text">' + this.text + '</textarea><br>' +
             '<input type="button" id="update-' + this.id + '" class="update btn btn-dark" name="' + this.id + '" value="保存">' +
             '</div></form><br></div>'

           ).appendTo('.posts');
           });

           $('.update-form').hide();

            }).fail(function(result) {
                console.log(result);
            });
 
           $(document).on('click', '.update',function(){

               $.ajax({
                 url:'./update',
                 type:'POST',
                 data:{
                   'id':$(this).attr("name"),
                   'name':$(this).parent().find('.update-name').val(),
                   'text':$(this).parent().find('.update-text').val()
                 }
                   }).done(function(){
                       location.reload();
                   })
           });

           $(document).on('click', '.update-button',function(){
             $(this).parent().find('.update-form').addClass('open');
             $('.open').slideToggle(300);
             $(this).parent().find('.update-form').removeClass('open');
           });
```

```web.php
$router->post('/update', 'AppController@update');
```

```AppController.php
  public function update(Request $request)
  {
    if ($request['id'] && $request['name'] && $request['text']) {
    $id = $request['id'];
    $name = $request['name'];
    $text = $request['text'];

    $post = Post::where('id', $id)->first();
    $post->name = $name;
    $post->text = $text;
    $post->save();

  }

  }
```

ページを開いたときにだす過去記事の一つ一つに編集ボタンを表示させます。
slideToggle()を利用し、編集ボタンを押すと該当過去記事の編集フォームが出され、もう一度押すと戻る処理を実装しています。
編集フォームに編集したい内容を入力し保存ボタンを押すと、POSTによるajax通信が実行され、コントローラーのupdateメソッドが呼び出されビューで入力した内容が渡されます。
保存ボタンを押した該当記事のIDで記事を取得し、入力した内容でその記事をアップデートをします。


### 過去記事の削除（Delete）

```index.php
  <div class="posts"></div>
```

```app.js
       function get(){
         return $.ajax({
           url: './get',
           type: 'GET'
         })
       }
           get().done(function(result) {
             // HTMLを初期化
             $(".posts").html("");

             //HTMLを生成
             $(result).each(function(){
             $(
             '<h6>'+ this.name + '</h6>'+
             '<span>' + this.text + '</span><br>' +
             '<form method="post" action="" accept-charset="utf-8" return false>' +
             '<div class="rapper-form col-sm-5 center-block"><input type="button" class="update-button btn btn-dark" value="編集"> ' +
             '<input type="button" id="delete-' + this.id + '" class="delete btn btn-danger" name="' + this.id + '" value="削除"><br><br>' +
             '<div class="update-form">' +
             '<label for="name">Name:</label>' +
             '<input type="text" class="update-name form-control" name="name" value="' + this.name +'"><br>' +
             '<label for="text">Text:</label>' +
             '<textarea class="update-text form-control" name="text">' + this.text + '</textarea><br>' +
             '<input type="button" id="update-' + this.id + '" class="update btn btn-dark" name="' + this.id + '" value="保存">' +
             '</div></form><br></div>'

           ).appendTo('.posts');
           });

           $('.update-form').hide();

            }).fail(function(result) {
                console.log(result);
            });
 
           $(document).on('click', '.delete',function(){
             var deleteConfirm = confirm('削除してよろしいでしょうか？');

             if(deleteConfirm == true) {

              $.ajax({
                  url:'./delete',
                  type:'POST',
                  data:{
                      'id':$(this).attr("name")
                  }
              }).done(function(){
                location.reload();
              })
            } else {
              (function(e) {
                e.preventDefault()
              });
            }
            
          });

```

```web.php
$router->post('/delete', 'AppController@delete');
```

```AppController.php
  public function delete(Request $request)
  {
      Post::where('id',$request->id)->delete();
  }
```

ページを開いたときにだす過去記事の一つ一つに削除ボタンを表示させます。
削除ボタンを押すと、まず「削除してよろしいでしょうか？」の確認ダイアログが表示され、はいを押すとPOSTによるajax通信が実行され、コントローラーのdeleteメソッドが呼び出され削除ボタンを押した該当記事のIDが渡されます。
コントローラー側では、ビューから渡されたIDの記事をdeleteします。



# 作ってみた感想
普段laravelを使っていて、同期処理でコントローラーを呼び出す方法でしか実装した経験がなかったのですが
非同期でjs上でpostやdeleteといった処理を呼び出す方法があるということを実感できました。
しかも同期よりもサクサク動くからすごい！
ただ、htmlのクラスの命名とかレイアウトとかコーダーさんからみたら恐ろしいことになってるような気がする。。
jsも色々ツッコミどころありそうな。
フロントもっと勉強せねばなー


# 参考

https://sole-color-blog.com/blog/707/
https://qiita.com/mpyw/items/62e6e415f86eb30a5ff4
https://qiita.com/zakiyamaaaaa/items/bdda422db2ccbaea60d9
https://qiita.com/MasaKu/items/82b9743383e73cb62c3e
https://qiita.com/y_w_0308/items/d43e932cd6d96c09b69a