StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

RealizacjeO mnieBlog
Porozmawiajmy
PL
EN

Nowoczesne strony internetowe dla firm, które myślą odważnie.

Przewiń do góry

Nazwa

StriveLab Maciej Sala

NIP

6772218995

REGON

524008527

E-mail

contact@strivelab.pl

Usługi główne
  • Tworzenie stron internetowych
  • Strony internetowe Next.js
  • Strony internetowe Astro
  • Strony internetowe React
Inne usługi
  • Usługi
  • Audyt SEO i Performance
  • Testy automatyczne i QA
  • Konsultacje Produktowe
  • Automatyzacja Procesów AI
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
Backend

Backend dla frontendowca: cache, deployment i bezpieczeństwo

Redis, cache HTTP, kolejki, deployment, monitoring, OWASP Top 10:2025 i RODO — backendowa wiedza, którą frontendowiec powinien znać.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
30 lipca 2025 11:45
Czytanie
18 min czytania
Aktualizacja
11 czerwca 2026 10:00

To trzecia część serii „Backend dla frontendowca”. Po fundamentach API oraz tematach real-time, webhooków i uwierzytelniania zostaje warstwa, która decyduje o tym, czy aplikacja wytrzyma prawdziwe użytkowanie: , kolejki, pliki, deployment, monitoring i .

w skrócie

  • Unieważnianie jest trudne — trzymaj w cache'u tylko dane, których świeżość rozumiesz; unieważnianie po zmianach wymaga strategii (TTL, tagi, zdarzenia albo świadome czyszczenie).
  • Kolejki dla operacji blokujących — wysyłka maili, przetwarzanie plików i powiadomienia powinny trafiać do kolejki, nie blokować requestu.
  • Presigned URLs dla plików — klient przesyła pliki bezpośrednio do S3/R2 przez presigned URL, ale backend musi ograniczać typ, rozmiar, ścieżkę i uprawnienia.
  • jako minimum — każdy projekt powinien mieć pipeline, który automatycznie testuje i wdraża; ręczny deployment to błędy w piątek.
  • Trzy filary obserwowalności — logi (co się stało), metryki (ile/jak szybko) i trace (gdzie się zatrzymało) razem dają pełny obraz.
  • Bezpieczeństwo to proces — walidacja, uprawnienia, rate limiting, sekrety, nagłówki, higiena zależności i regularne aktualizacje to podstawa projektu.

To nie są dodatki „na później”, jeśli projekt ma działać poza lokalnym środowiskiem. Cache potrafi zdjąć ogromną część ruchu z bazy, ale może też zwracać nieaktualne dane. Kolejka ratuje przy wolnych operacjach, ale wymaga idempotencji. Deployment bez monitoringu daje tylko wiarę, że wszystko działa.

Notatka

Cache, kolejki i monitoring nie są ozdobą dla dużych systemów. To mechanizmy, które decydują, czy aplikacja degraduje się przewidywalnie, czy zaczyna losowo zwracać stare dane, duplikować zadania i ukrywać błędy produkcyjne.

1. Cache — przyspieszanie odpowiedzi

Cache nie jest tylko optymalizacją na koniec projektu. To sposób na zdjęcie pracy z bazy, API zewnętrznych i serwera, ale też źródło trudnych błędów, jeśli nie wiesz, kiedy dane stają się nieaktualne.

Redis

Redis to in-memory database często używana jako cache, magazyn sesji, kolejka albo mechanizm pub/sub. Warto też znać Valkey, czyli forka Redis wspieranego przez Linux Foundation po zmianach licencyjnych w ekosystemie Redis. W najprostszym wariancie trzymasz w nim wynik wolnego zapytania przez kilka minut:

Code
async function getUsers() {
  // Sprawdź cache
  const cached = await redis.get('users:all')
  if (cached) return JSON.parse(cached)
 
  // Pobierz z bazy
  const users = await db.query('SELECT * FROM users')
 
  // Zapisz w cache na 5 minut
  await redis.set('users:all', JSON.stringify(users), 'EX', 300)
 
  return users
}

W praktyce równie ważna jak samo zapisanie jest strategia unieważniania cache po zmianie danych.

Kiedy cachować?

Cache ma sens tam, gdzie odczyt jest częsty, a zmiana rzadsza albo akceptujesz chwilowo nieświeże dane. Nie cachuj wszystkiego automatycznie, bo każdy cache trzeba potem unieważnić.

  • Dane często czytane, rzadko zmieniane
  • Wolne zapytania do bazy
  • Wywołania zewnętrznych API
  • Obliczenia / agregacje

Strategie cache

Cache-aside () — najczęstsze:

Code
1. Sprawdź cache
2. Brak? → pobierz z bazy → zapisz do cache → zwróć

Write-through — zapis idzie najpierw do cache, potem do bazy. Cache zawsze świeży.

Write-behind — zapis do cache od razu, do bazy asynchronicznie. Najszybsze, ale ryzyko utraty danych.

Stale-while-revalidate — zwróć stary cache od razu, w tle odśwież.

Unieważnianie cache — najtrudniejsza rzecz w programowaniu

"There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton

Problem polega na tym, że cache przyspiesza odczyt, ale oddala Cię od źródła prawdy. Masz trzy podstawowe podejścia:

  1. TTL (expire after N) — proste, przewidywalne, ale dane mogą być nieaktualne przez chwilę.
  2. Event-driven — po UPDATE w bazie wyczyść konkretne klucze (del('user:123')).
  3. Versioning — klucz zawiera wersję (user:123:v5); zmieniasz wersję, stare wpisy zostają, ale nikt ich nie czyta.
