HTTP od podstaw — żądania, odpowiedzi, kody statusu i nagłówki

Zrozum, jak naprawdę działa komunikacja klient-serwer. Metody HTTP, kody statusu, nagłówki, CORS, cookies i cache wyjaśnione z perspektywy frontend developera.

Opublikowano

22 lipca 2025 13:33

Czytanie

8 min czytania

Aktualizacja

15 kwietnia 2026 11:52

Jako frontend developer codziennie używasz HTTP. Każdy fetch(), każde załadowanie strony, każda integracja z API, czyli Application Programming Interface, definiuje sposób komunikacji między aplikacjami lub modułami. — to HTTP. Ale czy naprawdę rozumiesz, co się dzieje "pod spodem"?

Na rozmowach technicznych pytania o HTTP pojawiają się regularnie. "Co oznacza status 304?", "Czym różni się POST od PUT?", "Jak działa CORS?". Ten artykuł da Ci solidne fundamenty. Jeśli nie wiesz jeszcze, jak działa internet od podstaw — zacznij od tego.

Krótka odpowiedź: HTTP = request-response między klientem a serwerem. Metody: GET (pobierz), POST (utwórz), PUT (zastąp cały zasób), PATCH (aktualizuj częściowo), DELETE (usuń). Kluczowe kody: 200 OK, 201 Created, 401 (brak auth), 403 (brak uprawnień), 404 (nie znaleziono), 500 (błąd serwera). CORS blokuje przeglądarka — naprawia serwer, nie frontend.

Czym jest HTTP?

HTTP (HyperText Transfer Protocol) to protokół komunikacji między klientem (przeglądarka, aplikacja) a serwerem. Jest:

  • Bezstanowy — każde żądanie jest niezależne, serwer nie "pamięta" poprzednich
  • Semantyczny — pracujesz na metodach, URL-ach, nagłówkach i statusach; w HTTP/1.1 wiadomości są tekstowe, ale HTTP/2 i HTTP/3 używają binarnego framingu
  • Request-Response — klient pyta, serwer odpowiada

HTTPS

HTTPS to HTTP uruchomiony przez TLS. Dane są szyfrowane w transporcie, co chroni przed podsłuchiwaniem i modyfikacją po drodze, ale nie rozwiązuje problemów po stronie samej aplikacji.

Anatomia HTTP Request

Code
GET /api/users/123 HTTP/1.1
Host: example.com
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)

Składniki:

  1. Linia żądania: METODA /ścieżka HTTP/wersja
  2. Nagłówki: pary klucz-wartość
  3. Pusta linia
  4. Body (opcjonalne): dane wysyłane na serwer

Z body (POST/PUT):

Code
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 42
 
{"name": "Jan", "email": "jan@example.com"}

Anatomia HTTP Response

Code
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 85
Cache-Control: max-age=3600
Set-Cookie: session=abc123; HttpOnly
 
{"id": 123, "name": "Jan", "email": "jan@example.com"}

Składniki:

  1. Linia statusu: HTTP/wersja KOD_STATUSU OPIS
  2. Nagłówki odpowiedzi
  3. Pusta linia
  4. Body: dane zwrócone przez serwer

Metody HTTP

GET — pobierz dane

Code
// Pobierz listę użytkowników
fetch('/api/users')
 
// Pobierz konkretnego użytkownika
fetch('/api/users/123')
  • W praktyce zwykle bez body — dane przekazujesz przez URL (query params)
  • Cacheowalny — przeglądarka może zapisać odpowiedź
  • Bezpieczny — nie zmienia stanu serwera
  • Idempotentny — wielokrotne wywołanie = ten sam efekt

POST — utwórz zasób

Code
fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Jan', email: 'jan@example.com' })
})
  • Ma body — dane w ciele żądania
  • Nie cacheowalny (domyślnie)
  • Nie idempotentny — każde wywołanie może utworzyć nowy zasób

PUT — zastąp zasób

Code
fetch('/api/users/123', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Jan', email: 'jan.nowy@example.com' })
})
  • Idempotentny — wielokrotne wywołanie = ten sam stan
  • Zazwyczaj zastępuje cały zasób — wiele API oczekuje pełnej reprezentacji, ale to zawsze kwestia konkretnego kontraktu

PATCH — częściowa aktualizacja

Code
fetch('/api/users/123', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'jan.nowy@example.com' })  // tylko zmienione pole
})
  • Częściowa aktualizacja — tylko przekazane pola

DELETE — usuń zasób

Code
fetch('/api/users/123', {
  method: 'DELETE'
})
  • Idempotentny — wielokrotne wywołanie powinno prowadzić do tego samego stanu końcowego
  • Zazwyczaj bez body

Inne metody

  • HEAD — jak GET, ale tylko nagłówki (bez body)
  • OPTIONS — jakie metody są dozwolone? (używane w CORS preflight)

