AdonisJSとReactを使ってイベントを管理するためのモダンなWebアプリを構築する

このチュートリアルでは、**React**とAdonis Node.jsフレームワークを使って、**Alibaba Cloud**上でREST APIのバックエンドとフロントエンドを構築します。

*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/how-to-build-a-modern-web-app-to-manage-events-with-adonisjs-and-react_594640)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

#序章
Adonisは、スケーラブルで安定した効率的なWebアプリケーションやREST APIを構築するために使用できるNode.jsフレームワークです。Reactを使用して、Adonisで構築されたREST APIを消費するためのモダンで堅牢なフロントエンドWebアプリケーションを構築します。

これから構築するWebアプリケーションは、MySQLのデータベースにイベントを格納し、これらのイベントを読み込んで削除します。

完成したら、以下のことができるようになります。

- Adonis.jsを使ったREST APIの構築
- Reactを使ってREST APIを消費する堅牢なクライアントサイドアプリケーションを構築する
- Bootstrap 4とReactを統合する


#前提条件
このガイドを始める前に、以下のものが必要です。

- Ubuntu 18.04を搭載した[Elastic Compute Service](https://www.alibabacloud.com/ja/product/ecs) (ECS)開発マシン。
- Node.js 8.0.0.0+とNpm 3.0.0.0+がマシンにインストールされている。
- ローカルにインストールされたMySQL 5.7+。


#ステップ1 - Adonis APIプロジェクトの作成と依存関係のインストール

このステップでは、Adonisプロジェクトを足場にするのに役立つAdonis CLIをインストールし、アプリケーションのデータベース接続をセットアップするために必要な依存関係をインストールします。

ターミナルを開き、以下のコマンドを実行してAdonis.js CLIをグローバルにインストールします。

```
npm i -g @adonisjs/cli
```

これでインストールが完了したら、ターミナルのどこからでも`adonis`を実行できるようになるはずです。以下のコマンドを実行して、インストールしたCLIのバージョンを確認することで試すことができます。

```
adonis --version
```

次に、新しいAdonisアプリケーションを足場にする必要があります。任意のディレクトリに移動し、以下のコマンドを実行します。

```
adonis new adonis-events --api-only
```

新しいコマンドは `adonis-events` という新しいプロジェクトを足場にし、`--api-only` は生成されたプロジェクトが特に REST API 用であることを指定します。新しいコマンドは、新しいプロジェクトに必要なすべての npm 依存関係をインストールします。

生成されるプロジェクトは以下のような構造になっているはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/b96fe99c-f62f-5ed2-3a55-826d9ab7bb57.png)

次に、データベース接続を設定する必要があります。Adonis は多くの SQL データベースをサポートしていますが、使用する特定のデータベース、この場合は MySQL 用のパッケージをインストールする必要があります。プロジェクトフォルダに移動し、以下のコマンドを実行します。

```
npm i --save mysql
```

このプロジェクトでは、`.env`というファイルにアプリケーションを設定するための環境変数が含まれています。データベース接続が `mysql` に設定されていることを確認してください。

```
[~/adonis-events/.env]
...
DB_CONNECTION=mysql
...
```

最後に、このプロジェクトで使用する MySQL データベースをローカルに作成する必要があります。ローカルにMySQLがインストールされている場合は、以下のコマンドを実行してデータベースを作成します。

```
mysql -u root
```

```
mysql> CREATE DATABASE events;
```

新しいデータベースを作成したら、`.env` ファイルの資格情報を作成したデータベースと一致するように更新します。

```
[~/adonis-events/.env]
...
DB_CONNECTION=mysql
DB_USERNAME=root
DB_DATABASE=events
DB_PASSWORD=
...
```

これが完了したら、REST APIとReactフロントエンドの作成に進みます。

