Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

REST API - architektura wymiany danych

Witaj z powrotem, @name! Darwin tutaj z kolejną lekcją o przepływie danych.

Nauczyłeś/aś się formatów danych (JSON, CSV, XML) i protokołu HTTP (GET, POST, PUT, DELETE). Teraz czas na REST API - standardową architekturę budowania API, którą używa się na całym świecie! 🌐

Analogia Safari: HTTP to system kurierski. REST API to standardowe procedury - zasady, według których organizujesz stacje badawcze, katalogi danych i komunikację między nimi. Dzięki tym zasadom każdy biolog, niezależnie skąd pochodzi, wie jak pobrać dane o lwach:

GET /api/species/lion
📋

Czym jest REST API?

REST (REpresentational State Transfer) to styl architektury dla API - zbiór zasad i konwencji, jak projektować API wykorzystujące HTTP.

API (Application Programming Interface) to interfejs programistyczny - sposób, w jaki aplikacje rozmawiają ze sobą.

REST API = HTTP API zaprojektowane według zasad REST

Po co REST?

Bez standardów każde API wyglądałoby inaczej:

  • /getLion?id=123
  • /fetchLionData
  • /lion-information/get
  • /api/v1/animals/lion/retrieve

Z REST wszyscy wiedzą:

  • GET /api/species/lion
    - pobierz lwa
  • POST /api/species
    - utwórz gatunek
  • DELETE /api/species/lion
    - usuń lwa

Przewidywalne, spójne, intuicyjne! 🎯

Zasady REST

1. Zasoby (Resources) - rzeczowniki, nie czasowniki

W REST wszystko to zasób (resource) - obiekt, encja, dane.

Dobre ✅ - rzeczowniki:

  • /species
    - gatunek
  • /observations
    - obserwacje
  • /habitats
    - siedliska
  • /researchers
    - badacze

Złe ❌ - czasowniki:

  • /getSpecies
  • /createObservation
  • /deleteHabitat

Akcja określona przez metodę HTTP, nie URL!

2. Metody HTTP = akcje na zasobach

| Metoda | Akcja | Przykład | |--------|-------|----------| | GET | Pobierz zasób/zasoby |

GET /species
- lista,
GET /species/lion
- jeden | | POST | Utwórz nowy zasób |
POST /species
- dodaj gatunek | | PUT | Zastąp cały zasób |
PUT /species/lion
- zastąp dane lwa | | PATCH | Zaktualizuj część zasobu |
PATCH /species/lion
- zmień populację | | DELETE | Usuń zasób |
DELETE /species/lion
- usuń lwa |

3. Stateless - bezstanowość

Każde zapytanie musi zawierać wszystkie informacje potrzebne do jego obsłużenia. Serwer nie pamięta poprzednich zapytań.

Przykład:

1# Każde zapytanie zawiera token autoryzacji
2GET /api/species/lion
3Authorization: Bearer abc123
4
5GET /api/observations
6Authorization: Bearer abc123

Serwer nie wie "kto to wysłał wcześniej" - każde zapytanie samodzielne!

4. Hierarchia zasobów

Zasoby mogą być zagnieżdżone hierarchicznie:

1/species/lion                    # Gatunek: lew
2/species/lion/observations       # Obserwacje lwa
3/species/lion/observations/123   # Konkretna obserwacja lwa #123
4
5/habitats/serengeti              # Siedlisko: Serengeti
6/habitats/serengeti/species      # Gatunki w Serengeti

Zasada:

/zasób/id/podzasób/id

5. Wersjonowanie API

API ewoluuje - wersjonowanie zapobiega zepsuciu starych klientów:

1/api/v1/species
2/api/v2/species

Zmiana wersji gdy:

  • Usuwasz pola z odpowiedzi
  • Zmieniasz format danych
  • Usuwasz endpointy

RESTful Endpoints - standardowe wzorce

CRUD (Create, Read, Update, Delete) dla zasobu

