フロントエンド: react axios バックエンド：SpringBoot JWT認可方式のCRUD処理　検証

# 概要
フロントエンド：react nextjs axios
バックエンド：SpringBoot restapi
でB2CのECサイト作ってます。

SpringBoot側で作ったRestApiをフロントエンド側で動かすことができるか検証します。
ドメインは買い物かごです。

# 開発環境
開発環境
OS:windows10

バックエンド側：
IDE:IntelliJ Community
spring-boot-starter-parent 2.75
java　：　11

データベース
mysql:8.0.29
クライアントソフト: MySQL Workbench

フロントエンド：
IDE:VScode

├── @types/node@18.11.15
├── @types/react-dom@18.0.9
├── @types/react@18.0.26
├── axios@1.2.1
├── eslint-config-next@13.0.6
├── eslint@8.29.0
├── next-auth@4.18.7
├── next@13.0.6
├── react-bootstrap@2.7.0
├── react-dom@18.2.0
├── react@18.2.0
├── styled-components@5.3.6
├── styled-jsx@5.1.1
└── typescript@4.9.4
# ユースケース
ユースケースは買い物カゴです。
具体的には
1.顧客の買い物かごの情報を取得する(Read)
2.顧客の買い物かごに商品を追加する (Create)
3.顧客の買い物かごに入ってる商品の数量を変更する（Update）
4.顧客の買い物かごに入っている商品を一つ取り除く(Delete)
5.顧客の買い物カゴに入っている商品を全て乗り除く(Delete)

顧客の認可はSpringBoot側から発行されたjwt方式のトークンを使っています。


# ソースコード
Service,repository,Entity分野はここでは書きません。
詳細はgithubのリポジトリで確認お願いします。

https://github.com/RYA234/springboot-ecshop-rest-api/tree/6.CartItem/src/main/kotlin/com/example/restapi/domain/cartItem

https://github.com/RYA234/springboot-ecshop-rest-api/tree/6.CartItem/src/main/kotlin/com/example/restapi/implement/cartItem





## バックエンド側
```CartItemRestCotnroller.java
package com.example.restapi.implement.cartItem;


import com.example.restapi.domain.cartItem.CartItemService;
import com.example.restapi.domain.customer.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;


@RestController
//@CrossOrigin(origins = "http://127.0.0.1:3000")
@CrossOrigin(origins = "http://localhost:3000")
public class CartItemRestController {
    @Autowired
    CartItemService cartItemService;
    @Autowired
    CustomerService customerService;

    @PostMapping("/api/cart/add")
    public String addProductCart(  @RequestParam("productId") Integer productId,
                                   @RequestParam("quantity") Integer quantity,
                                   HttpServletRequest request
    ){
        String message;
        try {
            //Get customerId from Jwt
            Integer customerId = customerService.getIdfromJwtToken(request);
            cartItemService.addProduct(productId, quantity, customerId);
            // String  email = jwtTokenProvider.getCustomernameFromJWT(token);
            message = "customerId:"+ customerId +  "added" + quantity +"quantity productId" + productId;
            return message;
        }catch(Exception e){
            message = "Exception error";
            return message;
        }
    }

    @GetMapping("/api/cart/all")
    public List<CartItemDto> listCartItems(HttpServletRequest request){
        List<CartItemDto> cartItemDtoList = new ArrayList<>();
        //Get customerId from Jwt
        Integer customerId = customerService.getIdfromJwtToken(request);
        return cartItemService.listCartItems(customerId);
        }

    @PutMapping("/api/cart/update")
    public String updateQuantity(@RequestParam("productId") Integer productId,
                                 @RequestParam("quantity") Integer quantity,
                                 HttpServletRequest request){
        //Get customerId from Jwt
        Integer customerId = customerService.getIdfromJwtToken(request);
        cartItemService.updateQuantity(productId,quantity,customerId);
        return "updated";
    }


    @DeleteMapping("/api/cart/remove")
    public String removeProduct( @RequestParam("productId") Integer productId,
                                 HttpServletRequest request){
            //Get customerId from Jwt
            Integer customerId = customerService.getIdfromJwtToken(request);
            cartItemService.removeProduct(productId,customerId);
            return "ShoppingCart is removed in customerId" + customerId ;
        }

    @DeleteMapping("/api/cart/delete")
    public String deleteByCustomer(HttpServletRequest request){
        //Get customerId from Jwt
        Integer customerId = customerService.getIdfromJwtToken(request);
        cartItemService.deleteByCustomer(customerId);
         return customerId + "is all deleted in all ShoppinCart";
    }





}



```


