React開発入門⑤ 〜サーバーサイドAPI通信の実装(Express.js)〜

## 目的
・Expressのインストールおよび起動を行う。
・クライアントからPOSTメソッドを用いてサーバーサイドに情報を渡して処理を行い、クライアント側に返す。
・CORSやAPIを理解する。
・ざっくりと非同期処理を理解する

## 成果物イメージ
入力フォームの送信をクリックすると入力フォームの情報をPOSTメソッドによるAPI通信でサーバーに受け渡せる。

## Express.jsのインストールと起動
/backendなど適当な名前のフォルダを作成してください(reactプロジェクト直下に作るのではなく、分離して下さい。)
作成したフォルダ直下に入り、以下のコマンドを使ってexpressをインストールしてください。

```
npm　install express
```

フォルダの中に様々なパッケージファイルがインストールされたと思います。

次に、index.jsを記載します。
このindex.jsに
・どのポートで待ち受けるか？
・API用のURLを定義し、どのような処理を行い、レスポンスを行うか？
などを記載します。

今回はテスト用に以下のindex.jsをexpressプロジェクト配下で作成してください。

```index.js
const express = require('express');
const app = express();
const port = 3001; // listenするport番号

app.get('/test', (req, res) => {
    res.send("hello world");
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});
```

:::note
React(フロント用)で3000番ポートを使用しているため、3001番ポートを使用してください。
ポートがバッティングするとどちらかのアプリケーションが強制停止されてしまいます。
:::

次に以下のコマンドを叩いてexpressサーバーを立ち上げましょう。(index.jsと同じ階層で打ってください)

```
node index.js
```

GETメソッドで定義しているため、直接ブラウザ上でURLを叩くとレスポンスが帰ってくるはずです。
ブラウザにlocalhost:3001/testのURLを入力してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/4dfb823d-607e-760c-b9b8-5d67a01e6eab.png)


しっかりとレスポンスの内容が表示されています。

## ログインフォームを作る
まだ、フロント側のログインフォームを作っておりませんのでログインフォームコンポーネントを作成します。
バリデーションのチェックも行う処理も含めて下記のコードになります。（このページに遷移させるルーターも追加してください）

```loginForm.jsx

// 組み込むべき機能
// [1]バリデーションチェック：：パスワードポリシーのチェック
// [2]パスワードのストレッチング
// [3]3回ミスったらロック
// [4]メールアドレスでのパスワードチェンジ
// [5]新規アカウント作成画面への遷移
// [6]パスワードを●●●で隠す奴

import React, { useState, useRef } from 'react';

//　検証用のbackend_url
const backend_url = 'http://localhost:3001/'
//　本番用のbackend_url
// const backend_url = 'http//xxx.xxx.xxx.xxx:xxxx/'

export const LoginPage =()=> {
    
    const submitAuthdata = useRef();

    //入力フォームのバリデーションチェックテキスト
    const [usernameText, setUsername] = useState('ユーザー名を入力してください');
    const [passwordText, setPassword] = useState('パスワードを入力してください');

    // =========バリデーションチェック関数=========
    const judgeUsernameValid = (Username) => {
        // 文字列の中に英語＆数字が1回以上かつ8文字以上16文字以下
        const regexp = /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,16}$/i;

        if (Username.match(regexp)) {
            setUsername('入力されたユーザー名はポリシーを満たしています。');
        }
        else {
            setUsername('入力されたユーザー名はポリシーを満たしていません');
        }
        // デバック用
        console.log(Username.length);
        console.log(typeof(Username));
    };
    
    
    // ========バリデーションチェック関数========
    const judgePasswordValid = (Password) => {
        // 文字列の中に英語＆数字が1回以上かつ8文字以上16文字以下
        const regexp = /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,100}$/i;

        if (Password.match(regexp)) {
            setPassword('入力されたパスワードはポリシーを満たしています。');
        }
        else {
            setPassword('入力されたパスワードはポリシーを満たしていません');
        }
    };
        
    // ユーザーIDとパスワードのバリデーションチェックがOKなら送信処理を行う。
    const validationCheckText = async (event) => {
        event.preventDefault(); //ページリロード防止

        //バリデーションチェックの可否判定
        if (usernameText==='入力されたユーザー名はポリシーを満たしています。' 
            && passwordText==='入力されたパスワードはポリシーを満たしています。')
            {   
                console.log('フォームの検証成功');
                // 入力フォームの状態を受け取り
                const username = await submitAuthdata.current.querySelector('input');
                console.log(username);
                const password = await submitAuthdata.current.querySelector('input');
                console.log(password);
                
                // json形式のdictを作成
                const jsonString = await {'username': username.value,'password': password.value}
                console.log(jsonString);
                // json.stringify ⇒ dictをjsonオブジェクトに変換
                const userAuthJson = await JSON.stringify(jsonString);
                console.log("userAuthJson");
                console.log(userAuthJson);

                // user認証のAPI
                const url = await backend_url + 'userauth';
                // ★例外処理は後で書く
                const response = await fetch(url,{
                    method: 'POST',
                    headers: {
                        'Origin': 'http://localhost:3000',
                        'Content-Type': 'application/json',
                    },
                    body: userAuthJson
                })
                const responseJson = await response.json;
                console.log(response);
                console.log(responseJson);
                
                // ページを遷移する処理
                return 'return true';

            }else{

                console.log('フォームの検証失敗');
                return 'return false';
            }
    };

    return(
        <div className='loginForm'>
            <head>
                <meta charset='UTF-8' />
                <title>sample.html</title>
            </head>

            <body>
                <header>
                    <h1>勤怠管理アプリケーション</h1>
                </header>

                <form action='' onSubmit={validationCheckText} ref={submitAuthdata}>
                    <div>
                        Username : <input type='text' name='name' placeholder='username' autoComplete='username'
                                        onChange={(event) => judgeUsernameValid(event.target.value)} 
                                />
                        <p>{ usernameText }</p>
                    </div>
                    <div>
                        Password : <input type='password' name='pass' placeholder='password' autoComplete='current-password'
                                    onChange={(event)=>judgePasswordValid(event.target.value)}
                                />
                        <p>{passwordText}</p>
                    </div>
                    <input type='submit' value='送信' />
                </form>
            </body>
        </div>
        );
    };
```



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/20cb2d44-fc2c-a671-3d92-41500b72567e.png)
このようにリアルタイムで入力されたユーザー名とパスワードがポリシーを満たしているかを判定しています。
次に、送信ボタンクリックによるFetchAPIを用いたAPI呼び出しについて解説します。

