Express、FastAPIを使用し、二重fetch構成のメリット

更新日:2025/03/09

基本用語解説

FastAPI

FastAPI は現代的な高性能な Python ウェブフレームワークです。

pip

pip は Python のパッケージ管理ツールです。Python で書かれたソフトウェアパッケージ(ライブラリ)をインストール・管理するためのコマンドラインツールです。

Node.jsの世界に慣れていれば、requirements.txt は package.json の dependencies セクションだけを取り出したようなもの、と考えるとわかりやすいでしょう。

requirements.txtの役割 requirements.txt fastapi==0.104.1 uvicorn==0.23.2 python-multipart==0.0.6 pytesseract==0.3.10 Pillow==10.0.1 # コメントも書ける 役割 • プロジェクトの依存パッケージをリスト化 • バージョンを明示して再現性を確保 • チーム開発での環境統一 • Node.jsのpackage.jsonに相当 使用方法 • pip install -r requirements.txt • 全依存パッケージを一度にインストール 開発環境 requirements.txt 本番環境 同一環境
pipの基本概念 pip Python Package Installer • Pythonの標準パッケージマネージャー • コマンドラインからパッケージをインストール • PyPI (Python Package Index) のリポジトリを使用 開発者 pip PyPI プロジェクト コマンド実行 取得 パッケージ インストール $ pip install パッケージ名

構成ファイル

大枠のフローとして「フロントエンドから直接FastAPIへアクセスするのではなく、Expressサーバーを経由して、API連携します」

API連携アーキテクチャの比較 現在の構成(二重fetch) ブラウザ Express FastAPI fetch fetch/axios ログ収集 メリット: • 集中ログ管理 • エラーハンドリングの一元化 • 認証層の追加 • API保護(スロットリング) デメリット: • 2回のHTTP通信によるレイテンシ • システム複雑性の増加 • サーバーリソースの消費 フロントエンドから直接アクセスする構成 ブラウザ FastAPI fetch(直接) CORS必要 メリット: • レイテンシの削減 • システムがシンプル • サーバーリソースの節約 • リアルタイム性の向上 デメリット: • CORSの設定が必要 • クライアント側での認証処理 • ログ収集が分散 • APIエンドポイントの公開
API連携アーキテクチャの比較表 比較項目 二重fetch構成 直接アクセス構成 パフォーマンス 低い(2回の通信) 高い(直接通信) ログ収集 集中管理(Express側) 分散(クライアント/FastAPI) セキュリティ 高い(API隠蔽/認証制御) 考慮が必要(公開API) システム複雑性 高い(中間層あり) 低い(直接通信) サーバーリソース 多く消費 効率的 エラーハンドリング 一元化(Express側) フロントエンドで処理必要 設定の複雑さ 中程度(内部通信) CORS設定等が必要

Expressフレームワークを使用

Expressでフロントエンド処理とFastAPI中継(ローカルにNode.jsインストール済み)

1. クライアント側 (index.html)
   │
   ├── 画像ファイル選択
   │   └── <input type="file">で画像を選択
   │
   ├── フォーム送信
   │   └── FormDataオブジェクトを作成
   │
   └── APIリクエスト送信
       └── fetch('http://localhost:3000/fastapi')

2. Express サーバー (server.js - Port:3000)
   │
   ├── 静的ファイル配信
   │   └── index.htmlの提供
   │
   ├── ファイル受信
   │   └── multerによるファイル処理
   │
   └── FastAPIへ転送
       └── fetch('http://localhost:8000/ocr')

3. FastAPI サーバー (main.py - Port:8000)
   │
   ├── CORS処理
   │   └── クロスオリジンリクエストの許可
   │
   ├── OCR処理
   │   └── 画像からテキスト抽出
   │
   └── レスポンス返却
       └── JSON形式で結果を返却

4. データの戻り
   │
   ├── FastAPI → Express
   │   └── OCR結果のJSON
   │
   ├── Express → クライアント
   │   └── JSONデータの転送
   │
   └── クライアント表示
       └── 結果をHTML上に表示
Express/
├── server.js           # Express サーバー(ポート3000)
├── index.html          # フロントエンドのメインページ
├── main.py            # FastAPI サーバー(ポート8000)
├── package.json       # npm パッケージ設定
├── package-lock.json  # npm パッケージのバージョン固定
└── node_modules/      # npm パッケージのインストール先
    ├── express/
    ├── cors/
    ├── multer/
    ├── node-fetch/
    ├── form-data/
    └── ...

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FastAPIにPOSTリクエスト</title>
    <style>
        .result-area {
            margin-top: 20px;
            padding: 10px;
            border: 1px solid #ccc;
            min-height: 100px;
        }
    </style>
