Domů O mně Služby Portfolio Učím Blog Kontakt
← Zpět na učím

07.10 Fetch API

Výuka

Proč Fetch API?

Webové aplikace potřebují komunikovat se serverem - načítat data, odesílat formuláře, aktualizovat obsah bez nutnosti obnovit celou stránku. Dříve se k tomu používal XMLHttpRequest (XHR), který byl složitý a nepřehledný.

Fetch API je moderní, Promise-based náhrada za XMLHttpRequest. Je jednodušší, čitelnější a skvěle se kombinuje s async/await.

Představ si to jako: Kurýra, kterého pošleš na poštu. Řekneš mu adresu (URL), co má udělat (GET/POST), a on ti přinese odpověď. Nemusíš čekat na místě - dáš mu svůj telefon (callback/Promise) a on ti zavolá, až se vrátí.

Jak to funguje?

1. Zavoláš fetch(url, options) - odešle HTTP request
2. Dostaneš Promise<Response> - slib, že přijde odpověď
3. Response obsahuje: status, headers, body (stream)
4. Body musíš "přečíst" pomocí .json(), .text(), .blob() atd.
5. Toto čtení je TAKÉ asynchronní - vrací další Promise

Struktura HTTP požadavku

┌─────────────────────────────────────────┐
 HTTP Request                            
├─────────────────────────────────────────┤
 Method: GET / POST / PUT / DELETE       
 URL: https://api.example.com/users      │
 Headers: Content-Type, Authorization... 
 Body: (jen u POST/PUT) JSON, FormData...
└─────────────────────────────────────────┘
           
┌─────────────────────────────────────────┐
 HTTP Response                           
├─────────────────────────────────────────┤
 Status: 200, 404, 500...                
 Headers: Content-Type, Content-Length...
 Body: JSON, HTML, obrázek, soubor...    
└─────────────────────────────────────────┘

Klíčové koncepty

  • Request - co posíláš na server (URL, metoda, hlavičky, tělo)
  • Response - co server vrací (status, hlavičky, tělo)
  • Status code - číslo říkající úspěch/chybu (200 = OK, 404 = nenalezeno, 500 = chyba serveru)
  • Headers - metadata požadavku/odpovědi (typ obsahu, autorizace...)
  • Body - samotná data (JSON, text, binární...)

Důležité: Fetch NEVYHAZUJE chybu při 404 nebo 500!

Na rozdíl od mnoha knihoven, fetch vyhazuje chybu POUZE při síťové chybě (odpojení od internetu, CORS blokace). HTTP chyby (404, 500) jsou "úspěšné" odpovědi - musíš zkontrolovat response.ok nebo response.status!


JavaScript

// ==========================================
// 1. Základní GET request
// ==========================================

async function nactiUzivatele() {
  const response = await fetch("https://api.example.com/users");

  // DŮLEŽITÉ: Zkontrolovat jestli byl request úspěšný
  if (!response.ok) {
    throw new Error(`HTTP error! Status: ${response.status}`);
  }

  // Přečíst tělo odpovědi jako JSON
  const users = await response.json();
  return users;
}

// Použití
nactiUzivatele()
  .then(users => console.log("Uživatelé:", users))
  .catch(error => console.error("Chyba:", error));


// ==========================================
// 2. POST request s daty
// ==========================================

async function vytvorUzivatele(userData) {
  const response = await fetch("https://api.example.com/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",  // Říkáme serveru co posíláme
      "Accept": "application/json"         // Říkáme co chceme dostat zpět
    },
    body: JSON.stringify(userData)  // Tělo musí být string!
  });

  if (!response.ok) {
    const errorData = await response.json();
    throw new Error(errorData.message || "Nepodařilo se vytvořit uživatele");
  }

  return response.json();  // Vrátí vytvořeného uživatele
}

// Použití
vytvorUzivatele({
  name: "Jan Novák",
  email: "jan@example.com"
})
  .then(user => console.log("Vytvořen:", user))
  .catch(error => console.error("Chyba:", error));


// ==========================================
// 3. Kompletní příklad s error handling
// ==========================================

