0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ajax通信について

0
Last updated at Posted at 2026-02-23

Ajax通信とは

  • ウェブブラウザ上で動作するJavaScriptとXML(またはJSONなどの他のデータ形式)
    を用いて、ウェブページ全体の再読み込みを行うのではなく、サーバーと非同期に
    データを交換し、コンテンツの一部を動的に更新するプログラミング手法
  • 非同期とは、ある処理を開始した後、その処理の完了を待たずに
    次の処理を開始できる性質
  • 実現するには、JavaScriptのXMLHttpRequestクラスや、
    JQueryなどのフレームワークが用いられる
  • 現代のweb開発においては軽量でJavaScriptとの親和性が高い、
    JSON形式がデータ交換の主要な形式として用いられる
  • XMLHttpRequestオブジェクトは、JavaScriptからサーバーに対してHTTPリクエストを
    送信し、レスポンスを受け取るためのAPIである
  • ウェブページの構造をプログラムから操作するための仕組みであるDOMを利用して、
    サーバーから受け取ったデータに基づいてページの一部を更新する
  • 同期通信と非同期通信の違い
特徴 同期通信 非同期通信
処理のブロック サーバーからの応答を待つ間、ユーザー操作をブロックする サーバーからの応答を待つ間も、ユーザーは他の操作を継続できる
ユーザー体験 応答待ち時間が発生し、操作が遅く感じられる場合がある スムーズで応答性の高いユーザー体験を提供できる
効率性 クライアントが応答を待つ間、処理が停止するため、効率が低い場合がある クライアントは他の処理を実行できるため、効率的
主な用途 次の処理が直前のサーバー応答に依存する場合 UIの動的な更新、バックグラウンドでのデータ取得、インタラクティブな要素の実現に適している
画面遷移 ページ全体の再読み込みが発生する ページ全体を再読み込みせずに、一部のみを更新できる

Ajax通信の仕組み

Ajax通信は、クライアント(ウェブブラウザ)とサーバーの間で、
以下のような手順でデータがやり取りされている

  1. イベントの発生:
    ウェブページ上で、ユーザーによるボタンのクリックやフォームの入力で
    イベントが発生する

  2. XMLHttpRequestオブジェクトの生成:
    JavaScriptコードによって、サーバーとの通信を行うための
    XMLHttpRequestオブジェクトが生成される

  3. リクエストの設定
    作成されたXMLHttpRequestオブジェクトに対して、
    リクエストの種類(GETまたはPOST)、送信先のURL、
    そしてサーバーに送信されるデータなどが設定される

  4. リクエストの送信
    設定が完了したXMLHttpRequestオブジェクトを用いて、
    サーバーに対して非同期リクエストが送信される
    この際、ウェブページ全体の再読み込みは行われない。

  5. サーバーでの処理
    サーバーは受け取ったリクエストに基づいて、
    必要な情報の処理を行う

  6. レスポンスの送信
    サーバーは処理の結果をレスポンスとしてクライアントに返送する
    このレスポンスは多くの場合JSON形式でデータを含む。

  7. レスポンスの処理と画面の更新
    クライアント側では、サーバーから送られてきたレスポンスデータを受け取り、
    JavaScriptによって、DOMを操作してウェブページの一部分が更新される

Ajax通信のメリット・デメリット

メリット

  • 表示速度が速く、操作がスムーズ

  • サーバー負荷の軽減
    → 必要なデータのみをサーバーとやり取りするため、
     ページ全体を再読み込みするよりも通信量を大幅に減らせる

  • ネットワーク帯域幅の節約
    →データ転送量が少なくなるため、ネットワーク帯域幅を効率的に利用できる

  • フロントエンド(表示)とバックエンド(処理)の開発を分離しやすくなる 
    → それぞれの専門家が並行して作業を進めることができ、開発速度の向上につながる

デメリット

  • JavaScriptへの依存
    →ユーザーのブラウザでJavaScriptが無効になっている場合、
     Ajaxによる機能を利用できない

  • セキュリティ上のリスクがある
    →非同期での頻繫なデータ通信は、適切な対策が施されていない場合、
    セキュリティ上の脆弱性を生み出す可能性がある

  • SEO対策の難しさ
    →Ajaxによって動的に生成されるコンテンツは、
    検索エンジンが認識しにくい場合がある

Ajax通信は具体的にどういう時に使われるのか

  • 地図アプリの動的な地図表示
  • チャットアプリケーションのリアルタイムなメッセージ表示
  • ユーザーが検出キーワードを入力するにつれて、リアルタイムで検索候補を表示する機能

