初めに
はじめまして!
株式会社iimonでフロントエンドエンジニアをしている林です。
普段は、弊社プロダクトの拡張アプリの開発をしています。
本日はAjaxについて解説していきたいと思います。
Ajaxとは?
1. 概要
Ajaxとは、Asynchronous JavaScript + XMLの略であり、JavaScriptとXMLを使って非同期にサーバ間との通信を行う技術の事です。
従来のWebページではデータを追加取得して表示するには再レンダリングする必要がありましたが、Ajaxを使用することにより再レンダリングする必要がなく、サーバーから新しいデータを取得して既存のページに追加することが可能です。
名前の由来になっている通り、2005年頃に登場した初期のAjaxではXML形式のデータのやり取りが主流でしたが、その後、JSONの普及に伴い、現在ではJSON形式が主流になっています。
- 非同期処理とは
- ざっくり言うと複数の処理を同時に実行できる処理のことで、処理が完了するまで待たずに次の処理を実行できるため、データベースアクセスなど時間のかかる処理を行う際に別の処理をしながら、処理結果を待てます。
- XMLとは
- JSONとは
- JavaScript Object Notationの略で、軽量で扱いやすく、JavaScriptとの相性が良いため、Ajaxで広く使われるデータ形式の一つです。
2. Ajaxの使用例
- Googleマップ
- Twitter
- 新しいツイートをAjaxを使ってサーバーから取得し追加表示しています。
- YouTube
- 動画検索結果ページをスクロールすると、Ajaxを使用して新しい動画が自動的に読み込まれ、ページを再読み込みすることなく表示されます。
上記以外にも様々なところでAjaxは使われております。
3. 今回実装するアプリ
それでは下記のような構成の簡単なタスク管理ツールを作成して貰おうと思います。