Code
// Event-driven invalidation
async function updateUser(id, data) {
  await db.user.update({ where: { id }, data })
  await redis.del(`user:${id}`) // pojedynczy klucz
 
  // Redis DEL nie usuwa po patternie. Klucze list warto zapisywać pod tagiem.
  const listKeys = await redis.smembers('tag:users:list')
  if (listKeys.length > 0) await redis.unlink(...listKeys)
  await redis.del('tag:users:list')
}

Nie używaj KEYS users:list:* na produkcji — może zablokować Redis przy dużej liczbie kluczy. Jeśli musisz usuwać po wzorcu, użyj SCAN partiami albo trzymaj własny indeks kluczy per tag, jak w przykładzie wyżej.

Cache stampede — gdy wszyscy naraz przebijają cache

Cache może też zaszkodzić, gdy popularny klucz wygasa i nagle setki requestów jednocześnie idą do bazy. To cache stampede. Typowe zabezpieczenia:

  • jitter w TTL — dodaj losowe kilka-kilkadziesiąt sekund, żeby klucze nie wygasały naraz,
  • lock per klucz — tylko jeden request odbudowuje cache, reszta czeka albo dostaje stary wynik,
  • stale-while-revalidate — użytkownik dostaje starszą odpowiedź, a backend odświeża cache w tle,
  • pre-warming — najważniejsze klucze odświeżasz zanim wygasną.

HTTP cache — drugi (i pierwszy) poziom

Backend Redis to nie wszystko — przeglądarka i cache'ują na bazie nagłówków HTTP. Frontendowiec widzi to w Network tabie jako (disk cache) / (memory cache) / from CDN.

Code
Cache-Control: public, max-age=3600, stale-while-revalidate=86400
ETag: "abc123"
Last-Modified: Wed, 15 Apr 2026 10:00:00 GMT
HeaderCo robi
Cache-Control: publicCDN może cachować
Cache-Control: privateTylko przeglądarka użytkownika
Cache-Control: no-storeNie cachuj wcale
Cache-Control: no-cacheCachuj, ale zawsze rewaliduj
max-age=3600Cache świeży przez godzinę
s-maxage=3600TTL tylko dla CDN (override max-age)
stale-while-revalidate=NPo expiry serwuj stary, w tle pobierz świeży
stale-if-error=NPo błędzie serwuj stary (graceful degradation)
ETag: "abc"Hash zawartości — przeglądarka pyta "czy jest nowsze?"

Conditional request:

Code
GET /api/posts/1
If-None-Match: "abc123"

→ 304 Not Modified  (zero bajtów body!)

Trzy poziomy cache

Code
Browser cache  →  CDN (Cloudflare, Fastly)  →  Redis (backend)  →  Database
   100ms              30ms                       2ms                100ms+

Dobra aplikacja cachuje na każdym poziomie, gdzie ma to sens.

Next.js / framework caches

Nowoczesne frameworki mają własne warstwy:

  • Next.js Data Cache — cachuje wyniki fetch() z revalidate
  • Next.js Full Route Cache — całe HTML strony statycznej
  • React cache() — deduplikacja w obrębie jednego renderu
  • use cache — aktualny model cache w Next.js 16 przy włączonych Cache Components
  • unstable_cache — starsze API z tagami i rewalidacją, nadal spotykane w projektach sprzed migracji
Code
import { cacheTag, revalidateTag } from 'next/cache'
 
export async function getPosts() {
  'use cache'
  cacheTag('posts')
  return db.post.findMany()
}
 
// Po update:
revalidateTag('posts')

W Next.js 16 unstable_cache zostało zastąpione przez dyrektywę use cache. Ważna zasada pozostaje taka sama: nie czytaj cookies() ani headers() bezpośrednio we współdzielonym zakresie cache. Dane zależne od użytkownika przekaż jako argument albo użyj mechanizmu przeznaczonego do prywatnego cache.

2. Kolejki i zadania w tle

Nie każdy proces powinien kończyć się w czasie jednego requestu. Wysyłka maila, przeliczenie raportu, generowanie PDF-a, synchronizacja CRM czy przetwarzanie płatności mogą trwać za długo albo zależeć od zewnętrznego systemu. Kolejka pozwala szybko odpowiedzieć użytkownikowi, a ciężką pracę wykonać w tle.

Code
// Zamiast:
app.post('/api/send-email', async (req, res) => {
  await sendEmail(req.body) // 2-3 sekundy!
  res.json({ success: true })
})
 
// Lepiej:
app.post('/api/send-email', async (req, res) => {
  await queue.add('send-email', req.body) // Natychmiast
  res.json({ queued: true })
})
 
// Worker przetwarza w tle
queue.process('send-email', async (job) => {
  await sendEmail(job.data)
})

Popularne: BullMQ (Redis), RabbitMQ, AWS SQS, Inngest, Trigger.dev (event-driven, type-safe).

Idempotencja i deduplikacja zadań

Kolejka nie daje automatycznie gwarancji „dokładnie raz”. W praktyce wiele systemów działa w modelu at-least-once, czyli job może zostać wykonany więcej niż raz: po retry, restarcie workera albo chwilowej awarii. Dlatego zadania muszą być idempotentne.