</head>
<body>
    <h1>画像からテキストを抽出</h1>
    
    <form id="ocr-form">
        <div>
            <label for="image-file">画像ファイル:</label>
            
            <!-- accept="imgage/*"とは -->
            <input type="file" id="image-file" accept="image/*" required>
        </div>
        <div style="margin-top: 10px;">
            <button type="submit">テキスト抽出</button>
        </div>
    </form>
    
    <div class="result-area" id="result">
        <p>抽出結果がここに表示されます...</p>
    </div>

    <script>
        // 送信ボタンクリック時の処理
        document.getElementById('ocr-form').addEventListener('submit', async function(e) {
            e.preventDefault(); // デフォルトの動作を防止
            
            const fileInput = document.getElementById('image-file');
            const resultDiv = document.getElementById('result');
            
            if (!fileInput.files.length) {
                resultDiv.innerHTML = '<p>ファイルを選択してください</p>';
                return;
            }
            
            const file = fileInput.files[0];
            const formData = new FormData();
            formData.append('file', file);
            
            resultDiv.innerHTML = '<p>処理中...</p>';
            
            try {
                const response = await fetch('http://localhost:3000/fastapi', {
                    method: 'POST',
                    body: formData
                });
                
                if (!response.ok) {
                    throw new Error('APIリクエストに失敗しました');
                }
                
                const data = await response.json();
                resultDiv.innerHTML = `<p>抽出されたテキスト:</p><pre>${data.extracted_text}</pre>`;
            } catch (error) {
                resultDiv.innerHTML = `<p>エラー: ${error.message}</p>`;
                console.error('Error:', error);
            }
        });
    </script>
</body>
</html>

server.js

import express from 'express';
import multer from 'multer';
import fetch from 'node-fetch';

const app = express();

// 静的ファイルの配信設定
app.use(express.static('.'));

// アップロードされたファイルをメモリに保存
const upload = multer({ storage: multer.memoryStorage() });

// FastAPIエンドポイント
app.post('/fastapi', upload.single('file'), async (req, res) => {
    try {
        if (!req.file) {
            return res.status(400).json({ error: 'ファイルがアップロードされていません' });
        }

        // Blobを使用してFormDataを作成
        const formData = new FormData();
        const blob = new Blob([req.file.buffer], { type: req.file.mimetype });
        formData.append('file', blob, req.file.originalname);

        const response = await fetch('http://localhost:8000/express-fastapi', {
            method: 'POST',
            body: formData
        });

        if (!response.ok) {
            throw new Error('FastAPIサーバーでエラーが発生しました');
        }

        const data = await response.json();
        res.json(data);
    } catch (error) {
        console.error('エラー:', error);
        res.status(500).json({ error: 'リクエスト処理中にエラーが発生しました' });
    }
});

const PORT = 3000;
app.listen(PORT, () => {
    console.log(`サーバーが起動しました: http://localhost:${PORT}`);
}); 
ExpressとFastAPI間のデータ通信フロー ブラウザ Express FastAPI FormData POST リクエスト 画像データ (バイナリ) FormData POST リクエスト 画像データ (バイナリ) JSON { “extracted_text”: “抽出テキスト”, “status”: “success” } JSON { “extracted_text”: “抽出テキスト”, “status”: “success” } リクエスト (上) → レスポンス (下) 凡例: リクエスト (FormData – 画像バイナリデータ) レスポンス (JSON – テキストデータ)

FastApi

テストで確認用にシンプルな構成

python未インストールでDocker開発しました

プロジェクトルート/
├── app/
│   ├── __pycache__/    # Pythonのコンパイル済みファイル(自動生成)
│   │   └── *.pyc
│   └── main.py         # FastAPIのメインアプリケーションファイル
│
├── .dockerignore       # Dockerビルド時に除外するファイルの設定
├── Dockerfile          # Dockerイメージのビルド設定
└── requirements.txt    # Pythonパッケージの依存関係リスト

Dockerfile

# Pythonの公式イメージをベースとして使用
FROM python:3.9-slim

# Tesseract OCRとその日本語サポートのインストール
RUN apt-get update && apt-get install -y \
    tesseract-ocr \
    tesseract-ocr-jpn \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# コンテナ内の作業ディレクトリを/appに設定