1# LIST - lista wszystkich gatunków
2GET /api/species
3Response: [{"id": "lion", "name": "Lew"}, {"id": "elephant", "name": "Słoń"}]
4
5# GET - jeden gatunek
6GET /api/species/lion
7Response: {"id": "lion", "scientific_name": "Panthera leo", "population": 120}
8
9# CREATE - utwórz nowy gatunek
10POST /api/species
11Body: {"scientific_name": "Gorilla gorilla", "population": 230}
12Response: {"id": "gorilla", "scientific_name": "Gorilla gorilla", "population": 230}
13
14# UPDATE (całość) - zastąp gatunek
15PUT /api/species/lion
16Body: {"scientific_name": "Panthera leo", "population": 125, "habitat": "savanna"}
17Response: {"id": "lion", ...}
18
19# UPDATE (część) - zmień tylko populację
20PATCH /api/species/lion
21Body: {"population": 125}
22Response: {"id": "lion", "population": 125, ...}
23
24# DELETE - usuń gatunek
25DELETE /api/species/lion
26Response: 204 No Content (lub 200 z potwierdzeniem)

Zasoby zagnieżdżone (nested resources)

1# Lista obserwacji dla lwa
2GET /api/species/lion/observations
3Response: [
4  {"id": "obs1", "date": "2024-01-15", "count": 12},
5  {"id": "obs2", "date": "2024-01-20", "count": 8}
6]
7
8# Konkretna obserwacja lwa
9GET /api/species/lion/observations/obs1
10Response: {"id": "obs1", "species": "lion", "date": "2024-01-15", "count": 12}
11
12# Dodaj obserwację dla lwa
13POST /api/species/lion/observations
14Body: {"date": "2024-01-25", "count": 10, "location": "Serengeti"}
15Response: {"id": "obs3", "species": "lion", ...}

Query Parameters - filtrowanie, sortowanie, paginacja

Filtrowanie

1# Gatunki zagrożone
2GET /api/species?endangered=true
3
4# Gatunki w sawannie z populacją > 100
5GET /api/species?habitat=savanna&min_population=100
6
7# Obserwacje z zakresu dat
8GET /api/observations?start_date=2024-01-01&end_date=2024-01-31

Sortowanie

1# Sortuj według populacji rosnąco
2GET /api/species?sort=population
3
4# Sortuj według populacji malejąco
5GET /api/species?sort=-population
6
7# Sortuj po nazwie, potem populacji
8GET /api/species?sort=name,population

Paginacja

1# Strona 1, 20 wyników na stronę
2GET /api/species?page=1&per_page=20
3
4# Alternatywnie: offset i limit
5GET /api/species?offset=0&limit=20
6
7# Odpowiedź z metadanymi paginacji:
8{
9  "data": [...],
10  "pagination": {
11    "current_page": 1,
12    "per_page": 20,
13    "total_items": 150,
14    "total_pages": 8
15  }
16}

Wybór pól (field selection)

1# Tylko nazwa i populacja
2GET /api/species?fields=name,population
3
4Response: [
5  {"name": "Lew", "population": 120},
6  {"name": "Słoń", "population": 450}
7]

Struktura odpowiedzi JSON

Sukces - pojedynczy zasób

1{
2  "id": "lion",
3  "scientific_name": "Panthera leo",
4  "common_name": "Lew",
5  "population": 120,
6  "habitat": "sawanna",
7  "endangered": true,
8  "created_at": "2024-01-01T10:00:00Z",
9  "updated_at": "2024-01-15T14:30:00Z"
10}

Sukces - lista zasobów z paginacją

1{
2  "data": [
3    {"id": "lion", "name": "Lew", "population": 120},
4    {"id": "elephant", "name": "Słoń", "population": 450}
5  ],
6  "pagination": {
7    "page": 1,
8    "per_page": 20,
9    "total": 45,
10    "pages": 3
11  },
12  "links": {
13    "self": "/api/species?page=1",
14    "next": "/api/species?page=2",
15    "last": "/api/species?page=3"
16  }
17}

Błąd - standardowy format

1{
2  "error": {
3    "code": "SPECIES_NOT_FOUND",
4    "message": "Gatunek 'unicorn' nie istnieje",
5    "status": 404,
6    "timestamp": "2024-01-15T14:30:00Z"
7  }
8}

Błędy walidacji