Code
await queue.add(
  'send-email',
  { userId, template: 'welcome' },
  { jobId: `welcome-email:${userId}` },
)

Po stronie workera nadal warto zapisać efekt w bazie:

Code
async function processWelcomeEmail(job) {
  const alreadySent = await db.emailLog.findUnique({
    where: { key: `welcome-email:${job.data.userId}` },
  })
 
  if (alreadySent) return
 
  await sendEmail(job.data)
  await db.emailLog.create({
    data: { key: `welcome-email:${job.data.userId}` },
  })
}

Reguła: retry ma naprawiać błędy przejściowe, a nie tworzyć duplikaty.

Cron / zadania cykliczne

Cron to praca cykliczna: raport nocny, czyszczenie cache, naliczenie subskrypcji, przypomnienia mailowe. Prosty mechanizm, ale w produkcji musi być idempotentny i monitorowany, bo dwa równoległe uruchomienia potrafią narobić szkód.

Code
// node-cron
import cron from 'node-cron'
 
cron.schedule('0 2 * * *', async () => {
  // codziennie o 2:00
  await sendDailyDigest()
})

Reguły dla cron:

  • Idempotency — zadanie może odpalić się dwa razy (deploy, retry)
  • Lock — przy wielu instancjach tylko jedna ma wykonać (Redis lock, advisory lock w Postgres)
  • Monitoring — alert jeśli zadanie się nie wykonało (Healthchecks.io, Better Stack)

W produkcji użyj zarządzanej platformy: Vercel Cron, Cloudflare Cron Triggers, Inngest, GitHub Actions (cron + curl), Kubernetes CronJob.

Wzorce przetwarzania

  • Fan-out — jedno zdarzenie → wiele jobów (np. nowy użytkownik → wyślij e-mail + utwórz workspace + zapisz w CRM)
  • Pipeline — job A → job B → job C
  • Retry with backoff — przy błędzie spróbuj ponownie z opóźnieniem (1s, 2s, 4s, 8s...)
  • Dead letter queue — joby, które padły N razy, lądują na DLQ do manualnej analizy

Do tego dodaj alert: jeśli dead letter queue rośnie, system może dalej odpowiadać 200, ale biznesowo przestaje wykonywać pracę.

3. File storage

Pliki mają inne wymagania niż rekordy w bazie. Obrazy, dokumenty, avatary i eksportowane PDF-y zwykle nie powinny trafiać bezpośrednio do relacyjnej bazy danych. Najczęściej zapisujesz je w object storage, a w bazie trzymasz tylko metadane i ścieżkę.

Code
const key = `uploads/${filename}`
 
await storage.putObject({
  bucket: 'my-bucket',
  key,
  body: fileBuffer,
  contentType: mimeType,
})
 
// Zapisz URL w bazie
await db.user.update({
  where: { id: userId },
  data: { avatar: `https://cdn.example.com/${key}` },
})

Popularne: AWS S3, Cloudflare R2 (zero egress fee!), Google Cloud Storage, Backblaze B2, MinIO (hostowane samodzielnie).

Presigned URLs — upload bezpośrednio z frontu

W większych systemach backend nie powinien przepuszczać każdego pliku przez siebie. Zamiast tego generuje presigned URL, a frontend uploaduje plik bezpośrednio do storage. Backend kontroluje uprawnienia i zapisuje metadane, ale nie musi trzymać całego pliku w pamięci.

Code
// Backend — generuje presigned URL
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import { PutObjectCommand } from '@aws-sdk/client-s3'
 
app.post('/api/upload-url', async (req, res) => {
  const userId = req.user.id
  const filename = req.body.filename.replace(/[^a-z0-9._-]/gi, '-')
  const key = `uploads/${userId}/${crypto.randomUUID()}-${filename}`
 
  const url = await getSignedUrl(
    s3,
    new PutObjectCommand({ Bucket, Key: key, ContentType: req.body.type }),
    { expiresIn: 300 }, // 5 minut
  )
 
  res.json({ url, key })
})
 
// Frontend — upload bezpośrednio do S3
const { url, key } = await fetch('/api/upload-url', {
  method: 'POST',
  body: JSON.stringify({ filename: file.name, type: file.type }),
}).then((res) => res.json())
 
await fetch(url, {
  method: 'PUT',
  body: file,
  headers: { 'Content-Type': file.type },
})
 
await fetch('/api/save-meta', { method: 'POST', body: JSON.stringify({ key }) })

Plusy:

  • Backend nie przepuszcza pliku przez siebie — oszczędza pamięć i transfer
  • Skalowalne — S3 wytrzyma więcej niż Twój serwer
  • Szybsze dla użytkownika — brak przejścia przez backend

Bezpieczeństwo uploadu

Presigned URL nie znaczy „dowolny upload”. Backend nadal musi narzucić zasady:

  • allowlista MIME — np. tylko image/jpeg, image/png, image/webp, application/pdf,
  • limit rozmiaru — najlepiej egzekwowany polityką uploadu albo po stronie storage,
  • bezpieczny klucz — nie ufaj nazwie pliku od użytkownika; generuj własny prefiks i UUID,
  • prywatny bucket domyślnie — publiczny dostęp dawaj tylko tam, gdzie naprawdę jest potrzebny,
  • walidacja po uploadzie — sprawdź metadata, rozmiar i typ zanim zapiszesz plik jako aktywny,
  • skanowanie plików — szczególnie przy PDF-ach, dokumentach i uploadach dostępnych dla innych użytkowników,
  • osobny etap publikacji — upload może trafić do katalogu tymczasowego, a dopiero worker przenosi go do docelowej lokalizacji.

