Jak połączyć Django lub FastAPI z Reactem

Kacper Sieradziński
Kacper Sieradziński
30 maja 2025Edukacja5 min czytania

Nowoczesne aplikacje webowe coraz częściej dzielą się na dwa niezależne światy:

Obraz główny Jak połączyć Django lub FastAPI z Reactem
  • Backend – odpowiedzialny za logikę biznesową, API i dane (np. Django REST Framework, FastAPI),
  • Frontend – zajmujący się interfejsem użytkownika (React, Next.js, Vue).

To podejście pozwala tworzyć bardziej skalowalne i elastyczne aplikacje, ale wymaga prawidłowej integracji między warstwami. W tym artykule dowiesz się, jak połączyć Pythonowy backend (Django lub FastAPI) z Reactem, zachowując bezpieczeństwo, wydajność i wygodę pracy.

Architektura aplikacji frontend-backend

Typowa architektura aplikacji typu SPA (Single Page Application) wygląda tak:

Bash
1 [React/Next.js frontend] → [Python API: Django / FastAPI] → [Database]

Frontend komunikuje się z backendem poprzez REST API lub GraphQL. Backend zwraca dane w formacie JSON, które frontend przetwarza i renderuje w przeglądarce.

Aby to działało, musimy:

  1. Włączyć CORS, aby frontend mógł komunikować się z backendem z innego adresu,
  2. Zaimplementować autoryzację (JWT, sesje),
  3. Zaprojektować czytelne API endpoints,
  4. Skonfigurować środowisko deweloperskie i produkcyjne.

Konfiguracja CORS

CORS (Cross-Origin Resource Sharing) pozwala przeglądarce komunikować się z API działającym pod inną domeną (np. frontend: localhost:3000, backend: localhost:8000).

Django + Django REST Framework

Zainstaluj bibliotekę:

Bash
1 pip install django-cors-headers

W pliku settings.py:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 INSTALLED_APPS = [ ... "corsheaders", "rest_framework", ] MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware",# Musi być na początku "django.middleware.common.CommonMiddleware", ... ] # Dozwolone źródła (origins) CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "http://localhost:5173",# Vite "https://twojfrontend.pl" ] # Alternatywnie - dozwól wszystko w trybie deweloperskim # CORS_ALLOW_ALL_ORIGINS = True# Tylko w development! # Dozwolone metody HTTP CORS_ALLOW_METHODS = [ "DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT", ] # Dozwolone nagłówki CORS_ALLOW_HEADERS = [ "accept", "accept-encoding", "authorization", "content-type", "dnt", "origin", "user-agent", "x-csrftoken", "x-requested-with", ] # Zezwól na przesyłanie cookies CORS_ALLOW_CREDENTIALS = True

Konfiguracja CORS w FastAPI

FastAPI ma wbudowane wsparcie dla CORS:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=[ "http://localhost:3000", "http://localhost:5173", "https://twojfrontend.pl" ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )

Dzięki temu frontend może bez problemu wykonywać zapytania AJAX/FETCH do backendu.

Testowanie CORS

Możesz sprawdzić, czy CORS działa poprawnie, używając cURL:

Bash
1 2 3 4 5 curl -H "Origin: http://localhost:3000" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: Content-Type" \ -X OPTIONS \ http://localhost:8000/api/posts/

Odpowiedź powinna zawierać nagłówki Access-Control-Allow-Origin, Access-Control-Allow-Methods, itp.

Autoryzacja – JWT lub sesje

Integracja backendu i frontendu wymaga spójnego mechanizmu autoryzacji.

JWT (JSON Web Tokens)

Idealne w aplikacjach SPA lub mobilnych, gdzie frontend przechowuje tokeny w pamięci (np. localStorage lub cookie).

Django REST Framework

Zainstaluj:

Bash
1 pip install djangorestframework-simplejwt

W settings.py:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 INSTALLED_APPS = [ ... "rest_framework", "rest_framework_simplejwt", ] REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework_simplejwt.authentication.JWTAuthentication", ), "DEFAULT_PERMISSION_CLASSES": ( "rest_framework.permissions.IsAuthenticated", ), } from datetime import timedelta SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(minutes=30), "REFRESH_TOKEN_LIFETIME": timedelta(days=7), "ROTATE_REFRESH_TOKENS": True, }

