34.

LaravelにおけるajaxでPOST通信する際の注意点

編集
この記事の要点
  • Ajax で POST 通信する際の主要な注意点 5 つ
  • CSRF トークン: Laravel など多くの FW でヘッダ送信必須
  • Content-Type: JSON か form-data かで指定が異なる
  • CORS: クロスオリジンでは事前設定必要
  • エラーハンドリング: ネットワーク失敗・HTTP エラーを区別
  • 非同期処理: Promise / async-await で適切に待つ

 

fetch API での基本

// JSON 送信
async function postJson(data) {
    const response = await fetch("/api/users", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content,
            "Accept": "application/json"
        },
        body: JSON.stringify(data)
    });

    if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
    }

    return await response.json();
}

// 使用
postJson({ name: "Alice", email: "alice@example.com" })
    .then(result => console.log(result))
    .catch(error => console.error(error));

FormData(ファイル送信)

async function uploadFile(file) {
    const formData = new FormData();
    formData.append("file", file);
    formData.append("description", "ファイル説明");

    const response = await fetch("/api/upload", {
        method: "POST",
        headers: {
            "X-CSRF-TOKEN": csrfToken
            // ⚠️ Content-Type は指定しない (FormData が自動でセット)
        },
        body: formData
    });

    return await response.json();
}

// HTML


document.getElementById("fileInput").addEventListener("change", (e) => {
    uploadFile(e.target.files[0]);
});

axios での書き方

// 基本的な設定
axios.defaults.headers.common["X-CSRF-TOKEN"] =
    document.querySelector('meta[name="csrf-token"]').content;
axios.defaults.headers.common["Accept"] = "application/json";

// POST (JSON)
try {
    const response = await axios.post("/api/users", {
        name: "Alice",
        email: "alice@example.com"
    });
    console.log(response.data);
} catch (error) {
    if (error.response) {
        // サーバ応答 (4xx, 5xx)
        console.error("Status:", error.response.status);
        console.error("Data:", error.response.data);
    } else if (error.request) {
        // ネットワークエラー
        console.error("No response");
    } else {
        console.error("Error:", error.message);
    }
}

// FormData (ファイル含む)
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await axios.post("/api/upload", formData);
// axios も Content-Type を自動セット

jQuery での書き方

// $.ajax
$.ajaxSetup({
    headers: {
        "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
    }
});

// JSON 送信
$.ajax({
    url: "/api/users",
    method: "POST",
    contentType: "application/json",
    data: JSON.stringify({ name: "Alice", email: "alice@example.com" }),
    dataType: "json",
    success: (data) => console.log(data),
    error: (xhr, status, error) => console.error(error)
});

// $.post (簡略形)
$.post("/api/users", { name: "Alice" })
    .done(data => console.log(data))
    .fail((xhr) => console.error(xhr.statusText));

CSRF トークン送信

Laravel

// HTML head に meta タグ


// JS で読み取り
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

// fetch
fetch("/api/users", {
    method: "POST",
    headers: { "X-CSRF-TOKEN": csrfToken, "Content-Type": "application/json" },
    body: JSON.stringify(data)
});

// axios はメタタグから自動読込

Spring Security

// thymeleaf テンプレート



// JS
const csrfToken = document.querySelector('meta[name="_csrf"]').content;
const csrfHeader = document.querySelector('meta[name="_csrf_header"]').content;

fetch("/api/users", {
    method: "POST",
    headers: { [csrfHeader]: csrfToken, "Content-Type": "application/json" },
    body: JSON.stringify(data)
});

CORS (クロスオリジン)

// 別ドメインへの POST
fetch("https://api.example.com/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
    credentials: "include"  // Cookie 送信
});

// サーバ側 (Laravel)
// config/cors.php
'paths' => ['api/*'],
'allowed_origins' => ['https://myapp.example.com'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'allowed_headers' => ['*'],
'supports_credentials' => true,

// Spring Boot
@CrossOrigin(origins = "https://myapp.example.com", allowCredentials = "true")
@RestController
public class UserController { }

エラーハンドリング

// fetch では HTTP エラーは reject しない
async function postUser(data) {
    try {
        const response = await fetch("/api/users", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(data)
        });

        // HTTP エラー判定 (自動では throw しない)
        if (!response.ok) {
            const errorData = await response.json().catch(() => ({}));
            throw new Error(errorData.message || `HTTP ${response.status}`);
        }

        return await response.json();

    } catch (error) {
        if (error.name === "TypeError" && error.message.includes("Failed to fetch")) {
            // ネットワークエラー
            alert("ネットワークエラー: 接続を確認してください");
        } else {
            // HTTP エラー or その他
            alert(`エラー: ${error.message}`);
        }
        throw error;
    }
}