#ステップ2 - Reactフロントエンドを作成する
このステップでは、Reactを使ってアプリケーションのフロントエンドを作成します。webpack の設定や本番のビルドプロセスを気にすることなく、React アプリケーションを素早く足場にするために、`crease-react-app` という人気のあるパッケージを使用します。ローカルに`create-react-app`がインストールされていない場合は、以下のコマンドを実行してください。

```
npm i -g create-react-app
```

インストールしたら、新しいフォルダに移動し、以下のコマンドを実行して新しいReactプロジェクトを作成します。

```
create-react-app adonis-events-react
```

新たに生成されたReactプロジェクトの構造は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/9c767970-5059-869f-0110-509fdf2e571e.png)

新しく作成したReactプロジェクトを起動するには、Reactプロジェクトディレクトリに移動し、以下のコマンドを実行します。

```
npm start
```

これにより開発サーバーが起動し、デフォルトのブラウザで以下のようなReactアプリケーションが開くはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/6dd09116-9673-4ee7-962a-1b033e76b2ee.png)

最後に、基本的なスタイリングを行うためのBootstrap CSSフレームワークをインストールしてみましょう。

Reactプロジェクト内の`src`フォルダに入り、`index.css`ファイルを開きます。このファイルの内容を以下のように置き換えます。

```
[~/adonis-events-react/src/index.css]
@import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
```

上記は、ブートストラップCSSファイルへのリンクをインポートしたもので、フロントエンドプロジェクトのスタイルを素早く設定するために使用できるすべてのスタイルとクラスが含まれています。

フロントエンドアプリケーションのセットアップが完了したので、データベースモデルと移行を開発しましょう。

#ステップ3 - イベントの移行とモデルの作成
このステップでは、データベーステーブルをどのように構造化するかを決定する移行をセットアップし、このデータベーステーブルとの対話に役立つモデルをセットアップします。

まず、ターミナルでAdonisアプリケーションに移動し、次のコマンドを実行します。

```
adonis make:model Event -m
```

このコマンドは、モデルとそれに対応するマイグレーションを素早く作成する方法です。以下のような出力が得られます。

```
 create  app/Models/Event.js
 create  database/migrations/1541120387036_event_schema.js
```

作成したファイルは2つで、`app/Models/Event.js`はこんな感じです。

```
'use strict'

const Model = use('Model')

class Event extends Model {
}

module.exports = Event
```

これは基本的なES6のクラスで、ベースとなる`Model`クラスを継承しています。見ての通り、このクラスには多くの静的メソッドがあり、データベースと簡単にやりとりするのに役立ちます。

2つ目のファイルは、`database/migrations/1541120387036_event_schema.js`で、以下のようになっています。

```
'use strict'

const Schema = use('Schema')

class EventSchema extends Schema {
  up () {
    this.create('events', (table) => {
      table.increments()
      table.timestamps()
    })
  }

  down () {
    this.drop('events')
  }
}

module.exports = EventSchema
```

Adonisのマイグレーションは、ベースの`Schema`クラスを拡張したES6クラスで、2つの重要なメソッドを含んでいます：`up`は、データベーステーブルの作成や変更を含むデータベースへの変更を行うために使用され、`down`はそれらの変更を反転させるために使用されます。`up`関数では、データベーステーブルの構造を記述するAdonisが提供するメソッドを呼び出します。

現時点では、`up`関数の中の関数this.create('events')がデータベース内にイベントテーブルを作成します。また、`down`関数の`this.drop('events’)`は、必要に応じてデータベース内のイベントテーブルを削除します。

イベントテーブルには以下のフィールドがあります。

- title - イベントのタイトル
- start_date - イベントの開始日．
- end_date - イベントの終了日．
- location - 偶数の場所
- price - イベントの費用

マイグレーションでは、以下のようにこれらのフィールドを定義します。

次に、このマイグレーションファイルを実行して、Adonisが接続されたデータベースにこれらの変更を影響させることができるようにする必要があります。