Endpoint logowania w urls.py:

Python
1 2 3 4 5 6 7 from django.urls import path from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView urlpatterns = [ path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), ]

Implementacja JWT w FastAPI

FastAPI obsługuje JWT natywnie:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import jwt, JWTError from datetime import datetime, timedelta SECRET_KEY = "your-secret-key" ALGORITHM = "HS256" oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") def create_access_token(data: dict, expires_delta: timedelta = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=30) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt def get_current_user(token: str = Depends(oauth2_scheme)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception except JWTError: raise credentials_exception return {"username": username} @app.post("/token") async def login(form_data: OAuth2PasswordRequestForm = Depends()): # Weryfikacja użytkownika user = authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException(status_code=401, detail="Invalid credentials") access_token = create_access_token(data={"sub": user.username}) return {"access_token": access_token, "token_type": "bearer"} @app.get("/users/me") async def read_users_me(current_user: dict = Depends(get_current_user)): return current_user

Użycie JWT w React

JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // Login const response = await fetch('http://localhost:8000/api/token/', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: 'user', password: 'pass' }) }); const data = await response.json(); localStorage.setItem('access_token', data.access_token); localStorage.setItem('refresh_token', data.refresh_token); // Autoryzowane zapytania const token = localStorage.getItem('access_token'); const response = await fetch('http://localhost:8000/api/users/me', { headers: { 'Authorization': `Bearer ${token}` } });

Sesje

Dla aplikacji działających w przeglądarce klasycznie (SSR lub hybrydowo), można użyć sesji HTTP-only.

Django - Sesje z CSRF

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # settings.py SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SAMESITE = 'Lax' CSRF_COOKIE_HTTPONLY = True # W widoku from django.contrib.auth import login from django.views.decorators.csrf import csrf_exempt from django.middleware.csrf import get_token @csrf_exempt def login_view(request): if request.method == 'POST': username = request.POST['username'] password = request.POST['password'] user = authenticate(username=username, password=password) if user: login(request, user) return JsonResponse({'status': 'ok'}) csrf_token = get_token(request) return JsonResponse({'csrf_token': csrf_token})

Implementacja sesji w FastAPI

Bash
1 pip install python-multipart
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from fastapi import FastAPI, Request from starlette.middleware.sessions import SessionMiddleware from starlette.responses import JSONResponse app = FastAPI() app.add_middleware(SessionMiddleware, secret_key="your-secret-key") @app.post("/login") async def login(request: Request): form = await request.form() username = form.get("username") password = form.get("password") # Weryfikacja user = authenticate_user(username, password) if user: request.session["user_id"] = user.id return JSONResponse({"status": "ok"}) return JSONResponse({"status": "error"}, status_code=401) @app.get("/users/me") async def read_users_me(request: Request): user_id = request.session.get("user_id") if not user_id: return JSONResponse({"detail": "Not authenticated"}, status_code=401) return {"user_id": user_id}

API endpoints i struktura danych

Frontend potrzebuje czytelnych i przewidywalnych API endpoints.

Przykład dobrego REST API

GET/api/posts/ → lista postów
GET/api/posts/1/ → szczegóły posta
POST /api/posts/ → utworzenie posta
PUT/api/posts/1/ → edycja posta
PATCH/api/posts/1/ → częściowa edycja
DELETE /api/posts/1/ → usunięcie posta

Spójna struktura odpowiedzi

Zwracaj dane w spójnej strukturze JSON:

JSON
1 2 3 4 5 6 { "id": 1, "title": "Integracja Django z React", "author": "Kacper Sieradziński", "created_at": "2025-05-30T12:00:00Z" }

Dla list:

JSON
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "count": 100, "next": "http://api.example.com/api/posts/?page=2", "previous": null, "results": [ { "id": 1, "title": "Post 1", ... }, { "id": 2, "title": "Post 2", ... } ] }

Statusy HTTP

Zachowuj standardowe statusy HTTP:

  • 200 OK – sukces (GET, PUT, PATCH)
  • 201 Created – nowy rekord utworzony (POST)
  • 204 No Content – sukces bez treści (DELETE)
  • 400 Bad Request – błędne dane
  • 401 Unauthorized – brak autoryzacji
  • 403 Forbidden – brak uprawnień
  • 404 Not Found – nie znaleziono zasobu
  • 422 Unprocessable Entity – błędy walidacji
  • 500 Internal Server Error – błąd serwera