タイムアウト設定

// fetch + AbortController
async function postWithTimeout(url, data, timeoutMs = 10000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

    try {
        const response = await fetch(url, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(data),
            signal: controller.signal
        });
        clearTimeout(timeoutId);
        return await response.json();
    } catch (error) {
        clearTimeout(timeoutId);
        if (error.name === "AbortError") {
            throw new Error("タイムアウト");
        }
        throw error;
    }
}

// axios
axios.post("/api/users", data, { timeout: 10000 })

リトライ機構

async function postWithRetry(url, data, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            const response = await fetch(url, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(data)
            });

            if (response.ok) return await response.json();

            // 5xx の場合のみリトライ (4xx はクライアントエラー、リトライ無意味)
            if (response.status < 500) {
                throw new Error(`HTTP ${response.status}`);
            }
        } catch (error) {
            if (i === maxRetries - 1) throw error;
            // 指数バックオフ
            await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
        }
    }
}

ローディング表示・二重送信防止

let isSubmitting = false;

document.getElementById("submit-btn").addEventListener("click", async () => {
    if (isSubmitting) return;  // 二重送信防止
    isSubmitting = true;

    const btn = document.getElementById("submit-btn");
    btn.disabled = true;
    btn.textContent = "送信中...";

    try {
        const result = await postUser({ name: "Alice" });
        alert("成功");
    } catch (error) {
        alert("エラー: " + error.message);
    } finally {
        isSubmitting = false;
        btn.disabled = false;
        btn.textContent = "送信";
    }
});

よくあるトラブル

  • 419 Page Expired: CSRF トークン不正
  • 422 Unprocessable Entity: バリデーションエラー
  • CORS エラー: 別ドメイン + サーバ側 CORS 未設定
  • Mixed Content: HTTPS ページから HTTP API
  • Content-Type 違い: JSON vs form-data の指定ミス
  • Cookie 送信されない: credentials: "include" 必要

関連記事

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. インストールと設定
  2. クイックスタート & チュートリアル(初心者向け)
  3. クイックスタート & チュートリアル(中級者向け)
  4. ルーティング
  5. Bladeテンプレート(ビュー/レイアウト)
  6. コントローラー
  7. マイグレーションとテーブル定義
  8. データベースの設定
  9. Eloquentモデル (ORM)
  10. SQLとクエリビルダー
  11. バリデーション
  12. .envファイルの設定値へのアクセス
  13. 動作環境による分岐処理
  14. configフォルダ配下の設定値へのアクセス
  15. assetヘルパーを利用したpublicフォルダへのアクセス
  16. storageフォルダへのアクセス
  17. アプリケーション名の変更
  18. メンテナンス
  19. ログイン画面(認証システム)の作成
  20. ログインの必須化
  21. ログインユーザー情報の取得
  22. ルートの認証化
  23. 本番サーバーへのデプロイ方法
  24. 多言語化
  25. csrf_field
  26. ファイルのダウンロード
  27. CSVのアップロードおよび読み込み(maatwebsite/excel)
  28. ページタイトルの設定
  29. コマンド一覧
  30. エラー一覧
  31. SQLの実行ログ出力方法
  32. キャッシュのクリア
  33. Selectの結果の最初もしくは最後に任意の値を追加する方法
  34. ajaxでPOST通信する際の注意点
  35. ソーシャルログインの実装
  36. セッション情報の確認
  37. ログイン、ユーザー登録、パスワードリセット後のリダイレクト先の変更方法
  38. redirectやreturn viewにメッセージを付与する方法
  39. クッキー(cookie)の設定と取得
  40. クラスの再読み込み
  41. csrfの有効時間を変更する方法
  42. ViewComposerを用いてviewに共通の値を付与する方法
  43. View::shareを用いて共通の値を各ビューに渡す方法
  44. ミドルウェアを用いた処理の共通化
  45. Middleware内でAuth::check()などを使用する方法
  46. Controller以外でリダイレクトする方法
  47. セッションの値の取得/保存/更新/削除
  48. $requestの値を変更する方法
  49. 常時SSL化
  50. ページング(ページネーション)をする方法
  51. vue.jsとの連携
  52. Vue.jsと連携するSPA実行環境構築
  53. .envの値をvue.jsで参照する方法
  54. vue.jsを本番環境にリリースする方法
  55. could not find driver(Windows, MySQL編)