プロジェクトフォルダにあることを確認し、以下のコマンドを実行してください。

```
adonis migration:run
```

以下のような出力が表示されるはずです。

```
migrate: 1503250034279_user.js
migrate: 1503250034280_token.js
migrate: 1541120387036_event_schema.js
Database migrated successfully in 206 ms
```

このコマンドを実行すると、Adonisは各マイグレーションファイルを読み込み、これらのファイル内で`up`機能を実行します。したがって、現時点では、私たちのデータベースには、`users`、`tokens`、`events`の3つのテーブルが新たに作成されています。最初の2つのテーブルは、Adonisの新規インストール時にデフォルトで出荷されます。

データベースのセットアップが完了したら、REST APIの作成に進みます。

#ステップ4 - REST APIの作成
このステップでは、REST APIを作成します。イベントに対するさまざまなアクションを処理するために、いくつかの API エンドポイントを作成します。作成するエンドポイントは以下の通りです。

- `[POST] /api/events` は、イベントを作成
- `[DELETE] /api/events/:id`は、 特定のイベントを削除
- `[GET] /api/events`は、すべてのイベントのリストを取得

####[POST]/api/eventsエンドポイントの実装
まずは、これらのエンドポイントを処理するためのコントローラを生成することから始めましょう。コントローラは基本的に ES6 クラスで、すべてのビジネスロジックを処理するメソッドを含んでいます。以下のコマンドを実行してください。

```
adonis make:controller EventController
```

以下のような出力が表示されるはずです。

```
> Select controller type For HTTP requests
 create  app/Controllers/Http/EventController.js
```

次に、この特定のエンドポイントを扱うルートを登録する必要があります。`start/routes.js`ファイルを開き、以下を追加します。

`Route`クラスには`post`という静的関数があり、このメソッドはpostエンドポイントを登録します。最初のパラメータはパス、`api/events`、2番目のパラメータはこのエンドポイントへのリクエストを処理するコントローラとメソッドです。この場合、`EventController` の `store` メソッドです。つまり、このメソッドを作成する必要があります。`EventController`のように追加します。

Adonis は `context` と呼ばれるオブジェクトを注入しますが、この `context` には `request` オブジェクトと `response` オブジェクトを含む、requestに関する多くの関数とオブジェクトが含まれており、`request`オブジェクトにはrequestに関するすべての情報が含まれています。現時点での`store`メソッドでは、`request.all()`関数を呼び出すと、POSTリクエストで送信された全てのデータを返してくれます。今回は、イベントを作成するために、リクエストデータに`title、start_date、end_date、location、price`が提供されていることを想定しています。

次に、このデータをデータベースに保存しなければなりません。データベース、特に`events`テーブルと通信するために、モデルを使用します。`events`モデルを使用すると、Adonisは自動的に`events`テーブルに接続します。

`events`モデルを使用して、`create`関数を呼び出しました。これは、POSTリクエストから取得したデータを使用して、自動的に新しい行をeventsデータベーステーブルに保存します。この関数は新しく作成されたイベントを返し、このeventsと一緒にJSONレスポンスを返します。

**注**: Adonisの`use`関数はNode.jsの`require`関数のラッパーです。これは名前空間を使用し、上で行ったようにファイルを簡単にrequireするのに役立ちます。ですから、`require('../.../.../Models/Event.js’)`の代わりに、単に `use('App/Models/Event')` と書きます。

先ほど実装した/POSTエンドポイントを試してみましょう。そのためには、Adonis APIを実行する必要があります。

以下のコマンドでadonisサーバーを実行します。

```
adonis serve --dev
```

```
info: serving app on http://127.0.0.1:3333
```

新しく作成したPOST `/api/events`エンドポイントを試すために、任意のRESTクライアントを使用することができます。今回はPOSTMANを使ってみます。

操作結果は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/18fcc057-d86b-bc17-1d5b-180e565fad08.png)

