◾️はじめに
https://dk521123.hatenablog.com/entry/2025/10/05/014051
の続き。 今回は、Python の WebフレームワークであるFastAPIでの ファイルアップロードについて扱う # これで、あと、FastAPI のDBの扱いさえ覚えれば、 # バックエンド側でやりたいことができそう
目次
【1】インストール 【2】例1:Web server 1)ファイル構成 2)コード 3)サーバ起動および動作確認 【3】例2:REST server + Frontend 1)ファイル構成 2)コード 3)サーバ起動および動作確認
【1】インストール
pip install fastapi uvicorn jinja2 aiofiles python-multipart
【2】例1:Web server
* 画像又はPDFをアップロードし、画面に表示する
1)ファイル構成
app ├── main.py ├── templates │ └── upload_form.html └── uploads ... アップロードしたファイルを溜め込む
2)コード
app/main.py
from fastapi import FastAPI, File, UploadFile, Request from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates import shutil import os os.makedirs("uploads", exist_ok=True) app = FastAPI() # Set up templates templates = Jinja2Templates(directory="templates") # Mount the upload directory as static files app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads") # Show upload form @app.get("/", response_class=HTMLResponse) async def upload_form(request: Request): return templates.TemplateResponse("upload_form.html", {"request": request}) # Save and display uploaded file @app.post("/upload", response_class=HTMLResponse) async def upload_file(request: Request, file: UploadFile = File(...)): upload_dir = "uploads" os.makedirs(upload_dir, exist_ok=True) file_path = os.path.join(upload_dir, file.filename) with open(file_path, "wb") as buffer: shutil.copyfileobj(file.file, buffer) if file.content_type.startswith("image/"): file_type = "image" elif file.content_type == "application/pdf": file_type = "pdf" else: file_type = "other" # After saving, display the file file_url = f"/uploads/{file.filename}" return templates.TemplateResponse( "upload_form.html", { "request": request, "file_url": file_url, "file_type": file_type } )
app/templates/upload_form.html
<!DOCTYPE html> <html> <head> <title>Upload demo</title> </head> <body> <h1>Upload demo</h1> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file" accept="application/pdf,image/*"> <button type="submit">Upload</button> </form> {% if file_url %} <h2>Uploaded File:</h2> <img src="{{ file_url }}" alt="Uploaded File" style="max-width: 300px;"> {% endif %} </body> </html>
3)サーバ起動および動作確認
uvicorn main:app --reload # main: ファイル名(main.py) # app: FastAPI のインスタンス名 # --reload: 自動リロード # ブラウザで以下にアクセスし、画像もしくはPDFをアップロードしてみる
【3】例2:REST server + Frontend
1)ファイル構成
├── backend │ ├── Dockerfile │ ├── main.py │ └── requirements.txt ├── frontend │ ├── Dockerfile │ └── index.html ├── docker-compose.yml
2)コード
backend/main.py
from asyncio.log import logger import os from fastapi import FastAPI, File, UploadFile from fastapi.middleware.cors import CORSMiddleware import shutil app = FastAPI() # CORS settings for Development app.add_middleware( CORSMiddleware, # Allow the frontend URL allow_origins=["http://localhost:3000"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) UPLOAD_DIR = "uploads" os.makedirs(UPLOAD_DIR, exist_ok=True) @app.post("/upload/") async def upload_file(upload_file: UploadFile = File(...)): try: # Save the uploaded file file_path = os.path.join(UPLOAD_DIR, upload_file.filename) with open(file_path, "wb") as file: file.write(await upload_file.read()) except Exception as ex: logger.error(f"File upload error: {ex}") return { "status": "error", "error": str(ex) } if upload_file.content_type.startswith("image/"): file_type = "image" elif upload_file.content_type == "application/pdf": file_type = "pdf" else: file_type = "other" return { "status": "success", "filename": upload_file.filename, "file_type": file_type }
frontend/index.html
<!DOCTYPE html>
<html>
<head>
<title>OCR Upload</title>
</head>
<body>
<h1>Upload Image or PDF</h1>
<input type="file" id="fileInput" />
<button onclick="uploadFile()">Upload</button>
<pre id="result"></pre>
<script>
async function uploadFile() {
const fileInput = document.getElementById("fileInput");
const file = fileInput.files[0];
const formData = new FormData();
formData.append("upload_file", file);
try {
const response = await fetch("http://localhost:8000/upload/", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const result = await response.json();
document.getElementById("result").innerText = JSON.stringify(result, null, 2);
} catch (error) {
console.error("Upload failed:", error);
alert("Upload failed. Check console for details.");
}
}
</script>
</body>
</html>
backend/Dockerfile
# backend/Dockerfile
FROM python:3.13-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr \
tesseract-ocr-jpn \
fonts-noto-cjk \
poppler-utils \
build-essential \
gcc \
libgl1 \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . /app
RUN mkdir -p /app/uploads
RUN pip install --no-cache-dir -r requirements.txt
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
backend/Dockerfile
FROM python:3.13-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# RUN pip install --no-cache-dir -r requirements.txt
WORKDIR /app
COPY . /app
CMD ["python3", "-m", "http.server", "3000"]
docker-compose.yml
services: backend: build: ./backend container_name: fastapi_app ports: - "8000:8000" frontend: build: ./frontend container_name: frontend_app ports: - "3000:3000" depends_on: - backend
3)サーバ起動および動作確認
docker compose up --build # docker compose down -v
にアクセスして、PDF/画像をアップロードしてみる
4)注意点
* 変数名は、一致させておく必要がある => サンプルで言うと「upload_file」
backend/main.py
async def upload_file(upload_file: UploadFile = File(...)):
frontend/index.html
formData.append("upload_file", file);
関連記事
FastAPI ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2025/10/05/014051
FastAPI ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2025/10/09/001303
Flask ~ SQLAlchemy / 入門編 ~
https://dk521123.hatenablog.com/entry/2018/09/19/223200
Flask ~ SQLAlchemy / 基本編 ~
https://dk521123.hatenablog.com/entry/2018/09/23/165130
Python ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2014/08/07/231242
Python ~ 基本編 / 文字列 ~
https://dk521123.hatenablog.com/entry/2019/10/12/075251
Python ~ PyFPDF ~
https://dk521123.hatenablog.com/entry/2023/07/19/001703
Python 〜 PDF to TEXT 〜
https://dk521123.hatenablog.com/entry/2025/10/04/214922
Python ~ 画像処理 / Pillow ~
https://dk521123.hatenablog.com/entry/2023/07/10/000000
Python 〜 Tesseract OCR 〜
https://dk521123.hatenablog.com/entry/2025/10/03/141326