Open3

React⇔Fast APIのAPI連携の実装方法を理解する

ひでひで

単位変換アプリでの具体例

バックエンド(FastAPI)の役割

  • 変換の計算をする
  • 結果をJSONで返す
from fastapi import FastAPI

app = FastAPI()

@app.get("/convert/length")
def convert_length(value: float, from_unit: str, to_unit: str):
    # メートルを基準にした変換表
    to_meter = {
        "cm": 0.01,
        "m": 1.0,
        "km": 1000.0
    }
    
    # まずメートルに変換
    meter_value = value * to_meter[from_unit]
    # 目標の単位に変換
    result = meter_value / to_meter[to_unit]
    
    return {
        "original_value": value,
        "original_unit": from_unit,
        "converted_value": result,
        "converted_unit": to_unit
    }

フロントエンドとバックエンドの連携

React(フロントエンド)

// 単位変換を行う関数
const convertUnit = async () => {
  try {
    // FastAPIのエンドポイントを呼び出し
    const response = await fetch(
      `http://localhost:8000/convert/length?value=100&from_unit=cm&to_unit=m`
    );
    
    // JSONデータを取得
    const data = await response.json();
    
    console.log(data); // { "converted_value": 1.0, ... }
    
    // 結果を画面に表示
    setResult(data.converted_value);
    
  } catch (error) {
    console.error('エラーが発生しました:', error);
  }
};

4. データの流れを理解

ユーザー操作(React)
    ↓
「100cm を m に変換」ボタンクリック
    ↓
fetch()でAPIを呼び出し
    ↓
FastAPI「100cm → 1.0m」計算
    ↓
JSON形式で結果を返す
    ↓
Reactが結果を受け取り
    ↓
画面に「1.0m」と表示

5. サンプルコード

バックエンド(main.py):

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# React(localhost:3000)からのアクセスを許可
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/convert")
def convert(value: float, from_unit: str, to_unit: str):
    # 超シンプルな例:cm ↔ m
    if from_unit == "cm" and to_unit == "m":
        result = value / 100
    elif from_unit == "m" and to_unit == "cm":
        result = value * 100
    else:
        result = value  # 同じ単位の場合
    
    return {"result": result}

フロントエンド(App.tsx)

import React, { useState } from 'react';

function App() {
  const [value, setValue] = useState<number>(0);
  const [result, setResult] = useState<number>(0);

  const handleConvert = async () => {
    try {
      const response = await fetch(
        `http://localhost:8000/convert?value=${value}&from_unit=cm&to_unit=m`
      );
      const data = await response.json();
      setResult(data.result);
    } catch (error) {
      console.error('エラー:', error);
    }
  };

  return (
    <div>
      <input 
        type="number" 
        value={value}
        onChange={(e) => setValue(Number(e.target.value))}
      />
      <span>cm</span>
      
      <button onClick={handleConvert}>変換</button>
      
      <div>結果: {result}m</div>
    </div>
  );
}

export default App;
ひでひで

フロントエンド(React)側の詳細

ユーザーの入力を受け取る部分

function App() {
  // ここに入力値を保存
  const [inputValue, setInputValue] = useState<string>('');
  const [result, setResult] = useState<string>('');

  return (
    <div>
      {/* ユーザーが数値を入力 */}
      <input 
        type="number" 
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="数値を入力"
      />
      <span>cm</span>
      
      <button onClick={handleConvert}>mに変換</button>
      
      <div>結果: {result}m</div>
    </div>
  );
}

実際のデータの流れ

1. ユーザーが「150」と入力
   ↓ 
   inputValue = "150" (Reactのstate)

2. 「変換」ボタンをクリック
   ↓
   handleConvert関数が実行される

3. fetch()でAPIに送信
   ↓
   http://localhost:8000/convert?value=150&from_unit=cm&to_unit=m

JSONでのやり取りについて

送信時(React → FastAPI):

// URLパラメータとして送信
const response = await fetch(
  `http://localhost:8000/convert?value=${inputValue}&from_unit=cm&to_unit=m`
);

// または、POST形式でJSON送信
const response = await fetch('http://localhost:8000/convert', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    value: 150,
    from_unit: 'cm',
    to_unit: 'm'
  })
});

受信時(FastAPI → React):

// FastAPIからのレスポンス(JSON形式の文字列)
// '{"result": 1.5, "original_value": 150, "unit": "m"}'

const data = await response.json(); // JSON文字列をJavaScriptオブジェクトに変換

console.log(data.result); // 1.5
console.log(data.unit);   // "m"

コード例

import React, { useState } from 'react';

function App() {
  const [inputValue, setInputValue] = useState<string>('');
  const [result, setResult] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(false);

  const handleConvert = async () => {
    // 入力チェック
    if (!inputValue || isNaN(Number(inputValue))) {
      alert('数値を入力してください');
      return;
    }

    setLoading(true); // ローディング開始

    try {
      // FastAPIに送信
      const response = await fetch(
        `http://localhost:8000/convert?value=${inputValue}&from_unit=cm&to_unit=m`
      );

      // レスポンスがOKか確認
      if (!response.ok) {
        throw new Error('API呼び出しに失敗しました');
      }

      // JSON形式のレスポンスを取得
      const data = await response.json();
      
      // 結果を画面に表示
      setResult(data.result.toString());

    } catch (error) {
      console.error('エラー:', error);
      alert('変換に失敗しました');
    } finally {
      setLoading(false); // ローディング終了
    }
  };

  return (
    <div>
      <h1>単位変換アプリ</h1>
      
      <input 
        type="number" 
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="数値を入力"
      />
      <span>cm → </span>
      
      <button 
        onClick={handleConvert}
        disabled={loading}
      >
        {loading ? '変換中...' : 'mに変換'}
      </button>
      
      {result && (
        <div>
          <strong>結果: {result}m</strong>
        </div>
      )}
    </div>
  );
}