1{
2  "error": {
3    "code": "VALIDATION_ERROR",
4    "message": "Błędy walidacji danych",
5    "status": 400,
6    "details": [
7      {"field": "population", "message": "Musi być liczbą większą od 0"},
8      {"field": "habitat", "message": "Pole wymagane"}
9    ]
10  }
11}

Kody statusu HTTP w REST

2xx - Sukces

  • 200 OK - GET, PUT, PATCH - sukces z danymi
  • 201 Created - POST - zasób utworzony (+ nagłówek
    Location
    )
  • 204 No Content - DELETE - sukces bez danych

4xx - Błąd klienta

  • 400 Bad Request - błędne dane (np. zły JSON, walidacja)
  • 401 Unauthorized - brak uwierzytelnienia
  • 403 Forbidden - brak uprawnień (uwierzytelniony, ale nie ma dostępu)
  • 404 Not Found - zasób nie istnieje
  • 409 Conflict - konflikt (np. duplikat ID)
  • 422 Unprocessable Entity - prawidłowy format, ale logicznie błędne dane
  • 429 Too Many Requests - rate limit przekroczony

5xx - Błąd serwera

  • 500 Internal Server Error - błąd serwera
  • 503 Service Unavailable - serwis niedostępny

Autentykacja i autoryzacja

API Key

1GET /api/species
2X-API-Key: your_api_key_here

Bearer Token (JWT)

1GET /api/species
2Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Basic Auth

1GET /api/species
2Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Najczęściej: Bearer Token (OAuth 2.0, JWT)

Safari przykład - pełne REST API klienta

