Ajax通信とは
- ウェブブラウザ上で動作するJavaScriptとXML(またはJSONなどの他のデータ形式)
を用いて、ウェブページ全体の再読み込みを行うのではなく、サーバーと非同期に
データを交換し、コンテンツの一部を動的に更新するプログラミング手法 - 非同期とは、ある処理を開始した後、その処理の完了を待たずに
次の処理を開始できる性質 - 実現するには、JavaScriptのXMLHttpRequestクラスや、
JQueryなどのフレームワークが用いられる - 現代のweb開発においては軽量でJavaScriptとの親和性が高い、
JSON形式がデータ交換の主要な形式として用いられる - XMLHttpRequestオブジェクトは、JavaScriptからサーバーに対してHTTPリクエストを
送信し、レスポンスを受け取るためのAPIである - ウェブページの構造をプログラムから操作するための仕組みであるDOMを利用して、
サーバーから受け取ったデータに基づいてページの一部を更新する - 同期通信と非同期通信の違い
| 特徴 | 同期通信 | 非同期通信 |
|---|---|---|
| 処理のブロック | サーバーからの応答を待つ間、ユーザー操作をブロックする | サーバーからの応答を待つ間も、ユーザーは他の操作を継続できる |
| ユーザー体験 | 応答待ち時間が発生し、操作が遅く感じられる場合がある | スムーズで応答性の高いユーザー体験を提供できる |
| 効率性 | クライアントが応答を待つ間、処理が停止するため、効率が低い場合がある | クライアントは他の処理を実行できるため、効率的 |
| 主な用途 | 次の処理が直前のサーバー応答に依存する場合 | UIの動的な更新、バックグラウンドでのデータ取得、インタラクティブな要素の実現に適している |
| 画面遷移 | ページ全体の再読み込みが発生する | ページ全体を再読み込みせずに、一部のみを更新できる |
Ajax通信の仕組み
Ajax通信は、クライアント(ウェブブラウザ)とサーバーの間で、
以下のような手順でデータがやり取りされている
-
イベントの発生:
ウェブページ上で、ユーザーによるボタンのクリックやフォームの入力で
イベントが発生する -
XMLHttpRequestオブジェクトの生成:
JavaScriptコードによって、サーバーとの通信を行うための
XMLHttpRequestオブジェクトが生成される -
リクエストの設定
作成されたXMLHttpRequestオブジェクトに対して、
リクエストの種類(GETまたはPOST)、送信先のURL、
そしてサーバーに送信されるデータなどが設定される -
リクエストの送信
設定が完了したXMLHttpRequestオブジェクトを用いて、
サーバーに対して非同期リクエストが送信される
この際、ウェブページ全体の再読み込みは行われない。 -
サーバーでの処理
サーバーは受け取ったリクエストに基づいて、
必要な情報の処理を行う -
レスポンスの送信
サーバーは処理の結果をレスポンスとしてクライアントに返送する
このレスポンスは多くの場合JSON形式でデータを含む。 -
レスポンスの処理と画面の更新
クライアント側では、サーバーから送られてきたレスポンスデータを受け取り、
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. 大まかな処理の流れ
3. 作成したコード
<!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>
<?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);
}
}
<?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) ユーザーが「作成」ボタンをクリックする
<button type="button" id="create">作成</button>
(2) JavaScript のイベントリスナーが起動:
const createButton = document.querySelector("#create");
createButton.addEventListener('click', function() {
//この関数内の処理が実行される
}
(3) JavaScript が入力フォームの値と CSRF トークンを取得:
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 形式のデータ):
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)がリクエストを受け取り、コントローラーで処理:
<?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']);
<?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 データをパース:
//この部分がないと、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 を生成し、表示:
.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側の確認
ブラウザの開発者ツールの「要素」タブで、
<head> セクション内に <meta name="csrf-token" content="...">というタグが存在し、
content 属性に正しいCSRFトークンが設定されているか確認する
ブラウザの開発者ツールの「ネットワーク」タブで、
/postmenu へのリクエストの詳細を確認し、
「ヘッダー」セクションに X-CSRF-TOKEN ヘッダーが存在し、
その値が タグの content 属性の値と一致しているか確認する

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')
原因
.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チェーンが無かった
解決方法
- 下記の記述を追加
.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通信を理解する