**注**：POSTMANでは`Content-type`を`application/json`に設定することを忘れないでください。

####[取得] api/eventsエンドポイントの実装
このエンドポイントのルートを追加してみましょう。`routes.js`ファイルで、次のように追加します。

```
[~/adonis-events/start/routes.js]

Route.get(api/events', 'EventController.index')
```
これはエンドポイントを `EventController` の `index`メソッドにマッピングします。このコントローラでは、次のように追加してみましょう。

`index`メソッドは、`events`モデルの`all()`関数を使用して`events`テーブルからデータベースのすべての行を取得します。これをPOSTMANで試してみると、以下のような結果が得られるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/9199f6fb-46dd-9611-7da4-d37265278b9a.png)

####[削除] api/events/:id エンドポイントの実装
最後に実装しなければならないエンドポイントは、イベントを削除するためのものです。このエンドポイントはこれまでの他のエンドポイントとは少し異なり、*動的パラメータ*を使用します。このエンドポイントのルートを登録してみましょう。

```
[~/adonis-events/start/routes.js]

Route.delete(api/events/:id', 'EventController.delete')
```

ルートの `:id` は動的パラメータで、削除したいイベントの特定の `id` を渡すことができます。このルートを `EventController` の `delete` メソッドにマッチさせます。

`delete` メソッドでは、ルートから取得した動的 `ID` を使用してイベントを検出し、検出されたイベントに対して `delete` 関数を呼び出します。以下に POSTMAN でのテストの様子を示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/e18dde95-60f0-530d-cc44-d6f0290b0ba2.png)

それだけです。Adonisで完全に機能するAPIを作るのはこんなに簡単なことなのです。フロントエンドに切り替える前にもう一つ。現時点では、Adonis APIは他のサーバーやドメインからのリクエストを許可しません。これはCORS保護と呼ばれるもので、不正なリクエストがAPIに行われないようにするために本当に重要です。この特定のケースでは、ReactフロントエンドがAPIリクエストを行うことを許可するようにAdonisに指示しなければなりません。これを行うには、Adonisには設定ファイルが既に設定されているので、アプリケーションのCORS設定を変更することができます。`config/cors.js`に移動して、このファイルをこのように修正します。

```
[~/adonis-events/config/cors.js]

  origin: ['http://localhost:3000'],
```

ここでは、すべての認可されたオリジンの配列を作成し、この配列にリストされたすべてのドメインがAdonisアプリケーションにAPIリクエストを行うことができるようになります。クライアント側のReact開発サーバーのドメインは `http://localhost:300` なので、これを`origin`配列に追加しました。

次のステップでは、Reactフロントエンドに切り替えて、堅牢なクライアントサイドアプリケーションを構築するために、このデータの消費を開始しましょう。

#ステップ5 - クライアントサイドコンポーネントを設定する
最初に設定するコンポーネントは、`App` コンポーネントです。これは、最初にアプリケーションにアクセスしたときに表示されるコンポーネントです。他の不要なファイルもすべてクリーンアップします。

不要なファイルを削除した後の現在のフォルダ構造は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/5fa354a6-7961-2219-26ce-d2111921be2e.png)

次に、単一のイベントを表現するために使用するコンポーネントを作成してみましょう。プロジェクトフォルダに移動し、`src`フォルダ内に`Event.js`というファイルを作成します。

このコンポーネントは、現時点での静的データを表示し、単一のイベントを表示するためのテンプレートを表しています。

最後に、`AddEvent.js`という別のファイルを作成します。これは、新しいイベントを追加するためのフォームとして機能します。

必要なコンポーネントの作成が完了したので、APIサービスを作成してみましょう。

#ステップ6 - Axios APIサービスの作成
Adonis API と通信できるようにするために、シンプルな HTTP クライアントを使用します。このクライアントをインストールするには、Reactプロジェクトに移動し、次のコマンドを実行します。