## フロントエンド側

``` carItemServiceCheck.tsx
import { useState, useEffect, useCallback } from "react";
import { CartItem } from "../../types/cartItem";
import { Customer } from "../../types/customer";
import * as customerService from "../../service/customerService";
import * as cartItemService from "../../service/cartItemService";

/**
 * 
 * @remarks SpringBootのRestAPIを nextjs側が動かせるかの検証用ページ
 * 
 * @authoer RYA234
 * 
 * @returns 
 */
export default function CartItemServiceCheck(this: any) {
  const [currentCartItems, setCurrentCartItems] = useState<CartItem[]>([]);
  const [jwtAccessToken, setJwtAccessToken] = useState<string | null>();

  // ログイン処理は初回時のみ実行 CartItemドメインの検証用ページなので
  useEffect(() => {
    loginAndGetCartItem(), [];
  });

  // ログイン処理後カートアイテムの情報取得更新する
  const loginAndGetCartItem = async () => {
    await login();
    console.log("await between login and getCartItems");
    await getCartItems();
  };

  // ログインする用のAPIを叩く
  const login = () => {
    const requestBody: Customer = {
      email: "aaaba@gmail.com",
      password: "test",
      append: function (arg0: string, inputEmail: string): unknown {
        throw new Error("Function not implemented.");
      },
    };
    customerService
      .signIn(requestBody)
      .then((response) => {
        localStorage.setItem("accessToken", response.data.accessToken);
        setJwtAccessToken(response.data.accessToken);
        console.log("accesssToken is set");
      })
      .catch((error) => {
        localStorage.setItem("accessToken", " error");
        setJwtAccessToken(localStorage.getItem("accessToken"));
        console.log("accesssToken is not set");
      });
  };

  // カートアイテムの情報を取得する
  const getCartItems = () => {
    cartItemService
      .getCartItems(jwtAccessToken as string)
      .then((response) => {
        // 再レンダリングを防ぐために、レスポンスのデータが変わっている場合のみsetCurrentCartItemsを実行する。
        // if ((response.data as CartItem[]).toString() != currentCartItems.toString()) // ToString()だとエラーになるのでJSON.stringifyに修正
        if (
          JSON.stringify(response.data as CartItem[]) !=
          JSON.stringify(currentCartItems)
        ) {
          setCurrentCartItems(response.data);
          console.log("cartItems are set");
          console.log(response.data);
        }
      })
      .catch((error) => {
        console.log("cartItems are not set");
      });
  };

  let inputProductId: number = 0;
  let inputQuantity: number = 0;

  // カートアイテムを追加する
  const addCartItem = async () => {
    const inputCartItem: CartItem = {
      productId: inputProductId as number,
      quantity: inputQuantity as number,
      id: 0,
      customerId: 0,
    };
    await cartItemService
      .addProductCart(inputCartItem, jwtAccessToken as string)
      .then((response) => {
        console.log(response.data);
        console.log("cartItems is added");
      })
      .catch((error) => {
        console.log("cartItems are not set");
      });
  };

  // データベースに追加後、データベースから再取得する
  // ボタンに割り当てる
  const addCartItemHandler = async () => {
    console.log("jwt" + jwtAccessToken);
    await addCartItem();
    console.log("between addCartItem and getCartItems");
    await getCartItems();
  };

  // カートアイテムの数量を更新後、データベースから再取得して画面更新する。
  // ボタンに割り当てる
  const updateQuantitAndGetCartItemsyHandler = async () => {
    await updateQuantity();
    console.log("between updateQuantity and getCartItems");
    await getCartItems();
  };

  let updateInputquantiy: number = 0;
  let updateInputProductId: number = 0;
  // カートアイテムの数量を更新する
  const updateQuantity = async () => {
    const updateCarItem: CartItem = {
      productId: updateInputProductId as number,
      quantity: updateInputquantiy as number,
      id: 0, // 使わない
      customerId: 0, // 使わない
    };
    await cartItemService
      .updateQuantity(updateCarItem, jwtAccessToken as string)
      .then((response) => {
        console.log(response.data);
      })
      .catch((error) => {
        console.log("update is failed");
      });
  };
  let removeInputProductId: number = 0;

  // removeInputProducIdの値の商品を削除後　再度データベースから取得して画面更新する。
  // ボタンに割り当てる
  const removeCartItemAndGetItemsHandler = async () => {
    await removeCartItem();
    await getCartItems();
  };

  // removeInputProducIdの値の商品を削除する
  const removeCartItem = async () => {
    const removeCartItem: CartItem = {
      productId: removeInputProductId as number,
      id: 0,
      customerId: 0,
      quantity: 0,
    };

    await cartItemService
      .removeProduct(removeCartItem, jwtAccessToken as string)
      .then((response) => {
        console.log(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  };
  // 顧客のカートアイテム全削除後、再度データベースから取得して画面更新する。
  // ボタンに割り当てる
  const deleteAndGetItemsHandler = async () => {
    await deleteCartItem();
    await getCartItems();
  };

  // 顧客のカートアイテム全削除する
  const deleteCartItem = async () => {
    await cartItemService
      .deleteByCustomer(jwtAccessToken as string)
      .then((response) => {
        console.log(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  };

  return (
    <>
      <h1>カートアイテムの取得、追加、更新、削除 検証ページ</h1>
      <div>※aaabaa@gmail.comでログインしているものとする。</div>
      <h1>カートアイテムの全取得</h1>
      <div>リアルタイムで表示されます。</div>
      顧客ID,商品ID,数量
      {currentCartItems.map((cartItem: CartItem) => {
        return (
          // eslint-disable-next-line react/jsx-key
          <div>
            <div>
              {cartItem.customerId},{cartItem.productId},{cartItem.quantity}
            </div>
          </div>
        );
      })}
      <br />
      <h1>カートアイテム追加</h1>
      <label>商品ID</label>
      <input
        type="text"
        placeholder="商品ID"
        onChange={(event) =>
          (inputProductId = event.target.value as unknown as number)
        }
      />
      {/* onchangeをするとloginAndGetCartItemが実行されるのが良くない */}
      <br />
      <label>個数</label>
      <input
        type="text"
        placeholder="1"
        onChange={(event) =>
          (inputQuantity = event.target.value as unknown as number)
        }
      />
      <button onClick={addCartItemHandler}>カートアイテム追加</button>
      <h1>カートアイテムの数量を変更</h1>
      <label>数量変更する商品ID</label>
      <input
        type="text"
        placeholder="商品ID"
        onChange={(event) =>
          (updateInputProductId = event.target.value as unknown as number)
        }
      />
      <br />
      <label>数量</label>
      <input
        type="text"
        placeholder="数量"
        onChange={(event) =>
          (updateInputquantiy = event.target.value as unknown as number)
        }
      />
      <br />
      <button onClick={updateQuantitAndGetCartItemsyHandler}>数量変更</button>
      <h1>カートアイテムを1つ削除</h1>
      <label> 商品ID</label>
      <input
        type="text"
        placeholder="削除したい商品ID"
        onChange={(event) =>
          (removeInputProductId = event.target.value as unknown as number)
        }
      />
      <br />
      <button onClick={removeCartItemAndGetItemsHandler}>remove</button>
      <h1>カートアイテムを全削除</h1>
      <button onClick={deleteAndGetItemsHandler}>
        delete カートアイテム全削除
      </button>
    </>
  );
}



```

