До того как начать кодить, разложим, что именно собираем.
Что должно получиться
Маленькое веб-приложение из одной страницы, в котором:
- сверху — форма с инпутом и кнопкой «Добавить»;
- под ней — список задач с чекбоксом «выполнено» и кнопкой удаления у каждой;
- под списком — небольшая статусная строка («Загружаю...», «Ошибка», «Нет задач»).
Список приходит с сервера. Добавление, удаление, переключение — через fetch.
Какие endpoint используем
| Что делаем | Метод | URL |
| Загрузить список | GET | https://jsonplaceholder.typicode.com/todos?_limit=10 |
| Создать задачу | POST | https://jsonplaceholder.typicode.com/todos |
| Переключить статус | PATCH | https://jsonplaceholder.typicode.com/todos/<id> |
| Удалить задачу | DELETE | https://jsonplaceholder.typicode.com/todos/<id> |
Разметка
Самая базовая. Никаких фреймворков, чистый HTML и одна функция-рендерер.
<div class="app">
<h1>Мои задачи</h1>
<form id="form">
<input id="input" placeholder="Новая задача..." required />
<button type="submit">Добавить</button>
</form>
<ul id="list"></ul>
<p id="status">Загружаю...</p>
</div>
Общая утилита для API
Чтобы не писать одну и ту же проверку response.ok в каждом запросе, вынесем обёртку (мы уже встречали этот паттерн в модуле 6):
const BASE = 'https://jsonplaceholder.typicode.com';
async function api(path, options = {}) {
const url = `${BASE}${path}`;
let response;
try {
response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...(options.headers || {}),
},
});
} catch {
throw new Error('Нет связи с сервером');
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
// 204 No Content (после DELETE) — тела нет, парсить нечего.
if (response.status === 204) return null;
return response.json();
}
Что покрывает эта обёртка:
- склейка с базовым URL — не повторяем https://jsonplaceholder... в каждом месте;
- дефолтный Content-Type: application/json — не забываем на POST/PATCH;
- трансформация сетевых ошибок в человеческое сообщение;
- проверка response.ok с throw;
- защита от 204 No Content — не падаем на парсинге пустого тела.
Со всем этим запросы дальше будут писаться короче — одна строка на каждый.
Состояние приложения
Для такого мини-проекта хватит одной переменной — массива задач:
let todos = [];
Загрузили с сервера — присвоили. Добавили — сделали push. Удалили — отфильтровали. После каждого изменения — зовём render(), который перерисовывает список из текущего todos.
В настоящем проекте такое состояние держат в React, Vue или сторе. Для учебной цели хватит модульной переменной.
В следующей главе пишем функцию loadTodos: один GET-запрос, который заполняет todos при загрузке страницы. Это самая лёгкая часть проекта — с неё и начнём.