# 本題
## FecthAPIを用いたフロント側でのAPI呼び出し
React側のLoginpage.jsxでサーバーサイドのAPI呼び出し処理をFectAPIを使って書いていきます。

```API呼び出しの部分_loginForm.jsx
                // json形式のdictを作成
                const jsonString = await {'username': username.value,'password': password.value}
                console.log(jsonString);
                // json.stringify ⇒ dictをjsonオブジェクトに変換
                const userAuthJson = await JSON.stringify(jsonString);
                console.log("userAuthJson");
                console.log(userAuthJson);

                // user認証のAPI
                const url = await 'http://localhost:3001/userauth';
                // ★例外処理は後で書く
                const response = await fetch(url,{
                    method: 'POST',
                    headers: {
                        'Origin': 'http://localhost:3000',
                        'Content-Type': 'application/json',
                    },
                    body: userAuthJson
                })
                const responseJson = await response.json;
```

### ansyc/await
APIの呼び出しをする際は非同期処理を行うことが多い。
（ただ、今回のAPIは非同期処理を使わない方が正しい気がしている。。。
　パスワードの認証が終わるまで次の処理を実行する意味がないよなぁ。。。）

2025/1追記：：非同期処理に関する記載が誤っておりました。申し訳ないです。

:::note
非同期処理...
ある処理が完了するのを待たずに別の処理を行える。
:::

今回は認証情報を確かめるAPIのため、サーバーの応答を待たずに次の処理に進まれると困ります。
非同期を行う関数はaysncで定義し、非同期させたい処理にはawaitを付けることでその処理を終了が確定するまで(正確にはPromiseオブジェクトのresolveが返ってくるまで)待機させてくれます。
他のサイトでもっと詳しく説明されていると思いますので調べてみてください。

### json形式
APIでの情報の受け渡しには一般的にJsonというデータ構造を使用します。
Javascriptには標準で辞書型の文字列をJsonデータ構造に変換してくれるJSON.stringifyという関数があります。

```json変換_loginForm.jsx
const jsonString = await {'username': username.value,'password': password.value}
const userAuthJson = await JSON.stringify(jsonString);
```
userAuthJson内にJson形式にされたユーザー名とパスワード情報が格納されています。
これをFetchAPIのhttpボディに入れてサーバーへと送ります。


## サーバーサイド側でのJson受け取り
今回はサーバー側でJsonを受け取れるところまで書きます。

```index.js
const express = require('express');
const app = express();
const port = 3001; // listenするport番号


//cors対応
var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
    res.header('Access-Control-Allow-Headers', 'X-Requested-With, Origin, X-Csrftoken, Content-Type');
    next();
  }
app.use(allowCrossDomain);

// Body内のJsonを辞書オブジェクトに変換
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// POSTのAPI(受け取ったリクエストを表示)
app.post('/userauth', (req, res) => {
  console.log(req.body);
  res.json(
    "{'message': 'OK'}"
);
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});
```

### CORS対応

```js
var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
    res.header('Access-Control-Allow-Headers', 'X-Requested-With, Origin, X-Csrftoken, Content-Type');
    next();
  }
app.use(allowCrossDomain);
```

ここでAPIのレスポンスに'Access-Control-Allow-Origin'ヘッダーを付加しています。
これは別のドメインからAPIを呼び出す時に許可してもよいドメインを指定します。
別のドメインからAPIを呼び出す時に、このヘッダーがないorこのヘッダーのドメインに該当しないAPI問い合わせについては強制的に破棄されてしまいます。
面倒くさいですが、セキュリティ的に仕方ないのでしっかりと許可しても良いドメインを考えてヘッダーを付加しましょう。

## テスト：サーバー側で受け取れるか＆フロント側にレスポンスが返ってくるか
ログインフォームにポリシーを満たしたユーザー名とパスワードを入力して送信してみましょう。
しっかりとサーバーサイドで受け取れていれば
```js
console.log(req.body);
```
上のコードによるリクエストのボディがサーバーのログに表示されるはずです。

今回は
ユーザー名：testes12345
パスワード：qiita65535
で試してみます。
また、ブラウザの開発者ツールおよびExpress立ち上げているコンソールで挙動を確かめます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/b05cbec2-9037-86dd-f3a2-444b9b9031ac.png)
送信！

### フロント側
ステータスコード200番、つまり成功が返ってきてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/5f75b71b-2df0-3827-5d2b-a29e1adb9ad1.png)

### サーバー側
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/217bd7d9-60f4-c663-6659-9822f95a0cca.png)
しっかりとサーバー側のコンソールログ表示で、入力したユーザー名とパスワードの情報が表示されました。
あとはこいつをもとにデータベースに問い合わせたりして色々やっていくわけです。


## Next
DB接続とかセッションIDによるログイン状態の管理などをやりたいです。