``` cartItemService.ts
import axios from 'axios';
import { CartItem } from '../types/cartItem';

/**
 * 
 * @remarks SpringBootで作ったAPIを呼ぶ関数
 * 
 * 
 * @param jwtAccessKey 
 * @returns 
 */

export const addProductCart = ({productId,quantity}: CartItem, jwtAccessKey:string) =>{
	return axios.post(`http://127.0.0.1:5000/api/cart/add`
	+ `?productId=` +productId
	+ `&quantity=` +quantity
	,{},
	{ headers : {
		'Authorization': `Bearer ${jwtAccessKey}`,
		'Request-Method' : 'POST',
		'Content-Type': 'application/json',
		'Access-Control-Allow-Origin': 'http://127.0.0.1:5000/*',
		'Access-Control-Allow-Headers': 'accept, accept-language, content-language, content-type',
		'Access-Control-Allow-Credentials': 'true'
	}}
	);
}


//
export const getCartItems =(jwtAccessKey : string)=>{
	return axios.get(`http://127.0.0.1:5000/api/cart/all`,
	{ headers : {
			'Authorization': `Bearer ${jwtAccessKey}`
		}}
	);
}

// 
export const updateQuantity = ({productId,quantity}:CartItem,jwtAccessKey:string) =>{
	return axios.put(`http://127.0.0.1:5000/api/cart/update`
	+ `?productId=` +productId
	+ `&quantity=` +quantity,
	{},
	{ headers : {
			'Authorization': `Bearer ${jwtAccessKey}`,
			'Request-Method' : 'PUT',
			'Content-Type': 'application/json',
			'Access-Control-Allow-Origin': 'http://127.0.0.1:5000/*',
			'Access-Control-Allow-Headers': 'accept, accept-language, content-language, content-type',
			'Access-Control-Allow-Credentials': 'true'
	}}
	)
}