図の通り、ブラウザからwebサーバー(webServer.js)にリクエストを送り、リクエストファイルがあれば、それを返して、それが無ければアプリ(taskWebApp.js)に転送します。アプリはリクエストpathが正しければJSONでtaskを作成して返します。
バックエンドはnode.jsで簡単に作ってますが、あくまでAjaxを体験して頂きたくて作成する為、DB部分の実装とserver部分の解説は割愛させて頂きます。
feachのajax処理によりUIが再レンダリングされずに更新するのを確認頂ければと思います。
ディレクトリの作成と移動
mkdir ajax-tasks && cd $_
必要ファイルの作成
touch {tasks.html,tasks.js,taskWebApp.js,webServer.js}
package.jsonの作成
- 色々聞かれるが全てenterでok
npm init
http-serverのインストール
npm install http-server
[tasks.html]
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>tasks</title>
</head>
<body>
<h1>タスク管理アプリ</h1>
<input id="task-title-input" type="text" />
<button id="task-add-button" disabled>追加</button>
<h2>タスク一覧</h2>
<table>
<thead>
<tr>
<th>タイトル</th>
<th>作成日時</th>
</tr>
</thead>
<tbody id="tasks-table-body"></tbody>
</table>
</body>
<script src="tasks.js"></script>
</html>
[tasks.js]
const tasksTableBody = document.getElementById('tasks-table-body')
const taskTitleInput = document.getElementById('task-title-input')
const taskAddBtn = document.getElementById('task-add-button')
const loadTasks = async () => {
tasksTableBody.innerHTML = ""
// ajax
const response = await fetch('/api/tasks')
const responseBody = await response.json()
const tasks = responseBody.tasks
tasks.forEach((task) => {
const titleTd = `<td>${task.title}</td>`;
const createdAtTd = `<td>${task.createdAt}</td>`;
const trElement = `<tr>${titleTd}${createdAtTd}</tr>`;
tasksTableBody.insertAdjacentHTML('beforeend', trElement);
})
}
const createTask = async () => {
const title = taskTitleInput.value
const dt = new Date();
const year = String(dt.getFullYear()).padStart( 2, '0')
const month = String(dt.getMonth() + 1 ).padStart( 2, '0')
const date = String(dt.getDate()).padStart( 2, '0')
const hours = String(dt.getHours()).padStart( 2, '0')
const minutes = String(dt.getMinutes()).padStart( 2, '0')
const getSeconds = String(dt.getSeconds()).padStart( 2, '0')
const createdAt = `${year}/${month}/${date}/${hours}:${minutes}:${getSeconds}`
const requestBody = {
title: title,
createdAt: createdAt
}
// ajax
await fetch('/api/tasks', {
method: 'POST',
body: JSON.stringify(requestBody)
})
await loadTasks()
taskTitleInput.value = ''
}
const main = async () => {
taskTitleInput.addEventListener('input', (event) => {
const inputValue = event.target.value
const isInvalidInput = inputValue.length < 1 || 30 < inputValue.length
taskAddBtn.disabled = isInvalidInput
})
taskAddBtn.addEventListener('click', createTask)
await loadTasks()
}
main()
[webServer.js]
const http = require('http')
const fs = require('fs')
const PORT = 3000
const fileExists = (path) => {
return fs.existsSync(`.${path}`) && !fs.statSync(`.${path}`).isDirectory()
}
// HTTPサーバーを作成
http
.createServer((request, response) => {
const method = request.method
const path = request.url
const requestFile = path.endsWith('/') ? path + 'tasks.html' : path
if (method === 'POST' || !fileExists(requestFile)) {
const requestOptions = {
method: method,
path: path,
headers: request.headers
}
// taskWebAppへのリクエスト作成
const taskWebAppRequest = http.request(
'http://localhost:8080',
requestOptions
)
// クライアントから送信されたリクエストのボディを、taskWebAppRequest に書き込み。
request.on('data', (data) => {
taskWebAppRequest.write(data)
})
// responseが来た際のハンドリング
taskWebAppRequest.on('response', (taskWebAppResponse) => {
Object.entries(taskWebAppResponse.headers).forEach((header) => {
response.setHeader(header[0], header[1])
})
response.writeHead(taskWebAppResponse.statusCode)
taskWebAppResponse.on('data', (data) => {
response.write(data)
})
taskWebAppResponse.on('end', () => {
response.end()
})
})
// リクエスト終了後のハンドリング
request.on('end', () => {
taskWebAppRequest.end()
})
return
}
// リクエストのファイルをレスポンス
const fileContent = fs.readFileSync(`.${requestFile}`)
response.writeHead(200)
response.write(fileContent)
response.end()
})
.listen(PORT, '127.0.0.1')
console.log(`Server started on port ${PORT}`)
[taskWebApp.js]
const http = require('http')
const PORT = 8080
const tasks = [
{
title: 'サンプルタスク',
createdAt: '2023/03/17/14:10:10'
}
]
// HTTPサーバーを作成
http
.createServer((request, response) => {
const method = request.method
const path = request.url
// リクエストによってハンドリング
if (path === '/api/tasks' && method === 'GET') {
response.writeHead(200)
const responseBodyJson = {
tasks: tasks
}
const responseBody = JSON.stringify(responseBodyJson)
response.write(responseBody)
response.end()
return
} else if (path === '/api/tasks' && method === 'POST') {
let requestBody = ''
request.on('data', (data) => {
requestBody += data
})
request.on('end', () => {
const requestBodyJson = JSON.parse(requestBody)
const title = requestBodyJson.title
const createdAt = requestBodyJson.createdAt
const task = {
title: title,
createdAt: createdAt
}
tasks.push(task)
response.writeHead(201)
response.end()
})
return
}
response.writeHead(404)
response.end()
return
})
.listen(PORT, '127.0.0.1')
console.log(`Server started on port ${PORT}`)
- ターミナルを2つ用意し、次のコマンドをそれぞれ実行し、サーバーを立ち上げ
node webServer.jsnode taskWebApp.js

まとめ
- AjaxはJSを始めた時に理解するのに詰まりやすいポイントです。少しでも理解するお役に立てたのであれば幸いです。
参考資料
https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9
https://www.youtube.com/watch?v=V89nh3UCBbM
https://hnavi.co.jp/knowledge/blog/ajax/#title1
https://zenn.dev/wkb/books/node-tutorial/viewer/todo_03
https://qiita.com/ritukiii/items/7f28554369d63eb373c3