Ajax通信を避けた方がいい事例

  • JavaScriptが無効な環境への対応が必要な場合
    →JavaScriptを無効にしているユーザーが含まれる可能性がある場合、
    Ajaxに依存した機能は利用できない

  • 高度なセキュリティ対策が求められる機密性の高いデータを扱うとき
    →Ajax通信は、サーバーとの間で頻繁にデータのやり取りを行うため、
    セキュリティ対策が不十分な場合にはリスクが高まる
    クレジットカード情報や個人情報など、非常に機密性の高いデータを扱う場合には、
    従来のフォーム送信など、よりシンプルなセキュリティモデルの方が適している

  • 静的なコンテンツが中心でSEOを最優先とするウェブサイト
    →マーケティング用のランディングページや情報提供を主な目的とするウェブサイトなど
    検索結果の上位に表示されることが必要な場合、Ajaxによる動的なコンテンツの
    読み込みはSEO上の課題となる可能性がある

Laravel, JavaScriptを用いたAjax通信の活用事例

1. 概要

こちらのサイト( Laravelを使ってAjax通信を理解する )を真似して、
献立のメニューを入力して、結果を表示するようにしました。
JQueryライブラリを使わず、JavaScirptをそのまま使いました。

2. 大まかな処理の流れ

処理の流れ.drawio.png

3. 作成したコード

menu.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <title>Menu</title>
    </head>
    <body class="antialisased">
        <div>
            <h1>献立作成</h1>
            <br>
            <br>
            <form>
                @csrf
                主食:<br>
                <input name="staple">
                <br>
                主菜:<br>
                <input name="main">
                <br>
                副菜:<br>
                <input name="sub">
                <br>
                汁物:<br>
                <input name="soup">
                <br>
                飲み物:<br>
                <input name="drink">
                <br>
                デザート:<br>
                <input name="dessert">
                <br>

                <button type="button" id="create">作成</button>
            </form>
        </div>
        <div class="menu">
            <!-- ここにAjax通信で受け取った結果のHTMLを出力する -->
        </div>
        
        <script>
            const createButton = document.querySelector("#create");
            createButton.addEventListener('click', function() {

                //"input[name='staple']" : <input>タグの中で、name属性の値がstableである要素を取得して
                //.valueで入力された値を取得する
                const staple    = document.querySelector("input[name = 'staple']").value;
                const main      = document.querySelector("input[name = 'main']").value;
                const sub       = document.querySelector("input[name = 'sub']").value;
                const soup      = document.querySelector("input[name = 'soup']").value;
                const drink     = document.querySelector("input[name = 'drink']").value;
                const dessert   = document.querySelector("input[name = 'dessert']").value;
                

                // HTML内にある<meta>タグの中から、name属性が
                // csrf-tokenである要素を探し、その要素のcontent属性の値を取得する
                // .getAttribute('content'): HTML要素の属性の値を取得するためのメソッド
                // → JavaScriptからサーバーへAjaxリクエストをサーバーに送信するために使用される
                const csrfToken = document.querySelector("meta[name = 'csrf-token']").getAttribute('content');

                //postmenuに対してPOSTリクエストを送信する
                //web.phpよりPOSTリクエストのMenuControllerのpostMenuメソッドへ
                fetch('/postmenu', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRF-TOKEN': csrfToken
                    },

                    //fetch APIのbodyオプションで
                    //JSON形式の文字列としてデータを送信する
                    body: JSON.stringify({
                        staple: staple,
                        main: main,
                        sub: sub,
                        soup: soup,
                        drink: drink,
                        dessert: dessert,
                    }),
                })

              
                //この部分がないと、data.stapleがundefined
                //responseオブジェクトを受け取る
                .then(response => {
                    console.log("レスポンス:", response); // レスポンス全体をログ出力
                    if(!response.ok) {
                        throw new Error(`HTTP error! status:' ${response.status}`);
                    }
                    return response.json();
                })

                
                 //ここでパースされたJSONデータ(コントローラから返された$menu)を
                 //dataとして受け取る
                .then(data => {
                    const html = `<div>
                                        <h1>献立一覧</h1>
                                        <br>
                                        <h2>主食</h2>
                                        <h3>${data.staple}</h3>
                                        <br>
                                        <h2>主菜</h2>
                                        <h3>${data.main}</h3>
                                        <br>
                                        <h2>副菜</h2>
                                        <h3>${data.sub}</h3>
                                        <br>
                                        <h2>汁物</h2>
                                        <h3>${data.soup}</h3>
                                        <br>
                                        <h2>飲み物</h2>
                                        <h3>${data.drink}</h3>
                                        <br>
                                        <h2>デザート</h2>
                                        <h3>${data.dessert}</h3>
                                    </div>`;
                    //上記のHTMLを出力したい場所を取得する                
                    const menusContainer = document.querySelector('.menu');
                    

                    //.insertAdjacentHTML():指定された位置にHTML文字列を挿入する
                    //第一引数: 挿入位置を示す, 第二引数: 挿入するHTML文字列
                    //beforend: 要素の最後の子供の直後の位置(要素の内側)
                    menusContainer.insertAdjacentHTML('beforeend', html);
                
                })
            });
        </script>
    </body>