Porównanie metod

MetodaBodyIdempotentnaCacheableUżycie
GETNieTakTakPobieranie
POSTTakNieNie*Tworzenie
PUTTakTakNieZastępowanie
PATCHTakNieNieAktualizacja
DELETENie*TakNieUsuwanie

* POST może być cacheowalny przy jawnych nagłówkach Cache-Control/Expires. DELETE technicznie może mieć body, ale serwery zazwyczaj je ignorują.

Kody statusu HTTP

Kody dzielą się na 5 kategorii:

1xx — Informacyjne

Rzadko spotykane w praktyce.

  • 100 Continue — serwer otrzymał nagłówki, kontynuuj wysyłanie body

2xx — Sukces ✓

  • 200 OK — standardowy sukces
  • 201 Created — zasób utworzony (po POST)
  • 204 No Content — sukces, ale brak treści do zwrócenia (po DELETE)
Code
// 201 — serwer zwraca utworzony zasób
const response = await fetch('/api/users', { method: 'POST', body: data })
// response.status === 201
const newUser = await response.json()

3xx — Przekierowania

  • 301 Moved Permanently — zasób na stałe przeniesiony
  • 302 Found — tymczasowe przekierowanie
  • 304 Not Modified — użyj cache (dane się nie zmieniły)
Code
// 304 — przeglądarka użyje cache
// Dzieje się automatycznie gdy serwer wspiera ETag/If-None-Match

4xx — Błędy klienta

  • 400 Bad Request — nieprawidłowe żądanie (np. zły JSON)
  • 401 Unauthorized — brak uwierzytelnienia (nie jesteś zalogowany) — więcej w artykule o autoryzacji i autentykacji
  • 403 Forbidden — brak uprawnień (jesteś zalogowany, ale nie masz dostępu)
  • 404 Not Found — zasób nie istnieje
  • 405 Method Not Allowed — endpoint nie wspiera tej metody
  • 409 Conflict — konflikt (np. duplikat)
  • 422 Unprocessable Entity — walidacja nie przeszła
  • 429 Too Many Requests — rate limiting (sprawdź, jak wdrożyć go w Upstash Redis w Next.js — sesje, cache, rate limiting i liczniki)
Code
const response = await fetch('/api/users/999')
 
if (response.status === 404) {
  console.log('Użytkownik nie istnieje')
}
 
if (response.status === 401) {
  // Przekieruj do logowania
  window.location.href = '/login'
}

5xx — Błędy serwera

  • 500 Internal Server Error — ogólny błąd serwera
  • 502 Bad Gateway — serwer proxy otrzymał złą odpowiedź
  • 503 Service Unavailable — serwer tymczasowo niedostępny
  • 504 Gateway Timeout — timeout proxy/load balancera
Code
const response = await fetch('/api/data')
 
if (response.status >= 500) {
  console.log('Problem po stronie serwera, spróbuj później')
}

Nagłówki HTTP

Nagłówki żądania (Request Headers)

Code
Host: example.com                    // Wymagany — docelowy serwer
Accept: application/json             // Jakiego formatu oczekuję
Content-Type: application/json       // Format wysyłanych danych
Authorization: Bearer <token>        // Uwierzytelnienie
User-Agent: Mozilla/5.0...           // Informacje o kliencie
Accept-Language: pl,en               // Preferowane języki
Cookie: session=abc123               // Ciasteczka

Nagłówki odpowiedzi (Response Headers)

Code
Content-Type: application/json       // Format odpowiedzi
Content-Length: 1234                 // Rozmiar body w bajtach
Cache-Control: max-age=3600          // Instrukcje cachowania
Set-Cookie: session=xyz; HttpOnly    // Ustaw ciasteczko
Location: /api/users/123             // URL nowego zasobu (z 201)
ETag: "abc123"                       // Wersja zasobu (dla cache)
Access-Control-Allow-Origin: *       // CORS

Content-Type — najważniejszy nagłówek

Code
// JSON (najczęściej)
'Content-Type': 'application/json'
 
// Formularz HTML
'Content-Type': 'application/x-www-form-urlencoded'
 
// Pliki (multipart)
'Content-Type': 'multipart/form-data'
 
// Zwykły tekst
'Content-Type': 'text/plain'
 
// HTML
'Content-Type': 'text/html'

CORS — Cross-Origin Resource Sharing

CORS to mechanizm bezpieczeństwa przeglądarki. Domyślnie JavaScript może wysyłać żądania tylko do tego samego origin (protokół + domena + port).

Code
// Strona na https://app.example.com
 
// ✓ Same origin — działa
fetch('https://app.example.com/api/users')
 
// ✗ Cross-origin — zablokowane (domyślnie)
fetch('https://api.other-domain.com/users')

Jak działa CORS?

