【Laravel】購入処理APIを実装しPostmanで確認する

# はじめに

前回に商品管理システムを実装したため、それに合わせた購入処理APIを実装します。コードや掲載している知識についてはchatGPT4に確認しながら記述しています

## 前回作成した商品管理システムについて

https://qiita.com/EasyCoder/items/3e9c99bf96df7f1788eb

## 今回の目的

前回作成した商品管理システムは自動販売機と連携することを目的としています

## APIとはどのような意味か

`Application Programming Interface`の略称です。APIはシステム間のコミュニケーションの窓口の役割を果たします。

自動販売機（外部）から通信が送られることで決まった動作を行い、自動販売機に対して販売して良いかどうかなどの結果を返します

## 具体的な例

自動販売機は様々な場所に設置されている便利な機械ですが、それとは別に遠く離れた場所のPC（サーバー）に「商品管理アプリ」というものを設置するとします。これは店のオーナーが使うものでどの商品がどれだけ売れているか、どの商品の在庫が少なくなっているかなどを確認できるシステムです。

この2つ（自動販売機と商品管理アプリ）が、どうやって情報をやり取りするかというと、商品管理アプリ側（Laravel）にこれから作成する「API」という窓口を使います。

例えば自動販売機で商品が1つ売れたら「今、この商品が1つ売れましたよ」という情報をAPI（窓口）を使って商品管理アプリに伝えます。すると商品管理アプリの方で「あ、この商品が1つ売れたんだな」とデータベースの情報が更新されます。

この「情報を伝える」という窓口動作がAPIの役割です。遠く離れた2つのシステムがお互いに情報を交換するための「窓口」のようなものと考えていただければと思います。



### APIをレストランのオーダーに例えると？
お客がちょっと高級なレストランに入って料理を注文するとき、直接キッチンに行って料理を頼むわけではないと思います。代わりにウェイターに注文を伝えてウェイターがキッチンにそれを伝えて料理を運んできます。

この場面での「ウェイター」がAPIの役割に似ています。

- お客（クライアント）：料理を注文する側
- ウェイター（API）：注文を受け取り、キッチンに伝え、料理を持ってくる`橋渡しの役割`
- キッチン（サーバーやデータベース）：料理（データ）を準備する場所

### コンピュータの世界でのAPIは？
サーバー側のソフトウェアやアプリケーションが、外部のソフトウェアやサービスと「会話」するための手段や「ルール」を提供します。

例えば天気のアプリがあるとして、そのアプリが最新の天気情報を表示するには、天気情報を提供しているサービスからデータを取得する必要があります。このデータの取得の際に、天気のアプリがそのサービスと通信するための「手段やルール」がAPIです。

要するに、APIは異なるソフトウェアやサービス間で情報をやり取りするための「通信手段」や「約束事」を定義したものです。

### 自動販売機と商品管理アプリを繋ぐAPIとは？

#### 背景
ある都市には多くの自動販売機が配置されています。すべての自動販売機は中央のサーバー上の商品管理アプリと連携しています。この商品管理アプリは、各自動販売機の在庫や売り上げを管理しています。

#### APIの役割
ある商品が自動販売機で買われた場合、その情報を商品管理アプリに伝える必要があります。このときに使われる「通信の窓口」がAPIです。

#### 具体的な動作要件
1. 顧客が自動販売機で商品を選び、購入ボタンを押す。
1. 自動販売機は、この購入情報（どの商品が、いくつ購入されたか等）を商品管理アプリに送る
1. この情報の送信方法、形式などはサーバーのAPIで定められている。
例: 「商品IDと数量をJSON形式で送ってね」というルール（後述）
1. 自動販売機はサーバー側（API）のルールに従って情報を商品管理アプリに送る。
1. 商品管理アプリは受け取った情報をもとに、在庫や売り上げを更新する。
1. 商品管理アプリから自動販売機に「情報を受け取ったよ、ありがとう」というメッセージや
「商品を売買しても良い信号」などの返答（レスポンス）が返ってくる。

#### APIのメリットについて

1. 複数の自動販売機で動作しても、統一された方法で商品の在庫や売り上げを管理することができる。
1. 新しい自動販売機を導入した場合も、同じAPIのルールに従えば商品管理アプリと簡単に連携できる。
1. 万が一自動販売機が故障しても、他の自動販売機や商品管理アプリには影響を及ぼさない。


## APIのルールについて
APIの「ルール」や「仕様」というのは、具体的に「どんな情報を送ればいいのか」「どのような形式で送ればいいのか」など、ソフトウェアが無事に通信するための取り決めのことを指し、Laravelの場合は`controller`と`routing`に記述します。