Przykład endpointu w Django REST Framework

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from rest_framework import viewsets, status from rest_framework.response import Response from rest_framework.decorators import action class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer @action(detail=True, methods=['post']) def publish(self, request, pk=None): post = self.get_object() post.published = True post.save() return Response({'status': 'published'})

Przykład REST endpointu w FastAPI

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from fastapi import FastAPI, HTTPException, status from pydantic import BaseModel from typing import List from datetime import datetime app = FastAPI() class Post(BaseModel): id: int title: str author: str created_at: str class PostCreate(BaseModel): title: str author: str posts_db = [] @app.get("/api/posts/", response_model=List[Post]) def list_posts(skip: int = 0, limit: int = 10): return posts_db[skip:skip + limit] @app.get("/api/posts/{post_id}", response_model=Post) def get_post(post_id: int): post = next((p for p in posts_db if p["id"] == post_id), None) if not post: raise HTTPException(status_code=404, detail="Post not found") return post @app.post("/api/posts/", status_code=status.HTTP_201_CREATED, response_model=Post) def create_post(post: PostCreate): new_post = { "id": len(posts_db) + 1, **post.dict(), "created_at": datetime.now().isoformat() } posts_db.append(new_post) return new_post

Synchronizacja środowisk developerskich

Aby uniknąć konfliktów:

Uruchamianie backendu i frontendu równolegle

Terminal 1 - Backend:

Bash
1 2 3 4 cd backend python manage.py runserver# Django uvicorn main:app --reload # FastAPI

Terminal 2 - Frontend:

Bash
1 2 3 4 cd frontend npm start# React npm run dev# Next.js/Vite

Proxy w React

Użyj proxy w React, aby uprościć adresy żądań.

W pliku package.json:

JSON
1 2 3 4 { "name": "my-app", "proxy": "http://127.0.0.1:8000" }

Dzięki temu w React możesz pisać:

JavaScript
1 fetch("/api/posts")

zamiast:

JavaScript
1 fetch("http://127.0.0.1:8000/api/posts")

Konfiguracja proxy w Vite

Jeśli używasz Vite, skonfiguruj proxy w vite.config.js:

JavaScript
1 2 3 4 5 6 7 8 9 10 export default { server: { proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true, } } } }

Zmienne środowiskowe

Django:

Python
1 2 3 # settings.py FRONTEND_URL = os.environ.get('FRONTEND_URL', 'http://localhost:3000') CORS_ALLOWED_ORIGINS = [FRONTEND_URL]

FastAPI:

Python
1 2 3 4 5 6 7 8 9 10 # config.py from pydantic_settings import BaseSettings class Settings(BaseSettings): frontend_url: str = "http://localhost:3000" class Config: env_file = ".env" settings = Settings()

React (.env):

Bash
1 REACT_APP_API_URL=http://localhost:8000

Użycie w React:

JavaScript
1 2 const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; fetch(`${API_URL}/api/posts`);

Deploy – Django + React w produkcji

Masz dwa główne podejścia do wdrożenia:

1. Wspólny serwer (monolit)

Django serwuje statyczny build frontendu.

Konfiguracja Django

W settings.py:

Python
1 2 3 4 5 6 7 8 9 10 11 import os STATICFILES_DIRS = [ BASE_DIR / "frontend/build/static" ] STATIC_ROOT = BASE_DIR / "staticfiles" # Pliki media (uploady użytkowników) MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / "media"

W urls.py:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from django.contrib import admin from django.urls import path, re_path from django.views.static import serve from django.views.generic import TemplateView from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path("admin/", admin.site.urls), path("api/", include("api.urls")), # Serwuj statyczne pliki w produkcji re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}), re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), # Wszystkie inne ścieżki → React Router re_path(r'^.*$', TemplateView.as_view(template_name='index.html')), ] # W development if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Build frontendu