1. Proste żądania (GET, HEAD, POST — ale tylko z Content-Type: text/plain, application/x-www-form-urlencoded lub multipart/form-data i bez niestandardowych nagłówków):

Przeglądarka wysyła żądanie, serwer dodaje nagłówek:

Code
Access-Control-Allow-Origin: https://app.example.com

2. Preflight (złożone żądania):

Każde żądanie z nagłówkiem Authorization, Content-Type: application/json lub innym niestandardowym nagłówkiem — nawet zwykły GET — nie jest "prostym żądaniem" i wymusza preflight. Przeglądarka najpierw wysyła OPTIONS:

Code
OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

Serwer odpowiada:

Code
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 7200

Dopiero potem przeglądarka wysyła właściwe żądanie.

Typowy błąd CORS

Code
Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' 
has been blocked by CORS policy

Rozwiązanie: Serwer musi dodać odpowiednie nagłówki CORS. Frontend nie może tego "ominąć".

Cookies

Cookies to małe dane przechowywane w przeglądarce i automatycznie wysyłane z każdym żądaniem.

Code
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=86400

Atrybuty:

  • HttpOnly — niedostępne dla JavaScript (ochrona przed XSS)
  • Secure — tylko przez HTTPS
  • SameSite — ochrona przed CSRF (Strict/Lax/None); od Chrome 80 (2020) przeglądarki domyślnie traktują cookie bez SameSite jako Lax — nie płynie w cross-site requestach, jeśli nie ustawisz atrybutu jawnie
  • Max-Age/Expires — czas życia
  • Domain/Path — zakres obowiązywania

Wysyłanie cookies z fetch:

Code
// Domyślnie fetch NIE wysyła cookies cross-origin
fetch('https://api.example.com/user', {
  credentials: 'include'  // wyślij cookies
})
 
// Same-origin: credentials: 'same-origin' (domyślne)

Żeby to zadziałało cross-origin, serwer musi też odpowiedzieć Access-Control-Allow-Credentials: true, wskazać konkretny origin zamiast *, a same ciasteczka często wymagają SameSite=None; Secure.

Cache HTTP

Cache-Control

Code
Cache-Control: max-age=3600           // cachuj przez 1h
Cache-Control: no-cache               // zawsze waliduj z serwerem
Cache-Control: no-store               // nigdy nie cachuj
Cache-Control: private                // tylko cache przeglądarki
Cache-Control: public                 // można cachować wszędzie

ETag i walidacja

Code
// Pierwsza odpowiedź
HTTP/1.1 200 OK
ETag: "abc123"
Content-Type: application/json
 
{"data": "..."}
Code
// Kolejne żądanie z ETag
GET /api/data HTTP/1.1
If-None-Match: "abc123"
 
// Jeśli dane się nie zmieniły:
HTTP/1.1 304 Not Modified
// Brak body — użyj cache

Praktyczne przykłady z fetch

GET z obsługą błędów

Code
async function getUser(id) {
  const response = await fetch(`/api/users/${id}`)
  
  if (!response.ok) {
    if (response.status === 404) {
      throw new Error('Użytkownik nie istnieje')
    }
    throw new Error(`HTTP Error: ${response.status}`)
  }
  
  return response.json()
}

POST z JSON

Code
async function createUser(userData) {
  const response = await fetch('/api/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData),
  })
  
  if (response.status === 201) {
    return response.json()  // zwróć utworzonego usera
  }
  
  if (response.status === 422) {
    const errors = await response.json()
    throw new ValidationError(errors)
  }
  
  throw new Error(`HTTP Error: ${response.status}`)
}

Upload pliku

Code
async function uploadFile(file) {
  const formData = new FormData()
  formData.append('file', file)
  
  const response = await fetch('/api/upload', {
    method: 'POST',
    body: formData,  // Content-Type ustawia się automatycznie
  })
  
  return response.json()
}

FAQ

Czym jest HTTP i jak działa?

HTTP (HyperText Transfer Protocol) to protokół komunikacji klient-serwer. Klient (przeglądarka, aplikacja) wysyła request z metodą, URL, nagłówkami i opcjonalnym body. Serwer odpowiada kodem statusu, nagłówkami i body z danymi. HTTP jest bezstanowy — każdy request jest niezależny, serwer nie "pamięta" poprzednich. HTTPS to HTTP z szyfrowaniem TLS, które chroni transmisję przed podsłuchiwaniem.

Jaka różnica między PUT a PATCH w HTTP?

PUT zastępuje cały zasób — wysyłasz kompletną reprezentację obiektu (wszystkie pola). Jeśli pole nie jest w body, zostaje usunięte lub zresetowane. PATCH aktualizuje częściowo — wysyłasz tylko zmienione pola. PUT jest idempotentny (wielokrotne wywołanie = ten sam wynik). PATCH formalnie nie musi być idempotentny, choć wiele API tak go implementuje. W praktyce: edytujesz email użytkownika? PATCH. Zastępujesz cały profil? PUT.