```
npm install axios --save
```

これをインストールしたら、`src` フォルダに `apiService.js` というファイルを作成し、以下のクラスを追加します。

このクラスは axios のシンプルなラッパーであり、Adonis API への HTTP リクエストを行うためのヘルパー関数を提供します。コンストラクタでは、すべてのリクエストの`BaseURL`をAdonisアプリケーションのURLに設定します。

サービスの準備ができたので、これらのコンポーネントを機能させてみましょう。

#ステップ7 - イベントの作成
イベントを作成するために、`Create Event`ボタンを用意します。ユーザーがこのボタンをクリックすると、イベントを作成するためのフォームを持つコンポーネントが表示されます。Appコンポーネントを以下のように変更してみましょう。

現時点では以下のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/e18d82d9-fa3c-6cb6-c32b-dd35624fa067.png)

まず、`AddEvent`コンポーネントをインポートしました。`showCreateEventForm`というステート・プロパティを設定し、レンダー関数の中で、このプロパティが真であれば`AddEvent`コンポーネントをマウントし、そうでなければマウントしません。`Create Event`ボタンがクリックされると、このステート・プロパティの値を切り替えます。また、`showCreateEventForm` が `true` の場合は `Create Event` ボタンに `btn-danger` クラスを動的に追加し、このプロパティが `false` の場合は `btn-info` を追加します。

次のステップは、このフォームを実際に動作させて、ユーザーがイベントの詳細を入力して保存できるようにすることです。

####イベントの作成機能の実装
最初のステップは、ユーザーが入力した入力を取得することです。これを管理するためにステートを使います。ここに`AddEvent.js`コンポーネントのアップデート版があります。

この時点では、入力フィールドが変更されるたびに、その値は自動的にこのコンポーネントの状態に設定されます。

現時点では、デフォルトのブラウザの日付ピッカーを使用しており、これには時間ピッカーが統合されていません。さらに、この日付ピッカーの動作はブラウザによって非常に一貫性がありません。そこで、`start_date`と`end_date`のフィールドには、`FlatPickr`と呼ばれるサードパーティ製の日付ピッカーを使用します。この日付ピッカーをインストールするには、以下のコマンドを実行します。

```
npm i --save react-flatpickr
```

インストールできたら、このようにフォームを修正してみましょう。

まず、インストールした`FlatPickr`コンポーネントをインポートします。前回の日付入力の代わりに、`FlatPickr`をマウントし、`handleDateChange`と呼ばれる別のハンドラを渡します。これはほとんど同じことを行い、`start_date`と`end_date`の値が変更されたときの状態に設定します。

さて、このイベントを保存するためのAPIリクエストを実際に行う時が来ました。これを行うために、Appコンポーネントから関数を渡し、呼び出されたときにイベントを保存します。Appコンポーネントをこのように修正してみましょう。

コンポーネントをこのように更新してみましょう。

まず、`ApiService`をインポートし、Appコンポーネントのコンストラクタで新しいインスタンスを作成します。次に、apiServiceからの関数である`storeEvent`という`AddEvent`コンポーネントにプロップを渡します。

次に、`AddEvent`コンポーネントの`Create Event`ボタンが作成された際に、このプロップを呼び出す必要があります。

`Create Event`ボタンがクリックされると、`this.props.storeEvent()`を呼び出し、状態をパラメータとして渡します。この関数は axios を呼び出し、イベントを保存するための HTTP リクエストを行います。開発者ツールで発信HTTPリクエストを確認すると、AdonisサーバーからのHTTPレスポンスが表示されているはずです。

すべてのイベントのリストが表示されるまでは、作成されたことを確信することはできませんよね？次のステップでは、Adonis APIからすべてのイベントのリストをフェッチして表示します。