Bash
1 2 3 cd frontend npm run build cp -r build/* ../backend/frontend/

2. Oddzielne usługi (mikroserwisy)

Frontend (React) hostowany na CDN (np. Vercel, Netlify), backend (FastAPI/Django) na serwerze API (np. Render, Railway, AWS).

Konfiguracja dla oddzielnych serwisów

Backend (Django/FastAPI):

Python
1 2 3 4 5 # settings.py / config.py CORS_ALLOWED_ORIGINS = [ "https://twoj-frontend.vercel.app", "https://twoj-frontend.netlify.app" ]

Frontend:

JavaScript
1 2 3 4 // config.js const API_URL = process.env.REACT_APP_API_URL || 'https://api.twoj-backend.com'; export { API_URL };

Docker Compose (dla pełnej kontroli)

YAML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 version: '3.9' services: backend: build: ./backend ports: - "8000:8000" environment: - CORS_ALLOWED_ORIGINS=http://localhost:3000 frontend: build: ./frontend ports: - "3000:3000" environment: - REACT_APP_API_URL=http://localhost:8000 depends_on: - backend

Współpraca między zespołami frontend/backend

Dobre praktyki komunikacji:

Wspólna dokumentacja API

Swagger/OpenAPI (FastAPI automatycznie):

  • Dostępna pod /docs (Swagger UI)
  • Dostępna pod /redoc (ReDoc)

Django REST Framework:

Bash
1 pip install drf-yasg
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from drf_yasg.views import get_schema_view from drf_yasg import openapi schema_view = get_schema_view( openapi.Info( title="My API", default_version='v1', ), public=True, ) urlpatterns = [ path('swagger/', schema_view.with_ui('swagger', cache_timeout=0)), ]

Mock API

Frontend może testować interfejs zanim backend będzie gotowy. Użyj narzędzi takich jak:

  • MSW (Mock Service Worker) – mockowanie API w przeglądarce
  • JSON Server – szybkie REST API z JSON
  • Postman Mock Servers

Wersjonowanie API

Zawsze wersjonuj API:

/api/v1/posts/
/api/v2/posts/

Django:

Python
1 2 3 4 urlpatterns = [ path('api/v1/', include('api.v1.urls')), path('api/v2/', include('api.v2.urls')), ]

FastAPI:

Python
1 2 3 4 5 6 7 from fastapi import APIRouter v1 = APIRouter(prefix="/api/v1", tags=["v1"]) v2 = APIRouter(prefix="/api/v2", tags=["v2"]) app.include_router(v1) app.include_router(v2)

Stałe formaty błędów

Zawsze zwracaj błędy w spójnym formacie:

JSON
1 2 3 4 { "detail": "Invalid credentials", "code": "AUTH_ERROR" }

lub dla błędów walidacji:

JSON
1 2 3 4 5 6 { "errors": { "email": ["This field is required."], "password": ["Password must be at least 8 characters."] } }

Przykład pełnego przepływu logowania

Implementacja logowania w FastAPI

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from fastapi import FastAPI, Depends, HTTPException from fastapi.security import OAuth2PasswordRequestForm from jose import jwt from datetime import datetime, timedelta app = FastAPI() SECRET_KEY = "your-secret-key" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 def create_access_token(data: dict): to_encode = data.copy() expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire}) return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) @app.post("/api/token/") async def login(form_data: OAuth2PasswordRequestForm = Depends()): # Weryfikacja użytkownika user = authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException(status_code=401, detail="Invalid credentials") access_token = create_access_token(data={"sub": user.username}) refresh_token = create_access_token(data={"sub": user.username, "type": "refresh"}) return { "access_token": access_token, "refresh_token": refresh_token, "token_type": "bearer" } @app.post("/api/token/refresh/") async def refresh_token(refresh_token: str): try: payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM]) username = payload.get("sub") new_access_token = create_access_token(data={"sub": username}) return {"access_token": new_access_token, "token_type": "bearer"} except: raise HTTPException(status_code=401, detail="Invalid refresh token")

Frontend (React)

JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 // api.js const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; class ApiClient { constructor() { this.baseURL = API_URL; } async login(username, password) { const formData = new FormData(); formData.append('username', username); formData.append('password', password); const response = await fetch(`${this.baseURL}/api/token/`, { method: 'POST', body: formData, }); if (!response.ok) { throw new Error('Invalid credentials'); } const data = await response.json(); localStorage.setItem('access_token', data.access_token); localStorage.setItem('refresh_token', data.refresh_token); return data; } async get(path) { const token = localStorage.getItem('access_token'); const response = await fetch(`${this.baseURL}${path}`, { headers: { 'Authorization': `Bearer ${token}` } }); if (response.status === 401) { // Token wygasł, odśwież await this.refreshToken(); return this.get(path); // Ponów żądanie } return response.json(); } async refreshToken() { const refreshToken = localStorage.getItem('refresh_token'); const response = await fetch(`${this.baseURL}/api/token/refresh/`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refresh_token: refreshToken }), }); if (response.ok) { const data = await response.json(); localStorage.setItem('access_token', data.access_token); } else { // Wyloguj użytkownika localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); window.location.href = '/login'; } } } export default new ApiClient();

Komponent React

JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // Login.jsx import { useState } from 'react'; import apiClient from './api'; function Login() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = async (e) => { e.preventDefault(); try { await apiClient.login(username, password); window.location.href = '/dashboard'; } catch (error) { alert('Login failed'); } }; return ( <form onSubmit={handleSubmit}> <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Username" /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" /> <button type="submit">Login</button> </form> ); } export default Login;

Dobre praktyki integracji

Poniżej znajdziesz zestaw najlepszych praktyk do integracji frontendu z backendem:

1. Ustal spójną konwencję nazw endpointów i struktur JSON

Używaj RESTful conventions:

  • Rzeczowniki w liczbie mnogiej: /api/posts/, /api/users/
  • Czasowniki dla akcji: /api/posts/1/publish/
  • Spójne formaty dat: ISO 8601

2. Waliduj dane po obu stronach

Backend: Waliduj wszystkie dane wejściowe (Pydantic, Django serializers) Frontend: Waliduj przed wysłaniem (react-hook-form, yup)

3. Stosuj HTTPS i JWT z krótkim TTL

  • HTTPS w produkcji (Let's Encrypt)
  • Krótki czas życia access tokena (15-30 min)
  • Długi refresh token (7-30 dni)

4. Używaj HTTP-only cookies dla refresh tokenów

Python
1 2 3 4 5 6 7 8 # FastAPI response.set_cookie( key="refresh_token", value=refresh_token, httponly=True, secure=True, samesite="lax" )

5. Włącz logowanie błędów API

Backend:

Python
1 2 3 4 5 6 7 8 import logging logger = logging.getLogger(__name__) try: # Kod except Exception as e: logger.error(f"API error: {e}") raise HTTPException(status_code=500, detail="Internal server error")

Frontend:

JavaScript
1 2 3 4 5 6 try { const data = await fetch('/api/posts'); } catch (error) { console.error('API Error:', error); // Wyślij do Sentry/LogRocket }

6. Testuj komunikację end-to-end

  • Postman – testowanie API
  • Cypress/Playwright – E2E testy frontendu
  • Integration tests – testy całego flow

7. Używaj TypeScript dla lepszej integracji

TypeScript
1 2 3 4 5 6 7 8 9 10 11 12 13 // types.ts interface Post { id: number; title: string; author: string; created_at: string; } // api.ts async function getPosts(): Promise<Post[]> { const response = await fetch('/api/posts/'); return response.json(); }

8. Implementuj retry logic i error handling

JavaScript
1 2 3 4 5 6 7 8 9 10 11 async function fetchWithRetry(url, options, retries = 3) { for (let i = 0; i < retries; i++) { try { const response = await fetch(url, options); if (response.ok) return response; } catch (error) { if (i === retries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } }

Podsumowanie

Połączenie Pythona (Django/FastAPI) z Reactem to dziś standard w nowoczesnych projektach webowych. Dzięki jasnym zasadom komunikacji (REST, JWT, CORS) możesz tworzyć skalowalne aplikacje SPA, wydajne API i elastyczny proces wdrożeniowy (DevOps-ready).

Najważniejsze punkty:

  • CORS – włącz dla komunikacji między domenami
  • JWT – bezpieczna autoryzacja bezstanowa
  • Spójne API – RESTful endpoints i struktury JSON
  • Dokumentacja – Swagger/OpenAPI dla łatwej współpracy
  • Testowanie – E2E testy całego flow
  • Wersjonowanie/api/v1/, /api/v2/ dla kompatybilności wstecznej

Najważniejsze jest zachowanie spójności i przewidywalności – wtedy współpraca między frontendem i backendem staje się naturalna. Zacznij od podstawowej konfiguracji CORS i JWT, stopniowo dodawaj funkcjonalności i optymalizuj komunikację zgodnie z potrzebami projektu.