1import requests
2from typing import List, Dict, Optional, Any
3from datetime import datetime
4
5class SafariRESTClient:
6    """REST API klient dla Safari Data API"""
7
8    def __init__(self, base_url: str, api_key: str):
9        self.base_url = base_url.rstrip("/")
10        self.session = requests.Session()
11        self.session.headers.update({
12            "Authorization": f"Bearer {api_key}",
13            "Accept": "application/json",
14            "Content-Type": "application/json"
15        })
16
17    # === SPECIES - /api/species ===
18
19    def list_species(self, habitat: Optional[str] = None,
20                    endangered: Optional[bool] = None,
21                    page: int = 1, per_page: int = 20,
22                    sort: str = "name") -> Dict[str, Any]:
23        """
24        GET /api/species
25        Lista gatunków z filtrowaniem, sortowaniem, paginacją
26        """
27        params = {
28            "page": page,
29            "per_page": per_page,
30            "sort": sort
31        }
32        if habitat:
33            params["habitat"] = habitat
34        if endangered is not None:
35            params["endangered"] = str(endangered).lower()
36
37        response = self.session.get(f"{self.base_url}/api/species", params=params)
38        response.raise_for_status()
39        return response.json()
40
41    def get_species(self, species_id: str) -> Dict[str, Any]:
42        """
43        GET /api/species/{id}
44        Pobierz pojedynczy gatunek
45        """
46        response = self.session.get(f"{self.base_url}/api/species/{species_id}")
47        response.raise_for_status()
48        return response.json()
49
50    def create_species(self, data: Dict[str, Any]) -> Dict[str, Any]:
51        """
52        POST /api/species
53        Utwórz nowy gatunek
54        """
55        response = self.session.post(f"{self.base_url}/api/species", json=data)
56        response.raise_for_status()
57
58        if response.status_code == 201:
59            # Zwróć utworzony zasób + ID z Location header
60            location = response.headers.get("Location")
61            print(f"✓ Utworzono: {location}")
62
63        return response.json()
64
65    def update_species(self, species_id: str, data: Dict[str, Any]) -> Dict[str, Any]:
66        """
67        PUT /api/species/{id}
68        Zastąp cały gatunek
69        """
70        response = self.session.put(
71            f"{self.base_url}/api/species/{species_id}",
72            json=data
73        )
74        response.raise_for_status()
75        return response.json()
76
77    def partial_update_species(self, species_id: str,
78                              data: Dict[str, Any]) -> Dict[str, Any]:
79        """
80        PATCH /api/species/{id}
81        Zaktualizuj część gatunku
82        """
83        response = self.session.patch(
84            f"{self.base_url}/api/species/{species_id}",
85            json=data
86        )
87        response.raise_for_status()
88        return response.json()
89
90    def delete_species(self, species_id: str) -> bool:
91        """
92        DELETE /api/species/{id}
93        Usuń gatunek
94        """
95        response = self.session.delete(f"{self.base_url}/api/species/{species_id}")
96        response.raise_for_status()
97        return response.status_code == 204
98
99    # === OBSERVATIONS - /api/species/{id}/observations ===
100
101    def list_observations(self, species_id: str,
102                         start_date: Optional[str] = None,
103                         end_date: Optional[str] = None,
104                         page: int = 1) -> Dict[str, Any]:
105        """
106        GET /api/species/{id}/observations
107        Lista obserwacji dla gatunku
108        """
109        params = {"page": page}
110        if start_date:
111            params["start_date"] = start_date
112        if end_date:
113            params["end_date"] = end_date
114
115        response = self.session.get(
116            f"{self.base_url}/api/species/{species_id}/observations",
117            params=params
118        )
119        response.raise_for_status()
120        return response.json()
121
122    def create_observation(self, species_id: str,
123                          data: Dict[str, Any]) -> Dict[str, Any]:
124        """
125        POST /api/species/{id}/observations
126        Dodaj obserwację dla gatunku
127        """
128        response = self.session.post(
129            f"{self.base_url}/api/species/{species_id}/observations",
130            json=data
131        )
132        response.raise_for_status()
133        return response.json()
134
135    def get_observation(self, species_id: str, observation_id: str) -> Dict[str, Any]:
136        """
137        GET /api/species/{id}/observations/{obs_id}
138        Pobierz konkretną obserwację
139        """
140        response = self.session.get(
141            f"{self.base_url}/api/species/{species_id}/observations/{observation_id}"
142        )
143        response.raise_for_status()
144        return response.json()
145
146    # === STATISTICS - /api/statistics ===
147
148    def get_statistics(self, group_by: str = "habitat") -> Dict[str, Any]:
149        """
150        GET /api/statistics
151        Pobierz statystyki
152        """
153        params = {"group_by": group_by}
154        response = self.session.get(
155            f"{self.base_url}/api/statistics",
156            params=params
157        )
158        response.raise_for_status()
159        return response.json()
160
161    def close(self):
162        """Zamknij sesję"""
163        self.session.close()
164
165
166# === DEMONSTRACJA ===
167
168print("=== SAFARI REST API CLIENT ===\n")
169
170client = SafariRESTClient(
171    base_url="https://api.safari-data.org",
172    api_key="demo_bearer_token_12345"
173)
174
175# 1. LIST - pobierz wszystkie gatunki zagrożone w sawannie
176print("1. Lista gatunków (filtrowane, sortowane, paginowane)")
177result = client.list_species(
178    habitat="savanna",
179    endangered=True,
180    page=1,
181    per_page=10,
182    sort="-population"  # Malejąco według populacji
183)
184
185print(f"   Znaleziono: {result['pagination']['total']} gatunków")
186for species in result['data']:
187    print(f"   - {species['common_name']}: {species['population']} osobników")
188
189# 2. GET - pobierz konkretny gatunek
190print("\n2. Pobieranie konkretnego gatunku")
191lion = client.get_species("lion")
192print(f"   {lion['common_name']} ({lion['scientific_name']})")
193print(f"   Populacja: {lion['population']}")
194print(f"   Zagrożony: {'Tak' if lion['endangered'] else 'Nie'}")
195
196# 3. POST - utwórz nowy gatunek
197print("\n3. Tworzenie nowego gatunku")
198new_species = {
199    "scientific_name": "Acinonyx jubatus",
200    "common_name": "Gepard",
201    "population": 7100,
202    "habitat": "sawanna",
203    "endangered": True
204}
205
206created = client.create_species(new_species)
207print(f"   ✓ Utworzono: {created['common_name']} (ID: {created['id']})")
208
209# 4. PATCH - zaktualizuj tylko populację
210print("\n4. Częściowa aktualizacja (PATCH)")
211updated = client.partial_update_species("lion", {"population": 125})
212print(f"   ✓ Zaktualizowano populację: {updated['population']}")
213
214# 5. Obserwacje zagnieżdżone - lista
215print("\n5. Lista obserwacji dla lwa")
216observations = client.list_observations(
217    "lion",
218    start_date="2024-01-01",
219    end_date="2024-01-31"
220)
221
222print(f"   Znaleziono {len(observations['data'])} obserwacji w styczniu")
223for obs in observations['data']:
224    print(f"   - {obs['date']}: {obs['count']} osobników w {obs['location']}")
225
226# 6. Obserwacje zagnieżdżone - tworzenie
227print("\n6. Dodawanie nowej obserwacji")
228new_obs = {
229    "date": datetime.now().isoformat(),
230    "location": "Serengeti North",
231    "count": 15,
232    "notes": "Pride with 3 cubs"
233}
234
235obs_created = client.create_observation("lion", new_obs)
236print(f"   ✓ Utworzono obserwację ID: {obs_created['id']}")
237
238# 7. Statystyki
239print("\n7. Pobieranie statystyk")
240stats = client.get_statistics(group_by="habitat")
241print("   Statystyki według siedliska:")
242for habitat_stat in stats['data']:
243    print(f"   - {habitat_stat['habitat']}: {habitat_stat['species_count']} gatunków")
244
245# 8. DELETE - usuń gatunek
246print("\n8. Usuwanie gatunku")
247if client.delete_species("test-species"):
248    print("   ✓ Gatunek usunięty")
249
250client.close()
251print("\n✓ Demonstracja zakończona")

