画像保存の変遷:S3からDB直接保存(Binary)へ

画像の扱いについて、当初のS3利用からDB保存へ移行し、さらにその実装方式を最適化しました。これまでの3つの変遷をまとめます。

1. 初期:AWS S3 ストレージ

外部ストレージ(S3)に実体を保存し、DBにはパス(URL)のみを保存する一般的な構成です。

2. 変更後:DB保存(Base64方式)

S3をやめ、DBに直接データを保存するように変更しました。

まずは画像を文字列化して扱う実装を採用しました。

  • 保存先: データベース
  • 形式: Base64文字列(TEXT型)
[ブラウザ]
画像ファイル選択
    ↓
FileReader.readAsDataURL() // ← Base64に変換
    ↓
"data:image/png;base64,iVBORw0KGgo..." (文字列)
    ↓
JSON { avatar: "iVBORw0KGgo..." }
    ↓
[API] Laravel
    ↓
DB TEXT型 に文字列保存

3. 現在:DB保存(Buffer/Binary方式)

同じDB保存ですが、文字列変換をやめ、バイナリデータのまま扱うことで容量と処理を最適化しました。

  • 保存先: データベース
  • 形式: バイナリ(BLOB型)
[ブラウザ] 画像ファイル選択
 ↓ 
FormData.append(file)
 ↓ 
multipart/form-data で送信
 ↓ 
[サーバー内部 /tmp] PHPが一時ファイルを自動作成
 ↓ 
[API] Laravel $request->file('avatar')
 ↓ 
file_get_contents() // 一時ファイルをメモリ(Buffer)へ読み込み
 ↓ 
DB BLOB型 にバイナリ保存
[ブラウザ] 画像URLへアクセス(例: <img src="/api/avatar/1">)
 ↓ 
GETリクエスト
 ↓ 
[API] Laravel DBからBLOBデータを取得
 ↓ 
ヘッダー設定 (Content-Type: image/png)
 ↓ 
バイナリデータをそのままレスポンス
 ↓ 
[ブラウザ] 画像として描画

Blobとは

Binary Large OBject(バイナリ・ラージ・オブジェクト)の略です。

目次

参考記事

GitHub
laravel-rds/app/Http/Controllers/Api/UserController.php at main · idw-coder/laravel-rds Contribute to idw-coder/laravel-rds development by creating an account on GitHub.
Qiita
Node.jsで画像アップロードを受けつけるサーバー - Qiita カメラ撮影したものをサーバーにアップロードする仕組みを作っています。 クライアント側コードはまた別途 今回はサーバー側のミニマム構成っぽいものを書いてみたのでメモ...

画像アップロードエラー

【事実】

成功するケース

  • 小さい画像(推測: 1-2MB以下)
  • OPTIONS → Access-Control-Request-Headers: authorization,content-type
  • OPTIONS Response → CORSヘッダーあり
  • POST → 200 OK

失敗するケース

  • 大きい画像(推測: 2MB以上?具体的なサイズ不明)
  • OPTIONS → Access-Control-Request-Headers: authorization のみ
  • OPTIONS Response → 空(CORSヘッダーなし)
  • POST → 送信されない
  • エラー: Unexpected token '<', "<!-- Malfo"... is not valid JSON

【環境設定】

PHP

  • upload_max_filesize: 100M
  • post_max_size: 100M

Laravel

  • バリデーション: max:5120 (5MB) ✅
  • ルート: POST /api/profile
  • CORS: HandleCors ミドルウェア使用

フロントエンド

  • サイズチェック: コメントアウト(無効)
  • FormData使用 ✅
  • fetchWithAuth: FormData時はContent-Typeを設定しない ✅

【試したこと】

  1. ✅ キャッシュクリア (route:clear, config:clear, etc)
  2. ✅ バリデーションサイズ上限を2MB→5MBに変更
  3. ✅ PHP設定確認(問題なし)
  4. ❌ nginx設定確認(未実施)
  5. ❌ 具体的な境界値の特定(何MBで失敗するか未確認)

【原因として考えられること】

仮説1: ブラウザ側の問題

  • 大きいファイルの場合、ブラウザがOPTIONSリクエストでcontent-typeを含めない
  • 理由不明(ブラウザのバグ?)

仮説2: Laravel HandleCorsの不安定性

  • Access-Control-Request-Headerscontent-typeがない場合、正しくレスポンスしない
  • 小さいファイルでは偶然動いている

仮説3: nginx/タイムアウト

  • 未確認(可能性は低い、OPTIONSが失敗しているため)

【次のステップ候補】

  1. 境界値の特定 – 何MBで失敗するか正確に測定
  2. nginx設定確認client_max_body_sizeなど
  3. fruitcake/laravel-cors導入 – 安定したCORS処理
  4. 別ブラウザでテスト – Chrome以外(Firefox/Safari)で同じ現象か確認
目次