### APIルールの具体例
ある自動販売機のシステム（A）が商品管理のシステム（B）に「今、コーラを1本販売しました」と伝える場合のルールを考えます。
- 通信先: AからBのデータ送信先（URL)を「/sale」とする。
- 自動販売機からどんな情報を送らせるか: 「商品ID」と「販売数量」
- どのような形式で送るか: 「商品IDは数字」「販売数量も数字」とする。

このような取り決めをサーバー側（B）、つまりLaravelのControllerとRootingに記述し、それを元に自動販売機側（A）が実際に通信を行います。この取り決めのことを「APIのルール」や「APIの仕様」と言います。

## 実装する購入処理機能の仕様

今回のAPIの仕様については次のとおりです

1. salesテーブルにレコードを追加する
1. productsテーブルの在庫数を減算する
1. 在庫が0の場合、エラーで購入できない結果を返す

### 実装するAPIの詳細について

![スクリーンショット 2023-08-09 13.36.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2765288/1052527a-de89-d567-b913-9762855dbaae.png)

今回のWEBアプリには以下3つのテーブルを設置されています
- sales(売上管理テーブル）
- products(商品管理テーブル）
- companies(会社名管理テーブル）

画像で並んでいる商品情報は、products（商品）とcompanies（会社名）というそれぞれのテーブルにある情報を元に表示させています。

自動販売機から商品の購入リクエストがAPIに届いた場合はsalesに売上の日時や商品名などを記録し、productsテーブルの在庫を減算します。また在庫が0の商品を購入しようとした場合、エラーとなり購入できないことように設定します

## APIの作成手順

1. APIへのルーティングを設定
（ルーティングに記述されるアドレスはエンドポイントと呼びます）
1. 既存もしくは新規コントローラーにAPIのメソッドを追加
（商品の取得、保存、更新、削除、などの処理が含まれます）
1. リクエストの送信元（自動販売機）に対して答えを返します
（在庫情報など必要な情報のみに整形し結果を返します）
（在庫がなく購入できない場合はエラーを返します）


## ルーティングを作成する

ルーティングは以下の`api.php`に記述することになります

```java
project-root-directory (プロジェクトの最も外側のフォルダ)
│
├── app (ここには主要なプログラムの部品が入っています)
│
├── ... (その他のたくさんのフォルダ)
│
├── routes (このフォルダにはアクセスのルールが書かれたファイルが入っています)
│   ├── web.php (通常のウェブページのルールが書かれています)
│   └── api.php (APIという特別なページのルールが書かれています)

```

### 【補足】web.phpとapi.phpの違い
Laravelはそれぞれのディレクトリやファイルに役割があり、ルーティングに書く`エンドポイント`はそれぞれ決まったルールが存在します。

エンドポイントとはWEBサイトを表示させたり、APIを実行するためのURLのことです
- web.php
通常のウェブページへのエンドポイントを書きますweb.phpに書かれているルールで、私たちが普段ブラウザで見るページにアクセスするときに使われます。

- api.php
「他のシステム」から、データを取得・送信するための特定のURL（エンドポイント）を指します。

もし、APIのルートをweb.phpに書いてしまうと、いくつかの不具合や混乱が起こる可能性があります。なぜなら、web.phpは普通のウェブページを表示するための設定が含まれているからです。

## api.phpに記述するコード

Laravel 8.x 以降では次のように書くことが推奨されています
```php:テンプレート
use App\Http\Controllers\モデル名Controller;

Route::post('/任意のURL', [モデル名Controller::class, 'メソッド名']);
```
今回は購入処理のため自動販売機側から`商品IDや数量`、場合によってはユーザー情報などを送る都合上、秘匿性の高いpost通信を用いています。

salesテーブルを操作するコントローラーに、購入（purchase）に関するメソッドを追加するため実装するコードは次のようなものになりました

```api.php
use App\Http\Controllers\SalesController; //追記する

Route::post('/purchase', 'SalesController@purchase'); //追記する
```



## コントローラーを作成する

次のコマンドでSalesControllerを作成します

```bash
php artisan make:controller SalesController
```
```php:作成された場所
project-root-directory
│
├── app
│   │
│   ├── Http
│   │   │
│   │   ├── Controllers
│   │   │   │
│   │   │   ├── SalesController.php
```

## SalesControllerに記述するコード

```SalesController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Product; // Productモデルを使用
use App\Models\Sale; // Saleモデルを使用

class SalesController extends Controller
{
    public function purchase(Request $request)
{
    // リクエストから必要なデータを取得する
    $productId = $request->input('product_id'); // "product_id":7が送られた場合は7が代入される
    $quantity = $request->input('quantity', 1); // 購入する数を代入する もしも”quantity”というデータが送られていない場合は1を代入する

    // データベースから対象の商品を検索・取得
    $product = Product::find($productId); // "product_id":7 送られてきた場合 Product::find(7)の情報が代入される

    // 商品が存在しない、または在庫が不足している場合のバリデーションを行う
    if (!$product) {
        return response()->json(['message' => '商品が存在しません'], 404);
    }
    if ($product->stock < $quantity) {
        return response()->json(['message' => '商品が在庫不足です'], 400);
    }

    // 在庫を減少させる
    $product->stock -= $quantity; // $quantityは購入数を指し、デフォルトで1が指定されている
    $product->save();


    // Salesテーブルに商品IDと購入日時を記録する
    $sale = new Sale([
        'product_id' => $productId,
        // 主キーであるIDと、created_at , updated_atは自動入力されるため不要
    ]);

    $sale->save();

    // レスポンスを返す
    return response()->json(['message' => '購入成功']);
}

}



```

上記のコードではSalesテーブルに挿入するフィールドの指定部分に`配列`を使われています。配列を使えば一度に複数のフィールドを埋めることができ可読性とセキュリティが高まりますが、Laravelで使用するためにはModel側に`$fillable（フィラブル）属性`の設定が必要になります

```Sale.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Sale extends Model
{
    use HasFactory;

    protected $fillable = ['product_id', //ここに配列で追加、編集するフィールドを入力する
                        // 'フィールド2',
                        // 'フィールド3',
                          ]; //　$fillable属性を追記

    public function product()
    {
        return $this->belongsTo(Product::class);
    }


}

```
### 【補足】$fillable（フィラブル）属性について
次のようにフィールドを単体で挿入する場合はモデルに`$fillable（フィラブル）属性`の設定は不要です

```php
$sale = new Sale;
$sale->product_id = $productId;
$sale->save();
```


## APIテストのためのツール: Postman

コードは上記で完成しておりますが、正しく動作するかテストをするために自動販売機を用意するのは難しいので、代わりにサーバーへ通信を送信するためのツールを用意します

Postman(ポストマン）は、「Postman, Inc.」という企業が開発したツールで、API（アプリケーション間の通信窓口）をテストや開発するための無料でも使えるツールです。使いやすい画面で、APIにデータを送ったり、データの返答を確認することができます。また、異なる設定やテスト条件を保存して、繰り返し利用することも可能です。APIの動作を確かめたり、エラーを探る際に役立ちます。初心者でも手軽にAPIのテストを始めることができます。

有料版では大規模なチームでの協同作業のサポート、モニタリングや高度なセキュリティ機能などの高度な機能が含まれておりますが、個人利用や小規模な開発チーム、基本的なAPIテスティングのニーズには、`Postman Free`という無料プランで十分に対応可能なツールとなっております

## Postmanのダウンロード

以下がPostmanの公式ダウンロードページになります
Postmanは現段階では日本語をサポートしていないため英語のUI（ユーザーインターフェース）しか使うことができません。

https://www.postman.com/downloads/

同じく無料のフリープラン制がある`Apidog`という他のツールは日本語版をサポートしておりますので都合に合わせて使用するツールを選択することができます

https://apidog.com/jp/compare/apidog-vs-postman/?utm_source=google_search&utm_medium=g&utm_campaign=20125284217&utm_content=152020087187&utm_term=postman&gad=1&gclid=CjwKCAjw_uGmBhBREiwAeOfsdxmLI_4qXZJCrtBGdps_gmHKOzZrZZkVRVI1esM92lkAyiR21C0p3hoCPwgQAvD_BwE

以下はPostmanダウンロードの画面です。`Windows`,`Mac`,`Linux`それぞれに対応しています。PCのスペックに合わせて必要なボタンを押して下さい

![スクリーンショット 2023-08-14 12.31.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2765288/dcc3435b-406e-2da8-f41a-2d0b30672a8d.png)

## ダウンロードしたPostmanを起動する

起動すると次のようにサインインが求められます
Postmanには一時的にリクエストやデータを内部に保存する機能がありましたが、今後はログインしないと保存機能などが使用できなくなるため、保存機能などを使用するためにはログインが必要であることを示しています

![スクリーンショット 2023-08-14 12.36.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2765288/c22ee447-3b14-0c29-245b-de11ec342378.png)

現段階では右上のログインしなくてもでは送信テストは可能ですが、今後ログインしなければテストに使用できない可能性があり、コードの保存機能なども使用できなくなるため必要であればログインして作業を実行して下さい

## Jsonについて
Postmanを扱うためにはJson（ジェイソン）を知る必要があります
Jsonは次のようなテキストベースのデータ構造を持ったデータを指します

```json
{
    "product_id":"11"
}
```

仮にこのJSONデータを次のURL（エンドポイント）に送信すると、`SalesController`の`purchaseメソッド`が実行されproductsテーブルの`商品IDが11`の在庫が1個減ります


```
http://localhost:8000/api/purchase
```

```(参照)　api.php
use App\Http\Controllers\SalesController; //追記する

Route::post('/purchase', 'SalesController@purchase'); //追記する
```

### JSONとは

JSON（JavaScript Object Notation）は、データ構造を表現するための軽量なテキストベースのフォーマットです。主にデータの交換のために使用されます。JSONは、人間にとって読み書きしやすく、マシンにとっても簡単に解析・生成できる特徴を持っています。

コンピュータ同士が情報をやりとりするときの「約束事」の一つです。人が話すときに日本語や英語などの言語を使うように、コンピュータが情報を伝えるときにJSONという形式を使います。具体的には、情報を「名前」と「値」の組み合わせで書きます。例えば、ある人の名前と年齢をJSONで書くと、こんな感じです

```json
{
    "名前": "taro",
    "年齢": 25
}
```

### APIへのデータ送信はほぼJSON

APIへデータを送信する場合はほぼJSONが使われており、LaravelのAPIでデータを受け取る場合はJSONがデフォルトに設定されています（他の形式でもデータの受け取りはできます）

### 今回必要なデータは？

今回、コントローラーのメソッドに記述したコードは次のようになっています

```SalesController.php
$productId = $request->input('product_id');// "product_id":"7"が送られた場合は7が代入される
$quantity = $request->input('quantity', 1); // 購入する数を代入する もしも”quantity”というデータが送られていない場合は1を代入する
```
```
$productIdに関しては購入する商品のIDを指していますので必ずデータが必要になってきます
$quantityに関してはデータが送信されていなければデフォルトで1を代入するようになっているため省略ができます
```
よって自動販売機（今回はPostman）から送信してほしいJSONデータは次のようになります

### 必要なデータの形式

```送信したいデータ.json
{
    "product_id":"11", //商品のID
    "quantity":2 //数量のレコードは省略可能
}
```

## PostmanでJSONデータを送信する

![スクリーンショット 2023-08-14 13.50.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2765288/c5d1cdfc-cf45-2556-439e-c580c9a01993.png)


設定したルーティングに合わせてPostmanの送信先をPostに設定し、URL(エンドポイント）を次のように設定します

```
http://localhost:8000/api/purchase
```
デフォルトでapi.phpで定義されたルートは`/api/プレフィックス`が自動的に付加されるため上記のようなURLになります。/api/というプレフィックスは変更も可能ですが、関連するコードを書き直す手間が発生するため推奨はされません。

```(参照)　api.php
use App\Http\Controllers\SalesController; //追記する

Route::post('/purchase', 'SalesController@purchase'); //追記する
```


`body`とはリクエストに含める主要な情報やデータのことを指します。特に、データを作成や更新するような操作を行うときに、そのデータを`body`に入れて送ります。

ラジオボタンの選択肢が複数存在しますが、`raw`という選択肢は、テキストとして自由にデータを入力できるオプションです。この`raw`を選んだとき、JSONやXMLなど、好きな形式でデータを`直接書き込む`ことができます。
```json
{
    "product_id":"5"
}
```


1. Postmanの左上にある"New"ボタンをクリックして新しいタブを開く
1. Postに変更する
1. URLを記述する
1. ラジオボタンをrawに変更する
1. JSONコードを記述する
1. SendボタンでサーバーのAPIにリクエストを送信する

## サーバーからのレスポンスを確認する

通信が成功すると画面下のメッセージにサーバーからのレスポンス（メッセージ）が表示されます
![スクリーンショット 2023-08-14 14.13.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2765288/c9f3049f-1663-a34f-021e-408495a6216e.png)

購入が成功していれば、該当するIDの商品在庫が減り、salesテーブルにも購入履歴が追記されます

次のように　return文でメッセージをレスポンスで送信しなければ特に表示はさせていません
```SalesController.php

// 商品が存在しない、または在庫が不足している場合のバリデーションを行う
    if (!$product) {
        return response()->json(['message' => '商品が存在しません'], 404);
    }
    if ($product->stock < $quantity) {
        return response()->json(['message' => '商品が在庫不足です'], 400);
    }


 // レスポンスを返す
    return response()->json(['message' => '購入成功']);
```

その他、サーバーのAPIまで通信が届いたもののフィールド名が異なっていたり、ダブルクォーテーションが抜けていたりなど文章に誤りがあった場合はレスポンスのメッセージではなく、Postmanの画面にエラーログが発生します。その場合は送信先を確認したり、Jsonの文章に誤りがないか確認する他、エラーログをchatGPTに問い合わせるなどすれば解決策を提示してくれますので、各々の対処が必要です

## Github

https://github.com/AlecTrippier/product_management

## さいごに

今回はAPIとPostmanの使い方について学習しました。APIはシステム間の窓口の役割を果たし、JSON形式でデータをAPIに送信することで、特定の操作や処理をサーバー上で実行させることができると理解しました。この記事が参考になった場合は「いいね」していただければ幸いです