FormDataの使い方 - JavaScriptでフォームデータを扱う基礎

JavaScriptのFormDataオブジェクトを使ったフォームデータの操作方法を初心者向けに解説。ファイルアップロードから複雑なフォーム処理まで、実用的なサンプルコードを紹介します。

Learning Next 運営
38 分で読めます

FormDataの使い方 - JavaScriptでフォームデータを扱う基礎

みなさん、JavaScriptでフォームデータを扱う際に「ファイルアップロードってどうやるの?」って困ったことはありませんか?

「複雑なデータ送信が難しい」 「ファイル送信の方法が分からない」

こんな悩みを感じたことはありませんか?

この記事では、JavaScriptのFormDataオブジェクトの基本的な使い方から実用的な応用例まで、初心者にも分かりやすく解説します。 現代的なWeb開発に欠かせない知識を一緒に身につけていきましょう。

FormDataって何のこと?

FormDataは、HTMLフォームのデータをJavaScriptで操作するためのWebAPIです。

簡単に言うと、フォームから送信するデータを簡単に扱えるようにしてくれる便利な機能です。 通常のテキストデータだけでなく、ファイルや画像なども一緒に送信できます。

// 基本的な使い方
const formData = new FormData();
formData.append('name', '田中太郎');
formData.append('email', 'tanaka@example.com');
console.log(formData.get('name')); // 田中太郎

この例では、FormDataオブジェクトを作成して、名前とメールアドレスを追加しています。

new FormData()でFormDataオブジェクトを作成します。

const formData = new FormData();

appendメソッドでデータを追加できます。

formData.append('name', '田中太郎');

getメソッドで追加したデータを取得できます。

FormDataの基本的な使い方

FormDataオブジェクトの作成方法

FormDataオブジェクトは2つの方法で作成できます。

// 空のFormDataを作成
const formData = new FormData();
// 既存のHTMLフォームからFormDataを作成
const form = document.getElementById('myForm');
const formDataFromForm = new FormData(form);

1つ目の方法は、空のFormDataオブジェクトを作成する方法です。

const formData = new FormData();

この後、手動でデータを追加していきます。

2つ目の方法は、既存のHTMLフォームからFormDataを作成する方法です。

const formDataFromForm = new FormData(form);

HTMLフォームの全ての入力値が自動的にFormDataに追加されます。

データの追加と操作方法

FormDataには様々なメソッドが用意されています。

const formData = new FormData();
// データを追加
formData.append('username', 'user123');
formData.append('email', 'user@example.com');
formData.append('age', 25);
// 複数の値を同じキーで追加
formData.append('hobby', 'プログラミング');
formData.append('hobby', '読書');
formData.append('hobby', '映画鑑賞');
// データを取得
console.log(formData.get('username')); // user123
console.log(formData.getAll('hobby')); // ['プログラミング', '読書', '映画鑑賞']
// データの存在確認
console.log(formData.has('email')); // true
// データを更新(既存の値を置き換え)
formData.set('age', 26);
// データを削除
formData.delete('age');

各メソッドについて詳しく説明します。

appendメソッドでデータを追加します。

formData.append('username', 'user123');

同じキーで複数回追加すると、配列のように複数の値を保持できます。

getメソッドで最初の値を取得します。

console.log(formData.get('username')); // user123

getAllメソッドで同じキーの全ての値を配列で取得します。

console.log(formData.getAll('hobby')); // ['プログラミング', '読書', '映画鑑賞']

FormDataの内容を確認しよう

FormDataの中身を確認する便利な関数を作ってみましょう。

function displayFormData(formData) {
console.log('FormData の内容:');
for (const [key, value] of formData.entries()) {
if (value instanceof File) {
console.log(`${key}: ファイル - ${value.name} (${value.size} bytes)`);
} else {
console.log(`${key}: ${value}`);
}
}
}
// 使用例
const formData = new FormData();
formData.append('name', '田中太郎');
formData.append('message', 'こんにちは');
displayFormData(formData);

この関数について詳しく説明します。

entries()メソッドでFormDataの全ての要素を取得できます。

for (const [key, value] of formData.entries()) {
// キーと値のペアを処理
}

ファイルかどうかを判定して、適切な情報を表示します。

if (value instanceof File) {
console.log(`${key}: ファイル - ${value.name} (${value.size} bytes)`);
} else {
console.log(`${key}: ${value}`);
}

ファイルアップロードを実装してみよう

基本的なファイルアップロード

ファイルアップロード機能を作ってみましょう。