CDN dla statycznych plików

Pliki publiczne (avatary, obrazki postów) idą zwykle przez CDN — Cloudflare, Bunny, CloudFront:

Code
Origin: https://my-bucket.s3.amazonaws.com/uploads/avatar.jpg
CDN:    https://cdn.example.com/uploads/avatar.jpg  (bliżej użytkownika, cache'owane)

Optymalizacja obrazów

Surowe obrazy są ciężkie. Najpopularniejsze podejścia:

  • Next.js Image — automatyczne resize, WebP / AVIF, lazy loading
  • Cloudinary / imgix / Cloudflare Images — managed image CDN z transformacjami w URL
  • Sharp (Node) — backend-side resize / optimize przy upload

4. Środowiska i deployment

Kod działający lokalnie to dopiero początek. Backend musi mieć środowiska, sekrety, migracje, monitoring, backupy i powtarzalny deployment. Bez tego każda zmiana na produkcji zaczyna przypominać ręczną operację na żywym organizmie.

Środowiska

Code
Development (localhost)
    ↓
Staging (testowanie)
    ↓
Production (użytkownicy)

Zmienne środowiskowe

Code
# .env
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
JWT_SECRET=super-secret-key
REDIS_URL=redis://localhost:6379
Code
const dbUrl = process.env.DATABASE_URL

Nigdy nie commituj .env do repo!

Opcje deploymentu

Nie ma jednego najlepszego hostingu dla backendu. Wybór zależy od tego, czy chcesz prostoty, kontroli, globalnego edge, czy przewidywalnych kosztów.

Platform as a Service (łatwe):

  • Vercel (Next.js)
  • Railway
  • Render
  • Heroku

Container (więcej kontroli):

  • Docker + Kubernetes
  • AWS ECS
  • Google Cloud Run

VPS (pełna kontrola):

  • DigitalOcean
  • AWS EC2
  • Hetzner (Niemcy, świetna cena/wydajność)
  • Coolify (PaaS hostowany samodzielnie na własnym VPS)

Edge / :

  • Cloudflare Workers
  • Vercel Edge Functions
  • Deno Deploy
  • AWS Lambda + API Gateway
  • Fly.io (globalne VM)

CI/CD — automatyzacja deploymentu

Deploy nie powinien zależeć od tego, kto ma akurat poprawnie skonfigurowany laptop. Pipeline w GitHub Actions albo GitLab CI sprawia, że testy, build i wdrożenie są powtarzalne:

Code
# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test
      - run: npm run lint
 
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }}

Minimum sensownego pipeline'u:

  1. Instalacja zależności z lockfile (npm ci, pnpm install --frozen-lockfile).
  2. Lint, testy jednostkowe i testy typów.
  3. Build produkcyjny.
  4. Skan sekretów i zależności.
  5. Migracje bazy w kontrolowanym kroku.
  6. Deploy.
  7. Smoke test po wdrożeniu.

Strategie deploymentu

  • Blue-green — dwa środowiska, switch ruchu w jednej chwili (zero downtime)
  • Canary — nowa wersja dostaje 1% ruchu, jeśli nie pada → 10% → 50% → 100%
  • Rolling — instancje aktualizowane po kolei (Kubernetes default)
  • Feature flags — kod wdrożony, ale wyłączony; włączany per użytkownik albo kohorta (LaunchDarkly, Unleash, GrowthBook)

Migracje bazy bez downtime

Najczęstszy błąd przy deploymentach backendu to założenie, że kod i baza zmienią się dokładnie w tej samej sekundzie. W realnym wdrożeniu przez chwilę mogą działać dwie wersje kodu: stara i nowa. Migracje muszą być z nimi kompatybilne.

Bezpieczny wzorzec to expand → migrate → contract:

  1. Expand — dodaj nową kolumnę/tabelę jako opcjonalną, bez usuwania starego pola.
  2. Deploy kodu — nowa wersja potrafi pisać do starego i nowego modelu albo czytać oba.
  3. Backfill — uzupełnij dane w tle, partiami.
  4. Przełącz odczyt — aplikacja zaczyna czytać z nowej struktury.
  5. Contract — dopiero po czasie usuń stare pole.

Unikaj migracji, które długo blokują tabele w godzinach ruchu. Przy dużych tabelach indeksy, backfill i zmiany NOT NULL trzeba planować osobno.

Health checks, readiness i rollback

Backend powinien mieć przynajmniej dwa endpointy kontrolne:

Code
GET /healthz  → proces żyje
GET /readyz   → proces może obsługiwać ruch (baza, Redis, migracje OK)

Po wdrożeniu uruchom smoke test: zalogowanie testowego użytkownika, podstawowy endpoint API, zapis/odczyt z bazy i jeden krytyczny flow biznesowy. Rollback też musi być przećwiczony: jeśli nowy kod jest cofany, baza nadal musi być kompatybilna z poprzednią wersją aplikacji.

Backups i disaster recovery