export const removeProduct = ({productId}:CartItem, jwtAccessKey:string) =>{
return axios.delete(`http://127.0.0.1:5000/api/cart/remove`
	+`?productId=`+ productId,
	{ headers : {
			'Authorization': `Bearer ${jwtAccessKey}`
	}}
);
}

export const deleteByCustomer = (jwtAccessKey:string) =>{
	return axios.delete(`http://127.0.0.1:5000/api/cart/delete`,
	{ headers : {
			'Authorization': `Bearer ${jwtAccessKey}`
	}}
);
}

```

```cartItem.d.ts


export type CartItem = {
	id:number;	// 買い物かごのID
	customerId:number // 顧客のID
	productId:number // 商品のId
	quantity: number  // 数量 

}
```

# 動きを確認
gifアニメで実際の動きを記録します。
画面左側はブラウザ　右側はmysql workbenchです。

## 追加時の処理
商品ID23　を２個追加している様子です。

![cartItem_add.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/a8db65bb-e492-b433-8208-8d4c9e970680.gif)

github

https://github.com/RYA234/ecshop_front_react/commit/2f3140500471197a8bb6d3d899b47da77d2fd53d

## 数量変更時
商品ID23　を５個に変更している様子です。

![cartItem_update.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/ea54306a-8961-d3e3-3341-a1f6e38885d7.gif)

github

https://github.com/RYA234/ecshop_front_react/commit/ffd2a8ee1e4e3374bb3d9872514eb66ef4bfa2bf


## 買い物かごから選択した商品IDを削除
商品ID4を買い物カゴを削除している様子です。
![cartItem_remove.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/9002850e-7a03-63d1-d0ea-a74455e59ae4.gif)

github

https://github.com/RYA234/ecshop_front_react/commit/fbf4f9e15e76879d6def12fd35c52bc91c7189ee

## 買い物カゴ全削除
買い物カゴの中身を全削除している様子です。
![cartItem_delete.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/c0580c9c-82f4-69a6-eb54-514680f58e7d.gif)

github

https://github.com/RYA234/ecshop_front_react/commit/292f5080a5fc74981437c24e9aa0a687d7114981