<!DOCTYPE html>
<html>
<head>
<title>ファイルアップロード</title>
</head>
<body>
<form id="uploadForm">
<div>
<label>ファイルを選択:</label>
<input type="file" id="fileInput" accept="image/*" multiple>
</div>
<div>
<label>説明:</label>
<textarea id="description" placeholder="ファイルの説明を入力"></textarea>
</div>
<button type="submit">アップロード</button>
</form>
<script>
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData();
const fileInput = document.getElementById('fileInput');
const description = document.getElementById('description').value;
// 複数ファイルの追加
for (let i = 0; i < fileInput.files.length; i++) {
formData.append('files', fileInput.files[i]);
}
// 説明文を追加
formData.append('description', description);
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData // Content-Typeは自動設定される
});
if (response.ok) {
alert('アップロード成功!');
} else {
alert('アップロード失敗');
}
} catch (error) {
console.error('エラー:', error);
alert('ネットワークエラー');
}
});
</script>
</body>
</html>

このコードの流れを詳しく説明します。

まず、フォームの送信イベントをキャッチします。

document.getElementById('uploadForm').addEventListener('submit', async (e) => {
e.preventDefault(); // デフォルトの送信を防ぐ

preventDefault()でブラウザのデフォルト送信動作を止めます。

ファイル選択要素から選択されたファイルを取得します。

const fileInput = document.getElementById('fileInput');
for (let i = 0; i < fileInput.files.length; i++) {
formData.append('files', fileInput.files[i]);
}

複数のファイルが選択されている場合は、全てFormDataに追加します。

fetchを使ってサーバーに送信します。

const response = await fetch('/upload', {
method: 'POST',
body: formData // Content-Typeは自動設定される
});

重要: Content-Typeヘッダーは自動設定されるので、手動で設定する必要はありません。

プレビュー機能付きアップロード

より実用的なファイルアップロード機能を作ってみましょう。

class FileUploader {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.files = [];
this.init();
}
init() {
this.createUI();
this.attachEvents();
}
createUI() {
this.container.innerHTML = `
<div class="upload-area" id="uploadArea">
<input type="file" id="fileInput" multiple accept="image/*" style="display: none;">
<div class="upload-text">
<p>ファイルをドラッグ&ドロップまたはクリックして選択</p>
</div>
</div>
<div id="previewArea" class="preview-area"></div>
<button id="uploadBtn" class="upload-button" style="display: none;">アップロード</button>
`;
}
attachEvents() {
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn');
// クリックでファイル選択
uploadArea.addEventListener('click', () => fileInput.click());
// ファイル選択時の処理
fileInput.addEventListener('change', (e) => {
this.handleFiles(e.target.files);
});
// ドラッグ&ドロップ
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('drag-over');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('drag-over');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('drag-over');
this.handleFiles(e.dataTransfer.files);
});
// アップロードボタン
uploadBtn.addEventListener('click', () => this.uploadFiles());
}
handleFiles(fileList) {
this.files = Array.from(fileList);
this.createPreviews();
const uploadBtn = document.getElementById('uploadBtn');
uploadBtn.style.display = this.files.length > 0 ? 'block' : 'none';
}
createPreviews() {
const previewArea = document.getElementById('previewArea');
previewArea.innerHTML = '';
this.files.forEach((file, index) => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';
if (file.type.startsWith('image/')) {
const img = document.createElement('img');
img.src = URL.createObjectURL(file);
img.style.maxWidth = '200px';
img.style.maxHeight = '200px';
previewItem.appendChild(img);
}
const info = document.createElement('div');
info.innerHTML = `
<p><strong>${file.name}</strong></p>
<p>サイズ: ${(file.size / 1024).toFixed(2)} KB</p>
<button onclick="uploader.removeFile(${index})">削除</button>
`;
previewItem.appendChild(info);
previewArea.appendChild(previewItem);
});
}
async uploadFiles() {
if (this.files.length === 0) return;
const formData = new FormData();
this.files.forEach((file) => {
formData.append('files', file);
});
// 追加の情報
formData.append('uploadTime', new Date().toISOString());
formData.append('fileCount', this.files.length);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.json();
console.log('アップロード成功:', result);
this.files = [];
this.createPreviews();
alert('アップロードが完了しました!');
} else {
throw new Error('アップロードに失敗しました');
}
} catch (error) {
console.error('アップロードエラー:', error);
alert('アップロードに失敗しました');
}
}
}
// 使用例
const uploader = new FileUploader('file-uploader');

このクラスの主要な機能について説明します。

ドラッグ&ドロップでファイルを選択できます。

uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('drag-over');
this.handleFiles(e.dataTransfer.files);
});

画像ファイルの場合はプレビューを表示します。

if (file.type.startsWith('image/')) {
const img = document.createElement('img');
img.src = URL.createObjectURL(file);
}

URL.createObjectURL()で一時的なURLを作成してプレビューを表示します。

複雑なフォームデータの処理

ネストした構造のデータを扱う

複雑なデータ構造もFormDataで扱えます。

function createComplexFormData(userData) {
const formData = new FormData();
// 基本情報
formData.append('name', userData.name);
formData.append('email', userData.email);
// 配列データ
userData.hobbies.forEach((hobby, index) => {
formData.append(`hobbies[${index}]`, hobby);
});
// オブジェクトデータ
Object.entries(userData.address).forEach(([key, value]) => {
formData.append(`address[${key}]`, value);
});
// ファイルデータ
if (userData.profileImage) {
formData.append('profileImage', userData.profileImage);
}
return formData;
}
// 使用例
const userData = {
name: '田中太郎',
email: 'tanaka@example.com',
hobbies: ['プログラミング', '読書', '映画鑑賞'],
address: {
prefecture: '東京都',
city: '渋谷区',
zipCode: '150-0001'
},
profileImage: fileInput.files[0] // ファイルオブジェクト
};
const formData = createComplexFormData(userData);

この関数について詳しく説明します。

配列データは、インデックス付きでFormDataに追加します。

userData.hobbies.forEach((hobby, index) => {
formData.append(`hobbies[${index}]`, hobby);
});

オブジェクトデータは、キーを使ってFormDataに追加します。

Object.entries(userData.address).forEach(([key, value]) => {
formData.append(`address[${key}]`, value);
});

サーバー側でhobbies[0]address[prefecture]のような形でデータを受け取れます。

動的フォームの処理を自動化しよう

フォームの内容を自動的にFormDataに変換するクラスを作ってみましょう。

class DynamicFormHandler {
constructor(formId) {
this.form = document.getElementById(formId);
this.init();
}
init() {
this.form.addEventListener('submit', (e) => {
e.preventDefault();
this.handleSubmit();
});
}
async handleSubmit() {
const formData = this.gatherFormData();
await this.submitForm(formData);
}
gatherFormData() {
const formData = new FormData();
const formElements = this.form.elements;
for (let element of formElements) {
if (this.shouldIncludeElement(element)) {
this.addElementToFormData(formData, element);
}
}
return formData;
}
shouldIncludeElement(element) {
return element.name &&
!element.disabled &&
element.type !== 'submit' &&
element.type !== 'button';
}
addElementToFormData(formData, element) {
switch (element.type) {
case 'checkbox':
if (element.checked) {
formData.append(element.name, element.value);
}
break;
case 'radio':
if (element.checked) {
formData.append(element.name, element.value);
}
break;
case 'file':
for (let file of element.files) {
formData.append(element.name, file);
}
break;
case 'select-multiple':
for (let option of element.selectedOptions) {
formData.append(element.name, option.value);
}
break;
default:
formData.append(element.name, element.value);
}
}
async submitForm(formData) {
try {
// FormDataの内容をログ出力
console.log('送信データ:');
for (const [key, value] of formData.entries()) {
console.log(`${key}:`, value);
}
const response = await fetch(this.form.action || '/submit', {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.json();
this.handleSuccess(result);
} else {
this.handleError('サーバーエラーが発生しました');
}
} catch (error) {
console.error('送信エラー:', error);
this.handleError('ネットワークエラーが発生しました');
}
}
handleSuccess(result) {
alert('フォームの送信が完了しました!');
this.form.reset();
}
handleError(message) {
alert(`エラー: ${message}`);
}
}
// 使用例
const formHandler = new DynamicFormHandler('contact-form');

このクラスの特徴について説明します。

フォーム要素の種類に応じて適切にFormDataに追加します。

switch (element.type) {
case 'checkbox':
if (element.checked) {
formData.append(element.name, element.value);
}
break;
case 'file':
for (let file of element.files) {
formData.append(element.name, file);
}
break;
}

チェックボックスはチェックされている場合のみ、ファイル要素は全ての選択ファイルを追加します。

進捗表示付きアップロード

プログレスバーの実装

アップロード進捗を表示する機能を作ってみましょう。

async function uploadWithProgress(formData, progressCallback) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// アップロード進捗の監視
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
progressCallback(percentComplete);
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
});
xhr.addEventListener('error', () => {
reject(new Error('ネットワークエラー'));
});
xhr.open('POST', '/upload');
xhr.send(formData);
});
}
// 使用例
async function handleFileUpload() {
const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
if (fileInput.files.length === 0) {
alert('ファイルを選択してください');
return;
}
const formData = new FormData();
for (let file of fileInput.files) {
formData.append('files', file);
}
try {
const result = await uploadWithProgress(formData, (progress) => {
progressBar.style.width = `${progress}%`;
progressText.textContent = `${Math.round(progress)}%`;
});
console.log('アップロード完了:', result);
alert('アップロードが完了しました!');
} catch (error) {
console.error('アップロードエラー:', error);
alert('アップロードに失敗しました');
}
}

