Пошук уроків, статей та іншого контенту
Надішлете форми, JSON і файли через fetch(), зокрема використовуючи FormData та multipart/form-data.
Для надсилання даних із браузера зазвичай використовують fetch():
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
name: "Олена",
email: "olena@example.com"
})
});
if (!response.ok) {
throw new Error(`HTTP-помилка: ${response.status}`);
}
const result = await response.json();
console.log(result);У запиті важливі такі властивості:
method — HTTP-метод: GET, POST, PUT, PATCH, DELETE;
headers — заголовки запиту;
body — тіло запиту;
response.ok — true, якщо статус відповіді в діапазоні 200–299;
response.json() — читання JSON-відповіді;
response.text() — читання звичайного тексту;
response.blob() — отримання двійкових даних, наприклад файлу.
Метод fetch() відхиляє Promise через помилку мережі, але HTTP-статуси 400, 404 або 500 самі по собі не вважаються помилкою JavaScript. Тому статус потрібно перевіряти окремо.
Класичні HTML-форми часто надсилають дані у форматі application/x-www-form-urlencoded.
Наприклад:
<form id="login-form">
<label>
Email:
<input name="email" type="email" required>
</label>
<label>
Пароль:
<input name="password" type="password" required>
</label>
<button type="submit">Увійти</button>
</form>
<script>
const form = document.querySelector("#login-form");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const formData = new FormData(form);
const body = new URLSearchParams(formData);
try {
const response = await fetch("/api/login", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body
});
if (!response.ok) {
throw new Error(`Не вдалося виконати вхід: ${response.status}`);
}
const result = await response.json();
console.log("Успішний вхід:", result);
} catch (error) {
console.error("Помилка запиту:", error);
}
});
</script>FormData збирає значення полів форми, а URLSearchParams перетворює їх на формат:
email=olena%40example.com&password=secretТакий спосіб підходить для звичайних текстових полів, але не для надсилання файлів.
JSON зручно використовувати для API, коли клієнт і сервер обмінюються структурованими даними.
Об’єкт JavaScript потрібно перетворити на рядок за допомогою JSON.stringify():
const user = {
name: "Олена",
age: 28,
roles: ["editor", "author"]
};
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify(user)
});
if (!response.ok) {
throw new Error(`Сервер повернув статус ${response.status}`);
}
const createdUser = await response.json();
console.log(createdUser);На сервері тіло такого запиту потрібно розібрати як JSON. Саме заголовок Content-Type повідомляє серверу, як інтерпретувати тіло.
Метод response.json() також повертає Promise:
const response = await fetch("/api/products");
if (!response.ok) {
throw new Error("Не вдалося отримати список товарів");
}
const products = await response.json();
for (const product of products) {
console.log(product.name);
}Не можна викликати response.json() двічі для тієї самої відповіді. Тіло відповіді можна прочитати лише один раз:
const response = await fetch("/api/data");
const firstRead = await response.json();
// Повторне читання спричинить помилкуFormData призначений для представлення даних HTML-форми у форматі multipart/form-data.
Створити його можна двома способами.
const form = document.querySelector("#profile-form");
const formData = new FormData(form);До FormData потраплять поля, які:
мають атрибут name;
не є вимкненими через disabled;
належать до активної форми;
відповідають правилам HTML-форми.
Наприклад, поле без name не буде передане:
<input type="text" value="Це значення не буде надіслане">
<input name="title" type="text" value="Це значення буде надіслане">const formData = new FormData();
formData.append("title", "Моя стаття");
formData.append("published", "true");
formData.append("tags", "javascript");
formData.append("tags", "browser");Метод append() додає нове значення. Якщо ключ повторюється, усі значення зберігаються:
const formData = new FormData();
formData.append("color", "red");
formData.append("color", "blue");
console.log(formData.get("color")); // red
console.log(formData.getAll("color")); // ["red", "blue"]Для заміни існуючого значення використовуйте set():
formData.set("color", "green");
console.log(formData.getAll("color")); // ["green"]Переглянути вміст можна за допомогою entries():
for (const [key, value] of formData.entries()) {
console.log(key, value);
}Значенням може бути як рядок, так і об’єкт File або Blob.
HTML-форма для вибору файлу має містити input із типом file:
<form id="avatar-form">
<label>
Ім’я користувача:
<input name="username" type="text" required>
</label>
<label>
Аватар:
<input name="avatar" type="file" accept="image/*" required>
</label>
<button type="submit">Завантажити</button>
</form>Надіслати форму можна так:
const form = document.querySelector("#avatar-form");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const formData = new FormData(form);
try {
const response = await fetch("/api/profile/avatar", {
method: "POST",
body: formData
});
if (!response.ok) {
throw new Error(`Помилка завантаження: ${response.status}`);
}
const result = await response.json();
console.log("Файл завантажено:", result);
} catch (error) {
console.error(error);
}
});Під час надсилання FormData браузер автоматично встановлює заголовок на кшталт:
multipart/form-data; boundary=----WebKitFormBoundary...boundary розділяє окремі частини запиту. Якщо встановити заголовок вручну, можна втратити правильне значення boundary, і сервер не зможе розібрати запит.
Правильно:
fetch("/api/upload", {
method: "POST",
body: formData
});Неправильно:
fetch("/api/upload", {
method: "POST",
headers: {
"Content-Type": "multipart/form-data"
},
body: formData
});Наступний приклад демонструє:
перехоплення відправлення форми;
перевірку вибраного файлу;
створення FormData;
надсилання текстових полів і файлу;
обробку HTTP-помилок;
відображення повідомлення користувачу.
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<title>Завантаження файлу</title>
</head>
<body>
<form id="upload-form">
<label>
Назва документа:
<input name="title" type="text" required maxlength="100">
</label>
<br>
<label>
Опис:
<textarea name="description" required></textarea>
</label>
<br>
<label>
Файл:
<input
name="document"
type="file"
accept=".pdf,.txt"
required
>
</label>
<br>
<button type="submit">Надіслати</button>
</form>
<p id="status" role="status"></p>
<script>
const form = document.querySelector("#upload-form");
const statusElement = document.querySelector("#status");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const fileInput = form.elements.document;
const file = fileInput.files[0];
if (!file) {
statusElement.textContent = "Виберіть файл.";
return;
}
const maximumSize = 5 * 1024 * 1024;
if (file.size > maximumSize) {
statusElement.textContent = "Розмір файлу не може перевищувати 5 МБ.";
return;
}
const formData = new FormData(form);
statusElement.textContent = "Надсилання...";
try {
const response = await fetch("/api/documents", {
method: "POST",
body: formData
});
if (!response.ok) {
throw new Error(`Сервер повернув статус ${response.status}`);
}
const result = await response.json();
statusElement.textContent =
`Документ успішно завантажено. Ідентифікатор: ${result.id}`;
form.reset();
} catch (error) {
console.error(error);
statusElement.textContent =
"Не вдалося завантажити документ. Спробуйте ще раз.";
}
});
</script>
</body>
</html>Цей код очікує, що сервер має endpoint /api/documents, який приймає multipart/form-data і повертає JSON, наприклад:
{
"id": "document-123"
}Для вибору кількох файлів використовуйте атрибут multiple:
<input name="documents" type="file" multiple>Властивість files містить об’єкт FileList:
const input = document.querySelector('input[name="documents"]');
const formData = new FormData();
for (const file of input.files) {
formData.append("documents", file);
}
await fetch("/api/documents", {
method: "POST",
body: formData
});Якщо сервер очікує унікальні імена полів, можна додати індекс:
for (const [index, file] of [...input.files].entries()) {
formData.append(`documents[${index}]`, file);
}Формат імен полів залежить від серверного API. Важливо, щоб клієнт і сервер використовували однакову домовленість.
Іноді разом із файлами потрібно передати складний об’єкт. Простий варіант — додати JSON як рядок:
const formData = new FormData();
const metadata = {
title: "Звіт",
tags: ["важливе", "2026"]
};
formData.append("metadata", JSON.stringify(metadata));
formData.append("file", selectedFile);
await fetch("/api/reports", {
method: "POST",
body: formData
});На сервері поле metadata потрібно окремо розібрати через JSON.parse().
Також можна передати JSON як Blob із відповідним типом:
const metadataBlob = new Blob(
[JSON.stringify(metadata)],
{ type: "application/json" }
);
formData.append("metadata", metadataBlob);
formData.append("file", selectedFile);Такий підхід корисний, коли сервер розрізняє частини multipart-запиту за їхнім MIME-типом.
Файл не обов’язково має походити з input type="file". Його можна отримати з іншого запиту або створити з даних:
const text = "Вміст текстового файлу";
const file = new File(
[text],
"note.txt",
{ type: "text/plain" }
);
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/files", {
method: "POST",
body: formData
});
if (!response.ok) {
throw new Error("Не вдалося надіслати файл");
}Blob описує двійкові дані, а File є спеціалізованим видом Blob із назвою файлу та додатковими властивостями.
Якщо сервер повертає файл, замість response.json() використовуйте response.blob():
const response = await fetch("/api/reports/42/export");
if (!response.ok) {
throw new Error("Не вдалося завантажити звіт");
}
const blob = await response.blob();
const downloadUrl = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
link.download = "report.pdf";
link.click();
URL.revokeObjectURL(downloadUrl);URL.createObjectURL() створює тимчасову адресу для об’єкта Blob. Після завершення роботи з нею бажано викликати URL.revokeObjectURL().
Надсилання даних може завершитися кількома видами помилок:
користувач не вибрав файл;
файл перевищує допустимий розмір;
мережа недоступна;
сервер повернув помилку;
відповідь має неочікуваний формат;
браузер заблокував кросдоменний запит через CORS.
Зручно розділяти мережеві та HTTP-помилки:
async function sendJson(url, data) {
let response;
try {
response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify(data)
});
} catch (error) {
throw new Error("Мережа недоступна або запит заблоковано");
}
if (!response.ok) {
let message = `HTTP-помилка ${response.status}`;
try {
const errorBody = await response.json();
if (errorBody.message) {
message = errorBody.message;
}
} catch {
// Відповідь може не містити JSON
}
throw new Error(message);
}
return response.json();
}
sendJson("/api/comments", {
text: "Корисний матеріал!"
})
.then((result) => {
console.log("Коментар створено:", result);
})
.catch((error) => {
console.error(error.message);
});Для скасування довгого завантаження можна використати AbortController:
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, 10_000);
try {
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
signal: controller.signal
});
if (!response.ok) {
throw new Error(`Помилка сервера: ${response.status}`);
}
} catch (error) {
if (error.name === "AbortError") {
console.log("Запит скасовано через перевищення часу очікування");
} else {
console.error("Помилка запиту:", error);
}
} finally {
clearTimeout(timeoutId);
}Той самий контролер можна пов’язати з кнопкою «Скасувати», викликавши controller.abort() під час натискання.
Якщо сторінка та API розміщені на різних origin, браузер застосовує правила CORS.
Наприклад, ці адреси мають різні origin:
https://example.com
https://api.example.comСервер має дозволити запит за допомогою відповідних заголовків, зокрема:
Access-Control-Allow-OriginДля деяких запитів браузер спочатку надсилає попередній OPTIONS-запит. Це називається preflight-запитом. Він перевіряє, чи дозволяє сервер потрібний метод і заголовки.
Помилку CORS не можна надійно виправити додаванням заголовка на стороні браузера. Це налаштовується на сервері або через проксі під час розробки.
Перевірка на клієнті покращує зручність, але не забезпечує безпеку. Користувач може змінити або обійти JavaScript-код.
Сервер також повинен:
перевіряти реальний MIME-тип файлу;
обмежувати розмір файлу;
перевіряти розширення та вміст;
не довіряти оригінальному імені файлу;
генерувати безпечне ім’я для збереження;
забороняти виконання завантажених файлів;
перевіряти права доступу;
захищатися від шкідливого вмісту.
Атрибут accept лише підказує браузеру, які файли показувати у вибирачі:
<input type="file" accept="image/png,image/jpeg">Він не є серверною перевіркою.
Content-Type для FormDataНе встановлюйте multipart/form-data вручну. Браузер має додати boundary.
// Правильно
fetch("/api/upload", {
method: "POST",
body: formData
});Неправильно:
fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: {
name: "Олена"
}
});Правильно:
fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
name: "Олена"
})
});Поле без name не потрапляє до FormData форми:
<!-- Це поле не буде передане -->
<input type="text" value="Олена">
<!-- Це поле буде передане як name=Олена -->
<input name="name" type="text" value="Олена">Код нижче не вважає статус 500 помилкою:
const response = await fetch("/api/data");
console.log(response.status);Потрібно перевіряти response.ok:
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error(`HTTP-помилка: ${response.status}`);
}Не додавайте конфіденційні дані до query-параметрів:
/login?password=secretURL може потрапити в історію браузера, журнали сервера або аналітичні системи. Для таких даних використовуйте тіло запиту та HTTPS.
Під час реалізації надсилання форми зручно діяти так:
Додайте name усім полям, які потрібно передати.
Перехопіть подію submit.
Викличте event.preventDefault(), якщо не потрібне стандартне перезавантаження сторінки.
Виберіть формат даних:
URLSearchParams для простих текстових полів;
JSON для структурованих даних;
FormData для файлів або multipart-запитів.
Додайте відповідний Content-Type, якщо він не встановлюється браузером автоматично.
Перевірте дані до надсилання.
Перевірте response.ok.
Прочитайте відповідь відповідним методом.
Покажіть користувачу стан завантаження або помилку.
fetch() використовується для надсилання HTTP-запитів із браузера.
JSON надсилають через JSON.stringify() із заголовком Content-Type: application/json.
Дані звичайної форми можна перетворити на URLSearchParams.
FormData підходить для текстових полів, файлів і multipart-запитів.
Для FormData не потрібно вручну встановлювати Content-Type.
Файли доступні через властивість files елемента input.
response.ok потрібно перевіряти окремо, адже HTTP-помилки не відхиляють Promise автоматично.
Клієнтська перевірка файлів не замінює серверну.
Для кросдоменних запитів сервер має правильно налаштувати CORS.
Відповідь із JSON, текстом або файлом потрібно читати відповідно через json(), text() або blob().