async function fetchWithErrorHandling(url, options = {}) {
  try {
    const response = await fetch(url, options);

    // Zpracování různých HTTP statusů
    if (response.status === 401) {
      throw new Error("Nepřihlášen - přihlas se prosím");
    }

    if (response.status === 403) {
      throw new Error("Nemáš oprávnění");
    }

    if (response.status === 404) {
      throw new Error("Zdroj nenalezen");
    }

    if (!response.ok) {
      throw new Error(`Server error: ${response.status}`);
    }

    // Různé typy odpovědí
    const contentType = response.headers.get("content-type");

    if (contentType?.includes("application/json")) {
      return await response.json();
    }

    if (contentType?.includes("text/")) {
      return await response.text();
    }

    // Pro ostatní (obrázky, soubory...)
    return await response.blob();

  } catch (error) {
    // Rozlišení síťové vs HTTP chyby
    if (error.name === "TypeError") {
      throw new Error("Síťová chyba - zkontroluj připojení");
    }
    throw error;
  }
}

Co se stalo?

  1. fetch() vrací Promise<Response> - čekáme na odpověď serveru
  2. response.ok je true pro statusy 200-299, false pro ostatní
  3. response.json() parsuje JSON z těla - je to TAKÉ asynchronní!
  4. U POST musíme nastavit method, headers a body
  5. body musí být string - proto JSON.stringify()

Další HTTP metody

// PUT - aktualizace celého zdroje
await fetch("/api/users/123", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Nové jméno", email: "novy@email.cz" })
});

// PATCH - částečná aktualizace
await fetch("/api/users/123", {
  method: "PATCH",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Jen změna jména" })
});

// DELETE - smazání
await fetch("/api/users/123", {
  method: "DELETE"
});

TypeScript

// ==========================================
// Typované Fetch API
// ==========================================

// Definice typů pro API odpovědi
interface User {
  id: number;
  name: string;
  email: string;
  createdAt: string;
}

interface CreateUserRequest {
  name: string;
  email: string;
}

interface ApiError {
  message: string;
  code: string;
}

// Generická fetch funkce s typováním
async function fetchApi<T>(
  url: string,
  options?: RequestInit
): Promise<T> {
  const response = await fetch(url, options);

  if (!response.ok) {
    const error: ApiError = await response.json();
    throw new Error(error.message);
  }

  return response.json() as Promise<T>;
}

// Typované API funkce
async function getUsers(): Promise<User[]> {
  return fetchApi<User[]>("/api/users");
}

async function getUser(id: number): Promise<User> {
  return fetchApi<User>(`/api/users/${id}`);
}

async function createUser(data: CreateUserRequest): Promise<User> {
  return fetchApi<User>("/api/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data)
  });
}

async function deleteUser(id: number): Promise<void> {
  await fetch(`/api/users/${id}`, { method: "DELETE" });
}

// Použití - TypeScript ví co dostaneš
async function main(): Promise<void> {
  const users = await getUsers();
  // users je User[] - máme autocomplete pro .name, .email, atd.

  const user = await getUser(123);
  console.log(user.name);  // TypeScript ví, že user má property name

  const newUser = await createUser({
    name: "Jan",
    email: "jan@example.com"
    // TypeScript by oznámil chybu, kdybychom zapomněli name nebo email
  });
}


// ==========================================
// Pokročilé: Type guard pro API odpovědi
// ==========================================

interface SuccessResponse<T> {
  success: true;
  data: T;
}

interface ErrorResponse {
  success: false;
  error: string;
}

type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

async function safeFetch<T>(url: string): Promise<ApiResponse<T>> {
  try {
    const response = await fetch(url);
    const data = await response.json();

    if (!response.ok) {
      return {
        success: false,
        error: data.message || `HTTP ${response.status}`
      };
    }

    return { success: true, data: data as T };

  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : "Unknown error"
    };
  }
}

// Použití s type narrowing
async function demo(): Promise<void> {
  const result = await safeFetch<User[]>("/api/users");

  if (result.success) {
    // TypeScript ví: result.data je User[]
    result.data.forEach(user => console.log(user.name));
  } else {
    // TypeScript ví: result.error je string
    console.error("Chyba:", result.error);
  }
}