WORKDIR /app

# 依存関係ファイルをコピー
COPY requirements.txt .

# 依存関係をインストール
RUN pip install --no-cache-dir -r requirements.txt

# 開発時はボリュームマウントを使用するため、COPYは不要
# COPY app/ .

# 開発用のコマンド
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

requirements.txt

# requirements.txt
# FastAPI: Webフレームワーク
fastapi==0.104.1

# Uvicorn: ASGIサーバー(FastAPIを実行するために必要)
uvicorn==0.23.2

# multipart: ファイルアップロード処理に必要
python-multipart==0.0.6

# pytesseract: TesseractのPythonラッパー(OCR処理用)
pytesseract==0.3.10

# Pillow: 画像処理ライブラリ
Pillow==10.0.1

main.py

from fastapi import FastAPI, File, UploadFile  # FastAPIとファイルアップロード関連のインポート
from fastapi.middleware.cors import CORSMiddleware  # CORSを許可するためのミドルウェア
import pytesseract  # OCRライブラリ
from PIL import Image  # 画像処理ライブラリ
import io  # バイナリデータ処理用
import logging

# ロギングの設定
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# FastAPIアプリケーションのインスタンス作成
app = FastAPI(title="Simple OCR API")

# CORSを許可するためのミドルウェアを追加
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 開発環境では一時的にすべてのオリジンを許可
    allow_credentials=False,  # '*'を使用する場合はFalseにする必要がある
    allow_methods=["*"],
    allow_headers=["*"],
)

# ルートエンドポイント(/)へのGETリクエスト処理
@app.get("/")
def read_root():
    return {"message": "Welcome to Simple OCR API"}  # JSONレスポンスを返す

# fastapiエンドポイント(/express-fastapi)へのPOSTリクエスト処理
@app.post("/express-fastapi")
async def extract_text(file: UploadFile = File(...)):
    try:
        # アップロードされた画像ファイルを読み込む
        image_bytes = await file.read()
        
        logger.debug(f"Received file size: {len(image_bytes)} bytes")
        logger.debug(f"File content type: {file.content_type}")
        
        # 画像オブジェクトを作成
        image = Image.open(io.BytesIO(image_bytes))
        
        logger.debug(f"Image size: {image.size}")
        logger.debug(f"Image mode: {image.mode}")
        
        # Tesseract OCRでテキスト抽出を実行
        logger.debug("Starting OCR processing...")
        text = pytesseract.image_to_string(image, lang='jpn+eng')
        logger.debug(f"OCR completed. Extracted text: {text}")
        
        return {"extracted_text": text.strip()}
        
    except Exception as e:
        logger.error(f"Error occurred: {str(e)}", exc_info=True)
        return {"error": str(e)}

pytesseract

pytesseractはPythonからTesseract OCR(光学文字認識エンジン)を利用するためのラッパーライブラリです。画像からテキストを抽出するのに使用されます。

pytesseractの具体的な使用方法

text = pytesseract.image_to_string(image, lang='jpn+eng')

FastAPIの開発を依頼

「ExpressサーバーからPOSTリクエストでOCR処理用の画像を送信するためのFastAPIサービスを開発していただきたいです。」

「画像はFormData形式で送信され、これをFastAPI側で受け取っていただき、画像内のテキストを抽出をお願いします」

「抽出したテキストは {“extracted_text”: “抽出されたテキスト”} という形式のJSONで返却してください。エラー発生時は {“error”: “エラーメッセージ”} 形式でレスポンスを返してください。」

「使用するエンドポイントは /express-fastapi としてください。」

「日本語と英語の両方のテキストを認識できるよう設定し、ExpressサーバーからのクロスオリジンリクエストのためにCORS設定も必要です。デバッグがしやすいよう、ログレベルはDEBUGで設定してください。」

FastAPI開発依頼要件表 項目 詳細 エンドポイント /express-fastapi (POST) 入力形式 マルチパートフォームデータ (FormData) で画像ファイル レスポンス形式 成功時: {“extracted_text”: “抽出されたテキスト”} エラー時: {“error”: “エラーメッセージ”} OCR設定 日本語と英語両方の認識対応 (lang=’jpn+eng’) CORS設定 クロスオリジンリクエストを許可 (ExpressサーバーからのPOST) ログ設定 DEBUG レベルでログ出力(開発時のトラブルシューティング用)
人気記事ランキング
話題のキーワードから探す