export default App;
ひでひで

FastAPI側の詳細解説

基本的な構造

from fastapi import FastAPI

# FastAPIのインスタンスを作成
app = FastAPI()

# この@app.get()が「エンドポイント」を作る
@app.get("/convert")
def convert_unit(value: float, from_unit: str, to_unit: str):
    # ここで実際の処理を行う
    result = value / 100  # cm → m の場合
    
    # 辞書形式で返す → 自動的にJSONになる
    return {
        "original_value": value,
        "original_unit": from_unit,
        "result": result,
        "result_unit": to_unit
    }

エンドポイントって何?

一言で言うと「APIの窓口

# http://localhost:8000/convert にアクセスできる窓口を作る
@app.get("/convert")
def convert_unit():
    return {"message": "変換API"}

# http://localhost:8000/hello にアクセスできる窓口を作る
@app.get("/hello")
def say_hello():
    return {"message": "Hello!"}

パラメータの受け取り方

URLパラメータで受け取る場合:

# http://localhost:8000/convert?value=100&from_unit=cm&to_unit=m
@app.get("/convert")
def convert_unit(value: float, from_unit: str, to_unit: str):
    print(f"受け取った値: {value}")        # 100.0
    print(f"元の単位: {from_unit}")        # "cm"
    print(f"変換先単位: {to_unit}")        # "m"
    
    # 計算処理
    if from_unit == "cm" and to_unit == "m":
        result = value / 100
    elif from_unit == "m" and to_unit == "cm":
        result = value * 100
    else:
        result = value
    
    return {"result": result}

単位変換の具体的な実装

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# React(localhost:3000)からのアクセスを許可
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # Reactのサーバー
    allow_methods=["*"],
    allow_headers=["*"],
)

# 長さの変換表(メートル基準)
LENGTH_UNITS = {
    "mm": 0.001,
    "cm": 0.01,
    "m": 1.0,
    "km": 1000.0,
    "inch": 0.0254,
    "ft": 0.3048
}

@app.get("/convert/length")
def convert_length(value: float, from_unit: str, to_unit: str):
    """
    長さの単位変換を行う
    
    例: http://localhost:8000/convert/length?value=100&from_unit=cm&to_unit=m
    """
    
    # エラーチェック
    if from_unit not in LENGTH_UNITS:
        return {"error": f"未対応の単位です: {from_unit}"}
    
    if to_unit not in LENGTH_UNITS:
        return {"error": f"未対応の単位です: {to_unit}"}
    
    # 変換処理
    # 1. まずメートルに変換
    meter_value = value * LENGTH_UNITS[from_unit]
    
    # 2. 目標の単位に変換
    result = meter_value / LENGTH_UNITS[to_unit]
    
    # 3. 結果をJSONで返す
    return {
        "success": True,
        "original": {
            "value": value,
            "unit": from_unit
        },
        "converted": {
            "value": round(result, 6),  # 小数点6桁まで
            "unit": to_unit
        },
        "calculation": f"{value} {from_unit} = {round(result, 6)} {to_unit}"
    }

FastAPIが返すJSONの形

FastAPIのコード:

@app.get("/convert")
def convert_unit(value: float, from_unit: str, to_unit: str):
    result = value / 100
    
    # この辞書が自動的にJSONに変換される
    return {
        "original_value": value,
        "result": result,
        "status": "success"
    }

実際にブラウザで受け取るJSON:

{
  "original_value": 150.0,
  "result": 1.5,
  "status": "success"
}

エラーハンドリング

from fastapi import FastAPI, HTTPException

@app.get("/convert")
def convert_unit(value: float, from_unit: str, to_unit: str):
    # 負の値チェック
    if value < 0:
        raise HTTPException(
            status_code=400, 
            detail="値は0以上である必要があります"
        )
    
    # 対応していない単位のチェック
    supported_units = ["cm", "m", "km"]
    if from_unit not in supported_units:
        raise HTTPException(
            status_code=400,
            detail=f"対応していない単位: {from_unit}"
        )
    
    # 正常な処理
    result = value / 100
    return {"result": result}

自動生成されるAPI仕様書

FastAPIのめちゃ便利機能!!

http://localhost:8000/docs

にアクセスすると、自動でAPI仕様書が表示されます!

処理の流れ(FastAPI側)

1. Reactから http://localhost:8000/convert?value=150&from_unit=cm&to_unit=m にリクエスト
   ↓
2. FastAPIが「@app.get("/convert")」の関数を実行
   ↓
3. パラメータを取得: value=150.0, from_unit="cm", to_unit="m"
   ↓
4. 計算処理: 150 ÷ 100 = 1.5
   ↓
5. 辞書を作成: {"result": 1.5, "original_value": 150.0}
   ↓
6. FastAPIが自動的にJSONに変換
   ↓
7. React側に送信: '{"result": 1.5, "original_value": 150.0}'