TypeScript přidává:

  • ✅ Typované Response body (User, User[])
  • ✅ Typované Request body (CreateUserRequest)
  • ✅ Generické fetchApi<T> pro znovupoužitelnost
  • ✅ Autocomplete při práci s odpovědí
  • ✅ Chyba při zapomenutí povinného fieldu

Rozdíl JS vs TS

JavaScript:

  • response.json() vrací any - můžeš přistupovat k čemukoliv
  • Žádná kontrola, jestli data mají očekávanou strukturu
  • Chyby se projeví až za běhu

TypeScript:

  • response.json() vrací Promise (nebo přetypuješ)
  • Musíš deklarovat, co očekáváš
  • Chyby v typech odhalíš při kompilaci
// TypeScript rozdíl v praxi

// ❌ JavaScript - nebezpečné
const data = await response.json();
console.log(data.users[0].profile.avatar);
// Pokud struktura nesedí → runtime crash!

// ✅ TypeScript - bezpečné
interface ApiData {
  users: Array<{
    profile: { avatar: string }
  }>
}

const data: ApiData = await response.json();
console.log(data.users[0].profile.avatar);
// TypeScript ví přesně co očekávat
// Pokud API změní strukturu → chyba při kompilaci

Tip

💡 Abort Controller - zrušení requestu:

// Užitečné pro: timeout, cancel při odchodu ze stránky, debounce

const controller = new AbortController();

// Timeout po 5 sekundách
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch("/api/slow", {
    signal: controller.signal  // Předáme signal do fetch
  });

  clearTimeout(timeoutId);  // Request uspěl, zrušíme timeout
  return response.json();

} catch (error) {
  if (error.name === "AbortError") {
    console.log("Request byl zrušen (timeout nebo manuálně)");
  } else {
    throw error;
  }
}

💡 Query parametry správně:

// ❌ Špatně - ruční skládání může rozbít speciální znaky
const url = `/api/search?query=${searchTerm}&page=${page}`;

// ✅ Správně - URLSearchParams enkóduje automaticky
const params = new URLSearchParams({
  query: searchTerm,  // Automaticky enkóduje mezery, &, ? atd.
  page: String(page)
});

const response = await fetch(`/api/search?${params}`);

💡 Request s autorizací:

async function authenticatedFetch(url, options = {}) {
  const token = localStorage.getItem("authToken");

  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      "Authorization": `Bearer ${token}`
    }
  });
}

Kvíz

Co vrací fetch() když server odpoví statusem 404 (Not Found)?

- Fetch NEVYHAZUJE chybu při HTTP chybových statusech (404, 500, atd.)! To je častý zdroj bugů.

- Správně! Fetch vrací úspěšný Promise s Response objektem. Musíš zkontrolovat response.ok nebo response.status sám.

- Fetch vždy vrací Promise, nikdy přímo null.

- Fetch vždy vrací Promise, nikdy přímo undefined.

Klíčové poučení: Fetch vyhazuje chybu POUZE při síťových problémech (žádné připojení, CORS blokace). HTTP statusy jako 404 nebo 500 jsou považovány za "úspěšné" odpovědi serveru!

🎯 Závěrečný projekt

Po dokončení všech 8 dílů vytvoříte jednoduchou Todо aplikaci v čistém JavaScriptu. Naučíte se, jak aplikovat vše, co jste se naučili, na reálný projekt.

Zobrazit podrobnosti projektu →

Připraveni začít?

Zaregistrujte se a získejte přístup ke všem dílům tohoto seriálu

Kontaktujte mě

Informace o seriálu

Obtížnost

Délka

Cca 480 minut

Počet videí

8 videí + projekty

Certifikát

Po dokončení obdržíte certifikát


Lekce v této sekci


Struktura lekcí (souborový strom)

06. Typescript specifika
  • v přípravě
08. Moduly tridy
  • v přípravě
09. React zaklady
  • v přípravě
10. React hooks
  • v přípravě
12. Nextjs server
  • v přípravě
13. Databaze auth
  • v přípravě
14. Nextjs pokrocile
  • v přípravě