Best Practices REST API

1. Używaj rzeczowników w liczbie mnogiej

/api/species
,
/api/observations
/api/specie
,
/api/observation

2. Zawsze wersjonuj API

/api/v1/species
/api/species
(co gdy zmienisz format?)

3. Hierarchia zasobów nie głębsza niż 2 poziomy

/species/lion/observations
/continents/africa/countries/kenya/parks/serengeti/species/lion/observations

4. Używaj snake_case lub camelCase - konsekwentnie

{"scientific_name": "..."}
lub
{"scientificName": "..."}
{"scientific_name": "...", "commonName": "..."}
- mieszane!

5. Filtry, sortowanie, paginacja przez query params

GET /species?habitat=savanna&sort=-population&page=2
POST /species/filter
z body

6. Zwracaj odpowiednie kody statusu

  • 201 Created (nie 200) dla POST
  • 204 No Content dla DELETE
  • 404 Not Found gdy zasób nie istnieje
  • 400 Bad Request dla błędów walidacji

7. Używaj nagłówka Location dla nowych zasobów

1POST /api/species
2Response: 201 Created
3Location: /api/species/cheetah

8. Rate limiting - komunikuj limity

1Response Headers:
2X-RateLimit-Limit: 1000
3X-RateLimit-Remaining: 999
4X-RateLimit-Reset: 1640000000

Podsumowanie

W tej lekcji nauczyłeś/aś się:

  • ✅ Czym jest REST API i po co służy
  • ✅ Zasady REST: zasoby, metody HTTP, stateless, hierarchia
  • ✅ RESTful endpoints:
    /api/v1/resources/:id
  • ✅ CRUD operations: GET, POST, PUT, PATCH, DELETE
  • ✅ Query parameters: filtrowanie, sortowanie, paginacja
  • ✅ Struktura odpowiedzi JSON (data, pagination, error)
  • ✅ Kody statusu w kontekście REST
  • ✅ Autentykacja: API Key, Bearer Token
  • ✅ Best practices dla REST API

Checkpoint

Przed przejściem dalej:

  • [ ] Rozumiesz różnicę między
    POST /species
    a
    GET /species/{id}
  • [ ] Potrafisz zaprojektować RESTful endpoint
  • [ ] Wiesz jak używać query params do filtrowania
  • [ ] Rozumiesz zasoby zagnieżdżone (
    /species/{id}/observations
    )
  • [ ] Znasz best practices REST API

Analogia Safari: REST API to uniwersalne standardy komunikacji między stacjami badawczymi na całym świecie. Dzięki nim biolog z Polski wie, że

GET /api/species/lion
zwróci dane lwa - bez względu na to, kto zbudował API! 🌍📡

W następnej lekcji Darwin nauczy Cię web scrapingu - jak zbierać dane ze stron internetowych, które nie mają API! 🕷️🌐

Ir a CodeWorlds