Co oznacza 401 vs 403 w HTTP?

401 Unauthorized oznacza, że użytkownik nie jest uwierzytelniony — nie podał credentiali lub token jest nieważny. Nazwa jest myląca (historycznie). 403 Forbidden oznacza, że użytkownik jest uwierzytelniony ale nie ma uprawnień do zasobu. Praktyka: 401 → przekieruj do logowania. 403 → pokaż komunikat "brak dostępu", nie logowania. 404 zamiast 403 bywa używane gdy chcesz ukryć samo istnienie zasobu.

Co to jest CORS i jak go naprawić?

CORS (Cross-Origin Resource Sharing) to mechanizm bezpieczeństwa przeglądarki blokujący requesty do innego origin (inna domena, port lub protokół). Frontend nie może ominąć CORS — blokadę wymusza przeglądarka. Naprawia się po stronie serwera: dodaj nagłówek Access-Control-Allow-Origin: https://twoja-domena.com. Dla złożonych requestów (PUT, nagłówki niestandardowe) serwer musi obsługiwać preflight OPTIONS. W dev: używaj proxy Next.js lub cors middleware w Express.

Co to jest idempotentność w HTTP?

Operacja jest idempotentna gdy wielokrotne wywołanie z tymi samymi parametrami daje ten sam wynik co pojedyncze wywołanie. GET (pobierz user) → za każdym razem ten sam user. PUT (zastąp user) → za każdym razem ten sam stan. DELETE (usuń user) → user jest usunięty, nie ma znaczenia czy wywołałeś 1 raz czy 5. Nie idempotentne: POST → każde wywołanie może tworzyć nowy zasób. To ma znaczenie przy retry logic — bezpiecznie powtarzasz tylko idempotentne operacje.

Jak działa cache HTTP?

Serwer ustawia Cache-Control: max-age=3600 — przeglądarka cachuje na 1 godzinę i nie pyta ponownie. Po wygaśnięciu: serwer zwraca ETag: "abc123", przeglądarka wysyła If-None-Match: "abc123" — jeśli dane się nie zmieniły, serwer odpowiada 304 Not Modified bez body (szybciej, mniej danych). Cache-Control: no-cache zawsze waliduje z serwerem. no-store nigdy nie cachuje. private tylko cache przeglądarki (nie CDN, czyli Content Delivery Network, przyspiesza dostarczanie zasobów z serwerów bliższych użytkownikowi.).

Jaka różnica między HTTP/1.1 a HTTP/2?

HTTP/1.1: każdy request wymaga osobnego połączenia TCP (lub kolejkuje w jednym — head-of-line blocking). Wiadomości tekstowe. Przeglądarka otwiera zwykle kilka równoległych połączeń per origin. HTTP/2: multiplexing — wiele requestów przez jedno połączenie TCP jednocześnie. Binarny format (szybszy parsing). Header compression (HPACK). Efekt: przy wielu małych requestach HTTP/2 jest znacznie szybsze. Historycznie istniał też mechanizm Server Push, ale nie stał się trwałą przewagą HTTP/2 i dziś nie warto traktować go jako głównego argumentu architektonicznego.

Źródła i dokumentacja

Podsumowanie — cheat sheet

MetodaUżycieIdempotentBody
GETPobierzTakNie
POSTUtwórzNieTak
PUTZastąpTakTak
PATCHAktualizujNieTak
DELETEUsuńTakNie
StatusZnaczenie
200OK
201Created
204No Content
301/302Redirect
304Not Modified (cache)
400Bad Request
401Unauthorized
403Forbidden
404Not Found
422Validation Error
429Too Many Requests
500Server Error

Zrozumienie HTTP to fundament pracy z API i projektowania REST API. Na rozmowie pokaż, że wiesz nie tylko jak użyć fetch(), ale też co dzieje się pod spodem.


Chcesz dowiedzieć się więcej o backendzie? Sprawdź artykuł o Node.js — pierwszy serwer w 30 minut lub przeczytaj kompletny przegląd backendu dla frontendowca.

Pracuję z tym zawodowo.

Jeśli chcesz uporządkować backendowe fundamenty aplikacji i uniknąć typowych błędów architektonicznych już na starcie, skontaktuj się ze mną. Pomagam przekładać wiedzę techniczną na rozwiązania, które da się sensownie utrzymać i rozwijać.

O autorze

Maciej Sala

Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.

Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Fachowe porównanie Astro.js i Next.js z perspektywy developera pracującego na co dzień w Next.js. Architektura, wydajność, SEO, DX, koszty i konkretne use case — z benchmarkami i przykładami kodu.

Maciej Sala

Maciej Sala

Founder Strivelab