進捗表示の実装について詳しく説明します。

XMLHttpRequestのprogressイベントでアップロード進捗を監視します。

xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
progressCallback(percentComplete);
}
});

e.loadedが送信済みバイト数、e.totalが全体のバイト数です。

進捗に応じてプログレスバーを更新します。

progressBar.style.width = `${progress}%`;
progressText.textContent = `${Math.round(progress)}%`;

視覚的にアップロードの進捗をユーザーに伝えることができます。

よくある注意点とベストプラクティス

Content-Typeの自動設定

FormDataを使う場合、Content-Typeヘッダーの設定には注意が必要です。

// ❌ 間違い:手動でContent-Typeを設定
fetch('/upload', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data' // これは設定してはいけない
},
body: formData
});
// ✅ 正しい:Content-Typeは自動設定される
fetch('/upload', {
method: 'POST',
body: formData
});

重要: FormDataを使う場合、Content-Typeヘッダーは自動設定されます。 手動で設定すると、境界値(boundary)が正しく設定されずエラーになります。

ファイルサイズとセキュリティ

ファイルアップロード時は、セキュリティを考慮した検証が重要です。

function validateFileUpload(file) {
const MAX_SIZE = 10 * 1024 * 1024; // 10MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
if (file.size > MAX_SIZE) {
throw new Error('ファイルサイズが大きすぎます');
}
if (!ALLOWED_TYPES.includes(file.type)) {
throw new Error('許可されていないファイル形式です');
}
// ファイル名の検証
if (!/^[a-zA-Z0-9._-]+$/.test(file.name)) {
throw new Error('ファイル名に無効な文字が含まれています');
}
return true;
}

セキュリティ検証のポイントについて説明します。

ファイルサイズの制限を設定します。

if (file.size > MAX_SIZE) {
throw new Error('ファイルサイズが大きすぎます');
}

許可するファイル形式を限定します。

if (!ALLOWED_TYPES.includes(file.type)) {
throw new Error('許可されていないファイル形式です');
}

ファイル名に危険な文字が含まれていないかチェックします。

エラーハンドリングを充実させよう

堅牢なフォーム送信のためのエラーハンドリング例です。

async function robustFormSubmit(formData, url) {
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(url, {
method: 'POST',
body: formData
});
if (response.ok) {
return await response.json();
} else if (response.status >= 500) {
// サーバーエラーの場合はリトライ
throw new Error(`Server error: ${response.status}`);
} else {
// クライアントエラーの場合はリトライしない
const error = await response.text();
throw new Error(`Client error: ${response.status} - ${error}`);
}
} catch (error) {
attempt++;
if (attempt >= maxRetries) {
throw error;
}
// 指数バックオフでリトライ
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
}

エラーハンドリングの工夫について説明します。

サーバーエラー(500番台)の場合はリトライします。

if (response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}

クライアントエラー(400番台)の場合はリトライしません。

指数バックオフでリトライ間隔を調整します。

await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);

1回目は1秒、2回目は2秒、3回目は4秒待ってからリトライします。

まとめ

JavaScriptのFormDataオブジェクトについて、基礎から実践的な応用まで詳しく解説しました。

重要なポイント

  • FormDataはmultipart/form-data形式でのデータ送信に最適
  • ファイルアップロードには欠かせない機能
  • Content-Typeヘッダーは自動設定される
  • 適切なバリデーションとエラーハンドリングが重要

実践のコツ

  • ファイルサイズとファイル形式の検証を実装
  • プログレスバーでユーザビリティを向上
  • セキュリティを考慮したファイル処理
  • 適切なエラーハンドリングとリトライ機能

FormDataは現代のWeb開発において、ファイルアップロードや複雑なフォーム処理に欠かせない技術です!

適切に活用することで、ユーザーフレンドリーで堅牢なWebアプリケーションを構築できるようになります。 ぜひ今日から、FormDataを使ったファイルアップロード機能に挑戦してみてください。

関連記事