####すべてのイベントをフェッチして表示する
すべてのイベントを取得するために、コンポーネントがマウントされた直後に `[GET]` リクエストを行い、結果を取得したら、その結果をステートに設定してコンポーネントに表示します。これを実現するために、`componentDidMount`というReactのライフサイクルフックを追加してみましょう。この関数は、コンポーネントがマウントされるとすぐに実行されます。

まず、イベントという新しいステートプロパティを空の配列に設定します。次に、`componentDidMount`関数の中で、APIリクエストをしてすべてのイベントを取得し、レスポンスが返ってきたら、その結果をstateに設定します。この時点でReact Dev toolsなどのツールを使ってコンポーネントの状態を検査すると、すべてのイベントのリストが表示されているはずです。

次のステップは、このイベントのリストをコンポーネントに表示することです。このために作成した`Event`コンポーネントを使用します。`App` コンポーネントをこのように変更してみましょう。

各イベントをマッピングして、1つのイベントを所定の場所に表示します。現時点では、アプリケーションがマウントされると、対応するイベントのリストが表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/80cc1690-d68b-5c82-1050-c83bff48405f.png)

イベント コンポーネントはまだ動的ではありません。これは、特定のイベント（Appコンポーネントから渡された）のためのプロップを受け取り、代わりにそれらを表示する必要があります。この動作をサポートするように変更してみましょう。

これで、各イベントは`event`プロップを介して渡されたカスタムデータを表示するようになりました。

現時点では、イベントを作成しても何も起こりません。より良いエクスペリエンスを得るために、App コンポーネントを修正して、`AddEvent` フォームを非表示にし、すべてのイベントを再度取得して、ユーザーが新しく作成されたイベントを見ることができるようにします。

最初に、`fetchEvents`という新しい関数を作成します。次に、storeEventという新しい関数を作成します。この関数はAPIを呼び出して新しいイベントを保存し、レスポンスを取得したら`fetchEvents`を呼び出してすべてのイベントを取得し、`toggleShowCreateEventForm`を呼び出してcreateイベントフォームを非表示にします。

#ステップ8 - Deleteイベントの実装
最後のタスクは、イベントを削除できるようにすることです。App コンポーネント上でイベントを削除する関数を作成し、この関数を`Event` コンポーネントのプロップとして渡し、ユーザーが削除ボタンをクリックするたびにこの関数が呼び出されます。Appコンポーネントは次のようになります。

新しく追加された関数`deleteEvent`はAPIサービスを呼び出し、イベントが削除されると、再び`fetchEvents`を呼び出して、すべてのイベントの新鮮なコピーを取得します。最後に、`Event`コンポーネントで、`id`パラメータを指定してこの関数を呼び出します。このように`Event`コンポーネントを修正します。

すべての変更を行った後のプロジェクトの動作は次のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/9abbe482-8801-f4c3-ea56-8554d9fef439.png)

このアプリケーションが完成したことで、実際のプロジェクトの参考として使用できる最新のフルスタックJavascriptアプリケーションの基礎を手に入れることができます。

#結論
このチュートリアルでは、Adonis.jsとReact.jsを使ってフルスタックのJavascriptデモアプリケーションを作成しました。REST APIの構築にはAdonis.js、APIの消費にはReact、アプリケーションのスタイリングにはBootstrap 4を使用しました。Adonis REST APIのソースコードは[こちら](https://github.com/bahdcoder/adonis-events?spm=a2c65.11461447.0.0.11dd16e7DDBJPF)のGitHubリポジトリに、Reactフロントエンドアプリケーションのソースコードは[こちら](https://github.com/bahdcoder/adonis-events-react?spm=a2c65.11461447.0.0.11dd16e7DDBJPF)のGitHubリポジトリにあります。

Adonisフレームワークについてもっと詳しく知りたい場合は、[公式ドキュメント](https://adonisjs.com/?spm=a2c65.11461447.0.0.11dd16e7DDBJPF)がとても便利で、ビデオチュートリアルも提供されています。


*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*