</html>
MenuController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Menu;

class MenuController extends Controller
{
    public function postMenu(Request $request) {
        $menu = new Menu();
        $menu->staple  = $request->input('staple');
        $menu->main    = $request->input('main');
        $menu->sub     = $request->input('sub');
        $menu->soup    = $request->input('soup');
        $menu->drink   = $request->input('drink');
        $menu->dessert = $request->input('dessert');
        $menu->save();

        //アプリケーション内で生成されたHTTPレスポンスを、
        //このリクエストを送信したクライアントに返す
        //今回だと、JavaScriptのfetch API
        return response()->json($menu);
    }
}

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\MenuController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/menu', function () {
    return view('menu');
});

Route::post('/postmenu', [MenuController::class, 'postMenu']);

4. 処理の流れ

(1) ユーザーが「作成」ボタンをクリックする

menu.blade.php
<button type="button" id="create">作成</button>

(2) JavaScript のイベントリスナーが起動:

menu.blade.php
const createButton = document.querySelector("#create");
createButton.addEventListener('click', function() {
    //この関数内の処理が実行される
}

(3) JavaScript が入力フォームの値と CSRF トークンを取得:

menu.blade.php
const staple  = document.querySelector("input[name = 'staple']").value;
const main    = document.querySelector("input[name = 'main']").value;
const sub     = document.querySelector("input[name = 'sub']").value;
const soup    = document.querySelector("input[name = 'soup']").value;
const drink   = document.querySelector("input[name = 'drink']").value;
const dessert = document.querySelector("input[name = 'dessert']").value;

const csrfToken = document.querySelector("meta[name = 'csrf-token']").getAttribute('content');

(4) fetch API で POST リクエストを送信 (JSON 形式のデータ):

menu.blade.php
fetch('/postmenu', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRF-TOKEN': csrfToken
        },
        
        body: JSON.stringify({
            staple: staple,
            main: main,
            sub: sub,
            soup: soup,
            drink: drink,
            dessert: dessert,
        }),
    })
  • method: 'POST':HTTP メソッドを POST に設定する
  • headers: リクエストヘッダーを設定する
  • 'Content-Type': 'application/json': リクエストボディの形式が JSON であることを、
    サーバーに伝える
  • body: 送信するデータ, JavaScript のオブジェクトを
    JSON.stringify() で JSON 形式の文字列に変換

(5) サーバー(Laravel)がリクエストを受け取り、コントローラーで処理:

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\MenuController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/menu', function () {
    return view('menu');
});

Route::post('/postmenu', [MenuController::class, 'postMenu']);
MenuController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Menu;

class MenuController extends Controller
{
    public function postMenu(Request $request) {
        $menu = new Menu();
        $menu->staple  = $request->input('staple');
        $menu->main    = $request->input('main');
        $menu->sub     = $request->input('sub');
        $menu->soup    = $request->input('soup');
        $menu->drink   = $request->input('drink');
        $menu->dessert = $request->input('dessert');
        $menu->save();

        return response()->json($menu);
    }
}
  • ルーティングで /postmenu への POST リクエストが
    MenuController の postMenu メソッドに紐付けられている
  • postMenu メソッドで、$request->input('キー名') を使って、
    JavaScript から送信された JSON データに対応する値を取得している
  • 取得した値で Menu モデルのインスタンスを作成し、$menu->save() でデータベースに保存する
  • return response()->json($menu); で、保存された $menu オブジェクトを
    JSON 形式にエンコードしたレスポンスを生成し、クライアントに返しています。

(6) JavaScript がレスポンスを受け取り、JSON データをパース:

menu.blade.php
 //この部分がないと、data.stapleがundefined
.then(response => {
    console.log("レスポンス:", response); // レスポンス全体をログ出力
    if(!response.ok) {
        throw new Error(`HTTP error! status:' ${response.status}`);
    }
    return response.json();
})
  • fetch の .then() ブロックで、サーバーからの response オブジェクトを受け取る
  • response.ok でレスポンスが成功したか(ステータスコードが 200 番台)を確認する
  • return response.json(); で、レスポンスボディを JSON として非同期的にパースし、その結果を次の .then() に渡す

(7) パースされた JSON データを使って HTML を生成し、表示:

menu.blade.php
.then(data => {
    const html = `<div>
                    <h1>献立一覧</h1>
                    <br>
                    <h2>主食</h2>
                    <h3>${data.staple}</h3>
                    <br>
                    <h2>主菜</h2>
                    <h3>${data.main}</h3>
                    <br>
                    <h2>副菜</h2>
                    <h3>${data.sub}</h3>
                    <br>
                    <h2>汁物</h2>
                    <h3>${data.soup}</h3>
                    <br>
                    <h2>飲み物</h2>
                    <h3>${data.drink}</h3>
                    <br>
                    <h2>デザート</h2>
                    <h3>${data.dessert}</h3>
                    </div>`;
    //上記のHTMLを出力したい場所を取得する                
    const menusContainer = document.querySelector('.menu');
                    

    //.insertAdjacentHTML():指定された位置にHTML文字列を挿入する
    //第一引数: 挿入位置を示す, 第二引数: 挿入するHTML文字列
    //beforend: 要素の最後の子供の直後の位置(要素の内側)
    menusContainer.insertAdjacentHTML('beforeend', html);
})
  • 次の .then() ブロックで、パースされた JSON データが data として渡される
    この data は、コントローラーから返された $menu オブジェクトに対応する JavaScript のオブジェクト
  • data.staple, data.main などのプロパティにアクセスして、HTML を生成
    生成した HTML を、クラスが .menu の div 要素の中に挿入し、Web ページに結果を表示する

発生したエラー

POST http://localhost:8000/postmenu 419 (unknown status)

原因

  • Javascriptからhttp://localhost:8000/postmenu へPOSTリクエストが送信されたが、
    サーバーからステータスコード 419 の応答があったことを示す
  • HTTPステータスコード419は、
    CSRF (Cross-Site Request Forgery)トークンが一致しないことを意味する

確認事項:

1. JavaScript側の確認
  • CSRFトークンの取得:

ブラウザの開発者ツールの「要素」タブで、
<head> セクション内に <meta name="csrf-token" content="...">というタグが存在し、
content 属性に正しいCSRFトークンが設定されているか確認する

  • リクエストヘッダーの確認:

ブラウザの開発者ツールの「ネットワーク」タブで、
/postmenu へのリクエストの詳細を確認し、
「ヘッダー」セクションに X-CSRF-TOKEN ヘッダーが存在し、
その値が タグの content 属性の値と一致しているか確認する
image.png

X-CSRF-TOKEN ヘッダーが存在し、
その値が タグの content 属性の値と一致しているか確認する

<meta name="csrf-token" content="{{ csrf_token() }}">{{ csrf_token() }}
一致させる

2. Laravelサーバー側での確認
  • ルート定義:

routes/web.php で /postmenu のルート定義を確認し、
POSTリクエストを受け付けるルートになっているかを確認する

3. 「@csrf」が書かれているか確認する

下記のサイト曰く、原因の多くは@csrfの書き忘れらしい
参考: Laravelで419エラーが発生してPOSTできないときの解決方法

解決方法

ファイル名がmenu.phpだったので、menu.blade.phpに変更
この修正を行うことで、@csrfはLaravelのBladeテンプレートエンジンによって
処理され、隠し入力フィールドとしてCSRFトークンが生成・挿入される

エラー内容:

 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'insertAdjacentHTML')

原因

スクリーンショット 2025-05-06 214645.png

menu.blade.php
.then(data => {
    const html = `<div>
                    <h1>献立一覧</h1>
                    <br>
                    <h2>主食</h2>
                    <h3>${data.staple}</h3>
                    <br>
                    <h2>主菜</h2>
                    <h3>${data.main}</h3>
                    <br>
                    <h2>副菜</h2>
                    <h3>${data.sub}</h3>
                    <br>
                    <h2>汁物</h2>
                    <h3>${data.soup}</h3>
                    <br>
                    <h2>飲み物</h2>
                    <h3>${data.drink}</h3>
                    <br>
                    <h2>デザート</h2>
                    <h3>${data.dessert}</h3>
                    </div>`;
  • then(data => ~)ブロックに渡されるdataがundefinedになっていたと推測できる
  • fetch API の then チェーン:
    fetch API は Promise を返す。.then() メソッドは、前の Promise が解決(成功)したときに呼び出されるコールバック関数を登録する
  • Promise チェーンのルールとして、何も返さない場合は、次の .then() には undefined が渡されます。=> 前のPromiseチェーンに原因がありそう?
  • 確認してみると、対応するPromiseチェーンが無かった

解決方法

  • 下記の記述を追加
menu.blade.php
.then(response => {
    console.log("レスポンス:", response); // レスポンス全体をログ出力
    if(!response.ok) {
        throw new Error(`HTTP error! status:' ${response.status}`);
    }
    return response.json();
})

参考文献:

[1]Ajaxとは?HTTP通信の基本からAjaxの利用法まで解説!
[2]Ajaxとはどういうもの?基本情報から仕組みまで詳しく解説
[3] Ajaxとは?特徴や使用例を知り、自社サービスに応用しよう
[4]Ajaxを解説!javascriptで非同期通信
[5] Laravelを使ってAjax通信を理解する

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?