Backup jest użyteczny dopiero wtedy, gdy potrafisz go odtworzyć. Sama informacja „mamy backupy” niewiele znaczy, jeśli nikt nigdy nie sprawdził restore'u.

  • Automatyczne backupy bazy (PostgreSQL: pg_dump, point-in-time recovery)
  • Test restore — backup, którego nie odtworzyłeś, nie istnieje
  • RTO (Recovery Time Objective) — jak długo system może być niedostępny
  • RPO (Recovery Point Objective) — ile danych możesz stracić

5. Monitoring i logging

Bez monitoringu backend jest czarną skrzynką. Widzisz tylko, że użytkownik zgłasza problem, ale nie wiesz, czy zawiodła baza, zewnętrzne API, deployment, cache czy konkretna ścieżka w kodzie.

Logi

Code
// Prosty logging
console.log('User created:', userId)
 
// Strukturalny logging
logger.info('User created', {
  userId,
  email,
  timestamp: new Date(),
})

Narzędzia: Winston, Pino, Datadog, Papertrail

Monitoring

Monitoring odpowiada na pytanie, czy system działa zdrowo. Logi mówią, co się wydarzyło w konkretnym przypadku, a metryki pokazują trend i skalę problemu.

  • Uptime — czy serwer działa?
  • Latency — jak szybko odpowiada?
  • Errors — ile błędów 5xx?
  • Resources — CPU, RAM, disk

Narzędzia: Grafana, Datadog, New Relic, Sentry (errors), Better Stack, Honeycomb.

Trzy filary observability

  1. Logs — co się stało (event-by-event)
  2. Metrics — ile czego (latency, throughput, errors)
  3. Traces — pełna ścieżka requestu przez system (request-id przez wszystkie usługi)

Minimum praktyczne dla frontendowca i backendowca to wspólny request ID. Backend zwraca go w nagłówku, frontend dołącza do raportu błędu, a logi pozwalają znaleźć dokładny request.

Code
X-Request-ID: req_01JABC123

Distributed tracing

W większych systemach pojedynczy request często przechodzi przez kilka usług. Bez trace'u widzisz tylko „endpoint trwa 800 ms”. Z trace'em widzisz, że 600 ms zjada konkretne zapytanie do bazy albo jedna integracja zewnętrzna.

Code
[trace-id: abc] frontend → API gateway → user service → auth service → database
                  120ms    8ms           45ms           12ms          50ms

Standard: OpenTelemetry (otel) — agnostic; eksportuje do Datadog, Honeycomb, Tempo, Jaeger.

Alerty — co budzi w nocy

Alert powinien odpowiadać na objaw, nie na przyczynę. SRE Google wyznacza tzw. "Four Golden Signals":

  • Latency — p50 / p95 / p99 czasu odpowiedzi
  • Traffic — RPS (requests per second)
  • Errors — % błędów 5xx
  • Saturation — wykorzystanie CPU, RAM, disku, pool connections

Alert niech budzi tylko wtedy, gdy użytkownik faktycznie odczuwa problem. Reszta to ticket „do obejrzenia”.

Frontend → backend observability

Frontend też powinien wysyłać błędy i metryki. Sentry, LogRocket, Datadog RUM pokazują, jak użytkownik dochodzi do błędu. Powiąż user-id / request-id między frontem a backendem, żeby widzieć całą ścieżkę.

6. Bezpieczeństwo

Bezpieczeństwo backendu nie polega na jednej bibliotece ani checkliście odhaczonej przed wdrożeniem. To zestaw nawyków: walidujesz dane, ograniczasz uprawnienia, nie ufasz klientowi, logujesz zdarzenia i zakładasz, że każdy publiczny endpoint będzie testowany przez kogoś nieżyczliwego.

Podstawowe zasady

  1. Waliduj input — nigdy nie ufaj danym od użytkownika
  2. Parametryzuj zapytania — unikaj injection
  3. Hashuj hasła — bcrypt, argon2
  4. HTTPS everywhere — szyfruj transmisję
  5. Rate limiting — ogranicz requesty
  6. Autoryzuj na backendzie — może ukryć przycisk, ale nie może być źródłem prawdy
  7. CORS — kontroluj, które originy mogą czytać odpowiedzi z API
Code
// ❌ SQL Injection
db.query(`SELECT * FROM users WHERE id = ${userId}`)
 
// ✅ Parametryzowane zapytanie
db.query('SELECT * FROM users WHERE id = $1', [userId])
Code
// ❌ Przechowywanie hasła
user.password = 'password123'
 
// ✅ Hashowanie
user.passwordHash = await bcrypt.hash('password123', 10)

Rate limiting w praktyce

Code
import rateLimit from 'express-rate-limit'
 
// Globalny limit
app.use(
  '/api/',
  rateLimit({
    windowMs: 60 * 1000, // okno 1 minuta
    max: 60, // 60 requestów / minutę / IP
    standardHeaders: true, // X-RateLimit-* + Retry-After
    legacyHeaders: false,
    message: {
      type: 'about:blank',
      title: 'Too Many Requests',
      status: 429,
      detail: 'Try again in a moment',
    },
  }),
)
 
// Bardziej restrykcyjny dla loginu
app.post(
  '/api/auth/login',
  rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minut
    max: 5, // 5 prób
    skipSuccessfulRequests: true,
  }),
  loginHandler,
)

Frontend reaguje na 429:

Code
async function fetchWithRetry(url, opts, attempt = 0) {
  const res = await fetch(url, opts)
  if (res.status === 429 && attempt < 3) {
    const retryAfter = Number(res.headers.get('Retry-After')) || 2 ** attempt
    await new Promise((r) => setTimeout(r, retryAfter * 1000))
    return fetchWithRetry(url, opts, attempt + 1)
  }
  return res
}

W produkcji rate limiting zwykle siedzi na CDN / WAF (Cloudflare, AWS WAF), nie w aplikacji — chroni przed DDoS i nie zużywa zasobów backendu.

CORS to nie autoryzacja

CORS mówi przeglądarce, czy JavaScript z danego origina może odczytać odpowiedź. Nie blokuje curl, aplikacji mobilnej, backendu ani atakującego, który woła API bezpośrednio. Dlatego CORS jest warstwą kontroli przeglądarki, a nie mechanizmem uprawnień.

Code
app.use(
  cors({
    origin: ['https://app.example.com'],
    credentials: true,
  }),
)

Backend nadal musi sprawdzić sesję, token, role i właściciela zasobu.

OWASP Top 10:2025 — must-know

OWASP Top 10 to lista najważniejszych kategorii ryzyk aplikacji webowych. Aktualną wersją jest OWASP Top 10:2025. Nie musisz znać każdego scenariusza ataku na pamięć, ale jako frontendowiec powinieneś rozumieć, które problemy są rozwiązywane wyłącznie po stronie backendu.

  1. Broken Access Control — sprawdzanie uprawnień na backendzie, nie tylko ukrywanie przycisków w UI.
  2. Security Misconfiguration — debug w produkcji, otwarte buckety, domyślne hasła, złe nagłówki.
  3. Software Supply Chain Failures — zależności, build pipeline, lockfile, paczki npm, obrazy Dockera.
  4. Cryptographic Failures — HTTPS wszędzie, bezpieczne algorytmy, brak danych wrażliwych w logach.
  5. Injection — SQL injection, XSS, NoSQL injection, command injection.
  6. Insecure Design — np. brak rate limitu na reset hasła albo brak modelu uprawnień.
  7. Authentication Failures — słabe hasła, brak MFA, długie sesje, zły reset hasła.
  8. Software or Data Integrity Failures — niepodpisane artefakty, niekontrolowane aktualizacje, zaufanie do niezweryfikowanych danych.
  9. Security Logging and Alerting Failures — nie wiesz, że trwa atak albo wyciek.
  10. Mishandling of Exceptional Conditions — wyjątki ujawniają dane, pomijają autoryzację albo zostawiają system w złym stanie.

SSRF nie zniknął jako problem — w OWASP Top 10:2025 został włączony w szerszą kategorię Broken Access Control.

XSS — Cross-Site Scripting

Atakujący wstrzykuje JS w kontekst Twojej domeny (komentarz, profil). Skradzione cookies, wyświetlone fałszywe formularze.

Obrona:

  • Escapuj output — React i większość frameworków robi to domyślnie. Uważaj na dangerouslySetInnerHTML.
  • Content-Security-Policy (CSP) — header, który mówi przeglądarce, skąd wolno ładować skrypty:
    Code
    Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com
    
  • Sanityzuj HTML od użytkownika — DOMPurify, sanitize-html

Walidacja input — nigdy nie ufaj klientowi

Walidacja w formularzu poprawia UX. Walidacja na serwerze chroni system. Użytkownik może wyłączyć JavaScript, zmodyfikować payload w DevTools albo wysłać request z własnego skryptu.

Code
import { z } from 'zod'
 
const CreateUserSchema = z.object({
  email: z.string().email().max(255),
  age: z.number().int().min(13).max(120),
  role: z.enum(['user', 'admin']),
})
 
app.post('/api/users', (req, res) => {
  const result = CreateUserSchema.safeParse(req.body)
  if (!result.success) {
    return res.status(422).json({
      type: 'about:blank',
      title: 'Validation failed',
      status: 422,
      errors: result.error.flatten().fieldErrors,
    })
  }
  // result.data jest typowane i zwalidowane
})

Reguła: klient waliduje dla UX, serwer waliduje dla bezpieczeństwa.

Mass assignment

Code
// ❌ Niebezpieczne — użytkownik może wysłać { role: 'admin' } w body
await db.user.update({ where: { id }, data: req.body })
 
// ✅ Tylko allowlistowane pola
const { name, bio } = req.body
await db.user.update({ where: { id }, data: { name, bio } })

Secret management

Sekrety nigdy nie idą do repo, nawet do prywatnego.

  • Lokalnie: .env + .gitignore, direnv, 1Password CLI
  • CI/CD: GitHub Secrets, GitLab CI Variables
  • Produkcja: AWS Secrets Manager, HashiCorp Vault, Doppler, Infisical
  • Rotacja — sekrety powinny mieć datę ważności (klucze API, hasła do bazy)

Jeśli sekret kiedyś trafił do repo (nawet usuniętego commita) — uznaj go za skompromitowany i wymień. Git history żyje wiecznie.

Supply chain i zależności

Bezpieczeństwo zależy też od tego, co instalujesz i jak budujesz aplikację:

  • commituj lockfile i instaluj zależności komendą deterministyczną (npm ci, pnpm --frozen-lockfile),
  • włącz Dependabot albo Renovate,
  • używaj npm audit z rozsądkiem: nie każda podatność dotyczy ścieżki produkcyjnej, ale krytycznych nie ignoruj,
  • pinuj obrazy Dockera do wersji albo digestu,
  • skanuj sekrety w CI,
  • ogranicz uprawnienia tokenów CI/CD do minimum,
  • nie uruchamiaj skryptów z internetu bez sprawdzenia, co robią.

