REST API — základní principy
REST (Representational State Transfer) je architektonický styl pro webová API. Definuje, jak klient (prohlížeč) komunikuje se serverem přes HTTP.
Zdroje (Resources)
Vše je zdroj identifikovaný URL: /users, /users/42, /users/42/orders. URL jsou podstatná jména, ne slovesa.
HTTP metody jako slovesa
GET = číst, POST = vytvořit, PUT/PATCH = aktualizovat, DELETE = smazat. Metoda říká, co s zdrojem udělat.
Bezstavovost (Stateless)
Každý požadavek obsahuje vše potřebné — server si nepamatuje předchozí požadavky. Autentizace = token v každém requestu.
JSON jako formát dat
Dnes prakticky vždy JSON. Hlavička Content-Type: application/json informuje server o formátu těla požadavku.
https://jsonplaceholder.typicode.com — bezplatné fake REST API pro testování. Podporuje GET, POST, PUT, PATCH, DELETE (změny se simulují, ale neukládají).HTTP metody & status kódy
| Metoda | URL příklad | Akce | Tělo requestu |
|---|---|---|---|
| GET | /users nebo /users/1 | Načíst seznam nebo detail | Žádné |
| POST | /users | Vytvořit nový záznam | JSON s daty |
| PUT | /users/1 | Nahradit celý záznam | Kompletní JSON |
| PATCH | /users/1 | Aktualizovat část záznamu | Jen změněná pole |
| DELETE | /users/1 | Smazat záznam | Žádné |
Nejdůležitější HTTP status kódy
| Kód | Název | Kdy nastane |
|---|---|---|
200 OK | Úspěch | GET, PUT, PATCH úspěšný |
201 Created | Vytvořeno | POST úspěšně vytvořil záznam |
204 No Content | Bez obsahu | DELETE úspěšný (žádná data zpět) |
400 Bad Request | Špatný požadavek | Neplatná data v těle requestu |
401 Unauthorized | Neautorizován | Chybí nebo neplatný token |
403 Forbidden | Zakázáno | Přístup odepřen (i s tokenem) |
404 Not Found | Nenalezeno | Zdroj neexistuje |
422 Unprocessable | Nelze zpracovat | Validační chyba na serveru |
500 Server Error | Serverová chyba | Chyba na straně serveru |
🧠 Kvíz: Chcete aktualizovat pouze pole email uživatele s ID 5. Jakou HTTP metodu použijete?
Architektura projektu
Než napíšeme první řádek kódu, navrhujeme strukturu. Dobře navržená aplikace je snáze rozšiřitelná a testovatelná.
// Vrstvová architektura pro jednoduchý SPA
//
// UI vrstva (DOM, events, rendering)
// ↕
// Business logika (validace, transformace dat)
// ↕
// API vrstva (fetch, error handling, auth)
// ↕
// REST API (server)
// Struktura souborů (vanilla JS projekt)
src/
api/
client.js // base fetch wrapper
contacts.js // contacts CRUD metody
components/
ContactCard.js // karta kontaktu
ContactForm.js // formulář pro přidání/editaci
Toast.js // notifikace
utils/
validate.js // validační funkce
format.js // formátování dat
app.js // hlavní logika
index.html
API wrapper — base client
// api/client.js — centrální místo pro všechny API volání
const BASE_URL = 'https://jsonplaceholder.typicode.com';
async function request(endpoint, options = {}) {
const url = `${BASE_URL}${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
// 'Authorization': `Bearer ${getToken()}`,
...options.headers,
},
...options,
};
try {
const response = await fetch(url, config);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${response.status}`);
}
// 204 No Content nemá tělo
if (response.status === 204) return null;
return response.json();
} catch (error) {
console.error(`API Error [${options.method || 'GET'} ${endpoint}]:`, error);
throw error;
}
}
// Exportované helperové funkce
export const api = {
get: (url) => request(url),
post: (url, data) => request(url, { method: 'POST', body: JSON.stringify(data) }),
put: (url, data) => request(url, { method: 'PUT', body: JSON.stringify(data) }),
patch: (url, data) => request(url, { method: 'PATCH', body: JSON.stringify(data) }),
delete: (url) => request(url, { method: 'DELETE' }),
};
CRUD operace v praxi
Create, Read, Update, Delete — čtyři základní operace každé datové aplikace. Ukážeme je na příkladu správce příspěvků:
// api/posts.js — CRUD pro příspěvky
const BASE = 'https://jsonplaceholder.typicode.com';
// READ — seznam
export async function getPosts(limit = 10) {
const res = await fetch(`${BASE}/posts?_limit=${limit}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// READ — detail
export async function getPost(id) {
const res = await fetch(`${BASE}/posts/${id}`);
if (!res.ok) throw new Error(res.status === 404 ? 'Nenalezeno' : `HTTP ${res.status}`);
return res.json();
}
// CREATE
export async function createPost(data) {
const res = await fetch(`${BASE}/posts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json(); // vrátí vytvořený objekt s id
}
// UPDATE (kompletní)
export async function updatePost(id, data) {
const res = await fetch(`${BASE}/posts/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// UPDATE (částečný)
export async function patchPost(id, fields) {
const res = await fetch(`${BASE}/posts/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(fields),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// DELETE
export async function deletePost(id) {
const res = await fetch(`${BASE}/posts/${id}`, { method: 'DELETE' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return true;
}
🧠 Kvíz: REST API vrátí status 201 Created. Co to typicky znamená pro klienta?
JavaScript komponenty bez frameworku
Frameworky (React, Vue) přinášejí komponentový model — ale základní principy lze implementovat i ve vanilla JS. Komponenta = funkce, která přijme data a vrátí HTML string nebo DOM element.
// Funkcionální komponenta — vrátí HTML string
function PostCard({ id, title, body, onDelete, onEdit }) {
return `
<article class="card" data-id="${id}">
<h3 class="card-title">${escapeHtml(title)}</h3>
<p class="card-body">${escapeHtml(body.substring(0, 100))}...</p>
<div class="card-actions">
<button class="btn-edit" data-id="${id}">✏️ Upravit</button>
<button class="btn-delete" data-id="${id}">🗑️ Smazat</button>
</div>
</article>
`;
}
// XSS ochrana — VŽDY escapujte user input!
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
// Render seznam komponent
function renderPosts(posts, container) {
container.innerHTML = posts.map(post => PostCard(post)).join('');
}
// Toast notifikace jako komponenta
function showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
document.body.appendChild(toast);
requestAnimationFrame(() => toast.classList.add('visible'));
setTimeout(() => {
toast.classList.remove('visible');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
Kompletní CRUD aplikace
Spojíme vše dohromady — správce příspěvků s načítáním, přidáváním, editací a mazáním. Volá skutečné JSONPlaceholder API.
🧠 Kvíz: Proč escapujeme HTML při renderování dat z API (escapeHtml())?
Praktické cvičení — Správce uživatelů
Rozšiřte aplikaci z kapitoly 6 — místo příspěvků spravujte uživatele z endpointu /users. Zobrazte: jméno, email, telefon, město. Přidejte filtrování podle jména nebo emailu.
https://jsonplaceholder.typicode.com/users, komponenta UserCard({name, email, phone, address}), filtr podle name i email, PATCH pro aktualizaci emailu, DELETE pro odebrání.👤 Správce uživatelů
Odznak: REST API Architekt
Zvládáte CRUD operace, komponentový přístup a REST API principy.
Taháček — REST API klient
CRUD šablona
const BASE = 'https://api.example.com';
const api = {
get: url => fetch(`${BASE}${url}`).then(r => r.ok ? r.json() : Promise.reject(r.status)),
post: (url, data) => fetch(`${BASE}${url}`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data) }).then(r => r.ok ? r.json() : Promise.reject(r.status)),
patch: (url, data) => fetch(`${BASE}${url}`, { method:'PATCH', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data) }).then(r => r.ok ? r.json() : Promise.reject(r.status)),
delete: url => fetch(`${BASE}${url}`, { method:'DELETE' }).then(r => { if (!r.ok) throw r.status; }),
};
Render pattern
// Komponenta = funkce → HTML string
const Card = ({id, title}) =>
`<div class="card" data-id="${id}"><h3>${title}</h3></div>`;
// Render
container.innerHTML = items.map(Card).join('');
// Events — vždy delegace na rodiče
container.addEventListener('click', e => {
const id = e.target.closest('[data-id]')?.dataset.id;
if (!id) return;
// zpracování...
});