SSRF — szczególnie przy URL-ach od użytkownika

SSRF pojawia się, gdy backend pobiera URL podany przez użytkownika: import obrazka, webhook testowy, parser Open Graph, PDF generator. Atakujący może próbować zmusić serwer do połączenia z adresem wewnętrznym, np. metadata service w chmurze.

Obrona:

  • allowlista hostów albo domen,
  • blokada prywatnych adresów IP (127.0.0.1, 10.0.0.0/8, 169.254.169.254, IPv6 local),
  • limit rozmiaru odpowiedzi i timeout,
  • brak przekazywania sekretów do pobieranego URL,
  • osobny worker/sandbox do pobierania nieufnych zasobów.

Security headers

Standardowe headery, które dodaje helmet:

Code
import helmet from 'helmet'
app.use(helmet())

Zwraca między innymi:

  • Strict-Transport-Security — wymuszaj HTTPS przez N dni (HSTS)
  • X-Content-Type-Options: nosniff — przeglądarka nie zgaduje MIME
  • X-Frame-Options: DENY — chroni przed clickjacking
  • Referrer-Policy: strict-origin-when-cross-origin
  • Permissions-Policy — wyłącza geolokalizację, kamerę itd. domyślnie

Sprawdź swoje headery na securityheaders.com.

GDPR / RODO — minimalne minimum

Jeśli zbierasz dane od użytkowników z UE:

  • Cel przetwarzania — wiedz, po co masz dane
  • Right to access / erasure — endpoint pozwalający exportować i kasować dane
  • Pseudonimizacja — adres IP w logach maskuj
  • Consent — banner cookie dla narzędzi analitycznych
  • DPA — umowa powierzenia z każdym dostawcą przetwarzającym dane

Co dalej? — ścieżka nauki

Nie próbuj uczyć się backendu przez czytanie wszystkiego naraz. Najszybciej rośnie się przez mały projekt, który dotyka kilku prawdziwych problemów: logowania, bazy, walidacji, uploadu, webhooka i deploymentu. Poniższa ścieżka układa tematy od fundamentów do architektury.

Poziom 1: Fundamenty

  1. HTTP — request / response, kody statusu, nagłówki
  2. Node.js + Express (lub Hono / Fastify)
  3. SQL podstawy — CRUD, JOIN, indeksy, transakcje
  4. design — zasoby, metody, statusy HTTP
  5. CORS — co to, kiedy psuje requesty, jak konfigurować

Poziom 2: Praktyka

  1. PostgreSQL + Prisma / Drizzle
  2. Autentykacja — sesje vs JWT, cookies, CSRF
  3. Walidacja inputu (Zod) i strukturalne błędy (RFC 9457)
  4. Deployment — Vercel / Railway / Render
  5. Webhooks — przyjmowanie i wysyłanie

Poziom 3: Zaawansowane

  1. Redis / Valkey — strategie cache, pub/sub, kolejki (BullMQ)
  2. Real-time — i WebSockets
  3. Docker + pipeline CI/CD
  4. Monitoring / logging / tracing (OpenTelemetry, Sentry)
  5. Connection pooling / serverless drivery (PgBouncer, Neon)

Poziom 4: Architektura

  1. Microservices vs monolith
  2. Event-driven architecture, kolejki
  3. CQRS, Event Sourcing
  4. Distributed systems — CAP theorem, eventual consistency
  5. Testy obciążeniowe (k6, Artillery)

Projekt do nauki

Najlepszy projekt do nauki nie musi być oryginalny. Ma zmusić Cię do przejścia przez typowe decyzje backendowe. Dobrym kandydatem jest prosty blog z autentykacją:

  • Rejestracja / logowanie (sesja albo JWT)
  • postów z paginacją
  • Komentarze (real-time z SSE — bonus)
  • Upload obrazów (presigned URLs do S3 / R2)
  • Webhook handler (Stripe sandbox jako „płatne posty”)
  • Deployment + CI/CD
  • Sentry / monitoring

Taki projekt nie wygląda efektownie na pierwszy rzut oka, ale pokrywa większość problemów, które wracają w komercyjnych aplikacjach: stan użytkownika, dane, uprawnienia, pliki, integracje, błędy i deployment.

KomponentRolaPopularne (2026)
SerwerLogika aplikacjiExpress, Fastify, Hono, NestJS
RuntimeWykonanie koduNode.js, Bun, Deno, Cloudflare Workers
Baza danychPrzechowywaniePostgreSQL, MySQL, MongoDB, SQLite (Turso)
ORM / query builderAbstrakcja bazyPrisma, Drizzle, Kysely
CachePrzyspieszanieRedis, Valkey, Memcached, HTTP cache
AuthBezpieczeństwoAuth.js, Clerk, Supabase Auth, Better Auth
API styleKomunikacjaREST, GraphQL, tRPC
Real-timePush z serweraSSE, WebSockets, Pusher
StoragePlikiS3, Cloudflare R2, object storage + CDN
KolejkiZadania w tleBullMQ, Inngest, Trigger.dev, SQS
WalidacjaBezpieczny inputZod, Valibot, Yup
DeploymentHostingVercel, Railway, Fly.io, Render, Cloudflare
ObservabilityCo się dziejeSentry, Datadog, Better Stack, Honeycomb

Werdykt Labu

Jeżeli masz zapamiętać z tej serii jedną rzecz, niech będzie nią ta: backend to nie osobna magia, tylko zestaw warstw, które muszą ze sobą sensownie współpracować. Frontendowiec nie musi znać każdej z nich na poziomie senior backend engineera, ale powinien rozumieć, gdzie leży odpowiedzialność i jak rozmawiać o problemach.

Najczęstsze błędy wychodzą dopiero przy prawdziwym ruchu: traktowanie wszystkich błędów 4xx tak samo, brak paginacji, POST bez idempotency key, bez refresh rotation, pomijanie walidacji po stronie serwera. Każdy z nich da się uniknąć, jeśli znasz fundamenty — a fundamenty są po to, żeby budować na nich, a nie odkrywać je w środku produkcyjnego incydentu.

Zacznij od prostego API, dodawaj kolejne elementy dopiero wtedy, gdy rozumiesz, po co są potrzebne. Jako frontendowiec nie musisz być backendowcem, ale musisz rozumieć, co dzieje się po drugiej stronie — to jedna z najkrótszych dróg do lepszych decyzji produktowych i spokojniejszego debugowania.

Audyt techniczny i optymalizacja pod kątem SEO i GEO.
SEO & Performance

Wybierasz warstwę danych? Porównaj Drizzle ORM i Prisma przed wdrożeniem aplikacji.

Pozostałe części serii

  • Backend dla frontendowca: serwer, bazy danych i API
  • Backend dla frontendowca: auth, real-time i integracje
  • 1. Cache — przyspieszanie odpowiedzi3 min
  • 2. Kolejki i zadania w tle2 min
  • 3. File storage2 min
  • 4. Środowiska i deployment3 min
  • 5. Monitoring i logging2 min
  • 6. Bezpieczeństwo4 min
  • Co dalej? — ścieżka nauki3 min
  • Werdykt Labu1 min
  • Pozostałe części serii1 min

Często zadawane pytania

Źródła i dokumentacja dla całej seriiZweryfikowano: 11 czerwca 2026

Materiały wykorzystane do weryfikacji artykułu „Backend dla frontendowca: cache, deployment i bezpieczeństwo”:

MDN: An overview of HTTP, MDN: CORS, MDN: HTTP cookies, MDN: HTTP caching, Next.js: use cache, Next.js: unstable_cache, Node.js Learn, PostgreSQL Tutorial, Use The Index, Luke! — SQL performance, Prisma docs, REST API design — Microsoft, JSON:API specification, RFC 9457: Problem Details for HTTP APIs, Stripe API design — postmortem, OWASP Top 10:2025, OWASP Authentication Cheat Sheet, OWASP Cross-Site Request Forgery Prevention, web.dev: Security headers, MDN: Server-sent events, MDN: WebSockets API, Google SRE: Monitoring distributed systems, Martin Fowler: Microservices, Redis: What is Valkey?.

Seria

Backend dla frontendowca
Część 3 / 3
  1. 1Backend dla frontendowca: serwer, bazy danych i API
  2. 2Backend dla frontendowca: auth, real-time i integracje
  3. Backend dla frontendowca: cache, deployment i bezpieczeństwo
Maciej Sala

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty side-projecty.

Moje artykułyWięcej o mnie

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Backend dla frontendowca: serwer, bazy danych i API
Backend dla frontendowca: serwer, bazy danych i API

Pierwsza część serii Backend dla frontendowca: architektura aplikacji, serwer, bazy danych, API, statusy HTTP, paginacja, idempotency, BFF i CORS.

Maciej Sala

Maciej Sala

Founder Strivelab

28 lipca 2025
Backend dla frontendowca: auth, real-time i integracje
Backend dla frontendowca: auth, real-time i integracje

Druga część serii Backend dla frontendowca: SSE, WebSockets, polling, webhooki, integracje zewnętrzne, sesje, JWT, cookies, CSRF, refresh token rotation i MFA.

Maciej Sala

Maciej Sala

Founder Strivelab

29 lipca 2025
REST API — zasady projektowania i dobre praktyki
REST API — zasady projektowania i dobre praktyki

REST API zaprojektowane naprędce wróci do Ciebie z długiem. Konwencje, wersjonowanie i obsługa błędów — zasady, których tutoriale zwykle pomijają.

Maciej Sala

Maciej Sala

Founder Strivelab

5 grudnia 2025

Poprzedni wpis

Backend dla frontendowca: auth, real-time i integracje
Backend dla frontendowca: auth, real-time i integracje

Druga część serii Backend dla frontendowca: SSE, WebSockets, polling, webhooki, integracje zewnętrzne, sesje, JWT, cookies, CSRF, refresh token rotation i MFA.

Maciej Sala

Maciej Sala

Founder Strivelab

29 lipca 2025

Następny wpis

Dlaczego doświadczeni programiści rzadziej wybierają React z automatu
Dlaczego doświadczeni programiści rzadziej wybierają React z automatu

React nadal ma sens, ale nie powinien być domyślną odpowiedzią na każdy projekt. Kiedy vanilla JS, Astro i HTMX wystarczą, a kiedy React nadal wygrywa?

Maciej Sala

Maciej Sala

Founder Strivelab

4 sierpnia 2025