Pytania rekrutacyjne Express.js

56 pytań — 20 odpowiedzi dostępnych od razu

Ten darmowy podgląd pytań rekrutacyjnych Express.js jest przeznaczony dla osób przygotowujących się do rozmowy o budowaniu backendu w Node.js. Pozwala sprawdzić przykładowe pytania i odpowiedzi z pełnej puli 56 zagadnień.

Materiał dotyczy podstaw Express.js oraz bardziej zaawansowanych wzorców i architektury aplikacji. Pomaga przećwiczyć rozmowę o routingu, middleware, obsłudze błędów i organizacji kodu serwera.

Darmowy start

Pierwsze 20 odpowiedzi są dostępne od razu

Przeglądaj pełną listę 56 pytań. Pełne odpowiedzi, przykłady kodu i materiały do nauki odblokujesz w panelu.

Odblokuj 36 odpowiedzi

Podstawy Express.js

Czym jest Express.js i jakie problemy rozwiązuje w ekosystemie Node.js?

Odpowiedź w 30 sekund: Express.js to minimalistyczny framework webowy dla Node.js, który upraszcza tworzenie serwerów HTTP i API. Rozwiązuje problem niskiego poziomu abstrakcji natywnego modułu HTTP poprzez dostarczenie intuicyjnego API do routingu, middleware i obsługi zapytań/odpowiedzi.

Odpowiedź w 2 minuty: Express.js został stworzony jako warstwa abstrakcji nad natywnym modułem HTTP w Node.js, który wymaga dużo boilerplate'u do wykonania podstawowych zadań. Framework ten rozwiązuje kilka kluczowych problemów:

Po pierwsze, upraszcza routing - zamiast ręcznie parsować URL i metody HTTP, Express oferuje proste metody jak app.get(), app.post() itp. Po drugie, wprowadza koncepcję middleware - funkcji pośredniczących, które przetwarzają zapytania w łańcuchu, co pozwala na modularną organizację kodu (parsowanie body, autoryzacja, logowanie). Po trzecie, standaryzuje obsługę zapytań i odpowiedzi poprzez rozbudowane obiekty req i res z użytecznymi metodami.

Express jest "unopinionated" - nie narzuca struktury projektu czy baz danych, dając deweloperom swobodę wyboru. Dzięki ogromnej społeczności dostępne są tysiące gotowych middleware do różnych zadań. Jest to de facto standard dla Node.js backend development, używany zarówno w małych API jak i dużych aplikacjach enterprise.

Warto zauważyć, że Express jest jednowątkowy (jak Node.js), co oznacza asynchroniczne przetwarzanie zapytań bez blokowania - idealnie nadaje się do aplikacji I/O-intensive, ale może wymagać dodatkowej konfiguracji dla zadań CPU-intensive.

Przykład kodu:

// Natywny Node.js HTTP - dużo kodu dla prostego serwera
const http = require('http');

const server = http.createServer((req, res) => {
  // Ręczne parsowanie URL i metod
  if (req.url === '/api/users' && req.method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ users: [] }));
  } else if (req.url === '/api/users' && req.method === 'POST') {
    // Ręczne zbieranie body z chunków
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', () => {
      res.writeHead(201);
      res.end('Created');
    });
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
});

server.listen(3000);

// Express.js - ten sam funkcjonalność w prostszy sposób
const express = require('express');
const app = express();

// Automatyczne parsowanie JSON
app.use(express.json());

// Prosty, czytelny routing
app.get('/api/users', (req, res) => {
  res.json({ users: [] });
});

app.post('/api/users', (req, res) => {
  // Body już sparsowane w req.body
  const newUser = req.body;
  res.status(201).send('Created');
});

app.listen(3000);

Materiały:

Jak utworzyć podstawową aplikację Express i uruchomić serwer HTTP?

Odpowiedź w 30 sekund: Aby utworzyć aplikację Express, należy zainstalować pakiet (npm install express), zaimportować go, utworzyć instancję aplikacji (const app = express()), zdefiniować route'y, a następnie uruchomić serwer metodą app.listen() podając numer portu.

Odpowiedź w 2 minuty: Tworzenie aplikacji Express składa się z kilku kroków. Najpierw inicjalizujemy projekt Node.js poprzez npm init -y, co tworzy plik package.json. Następnie instalujemy Express jako zależność: npm install express.

W pliku głównym (np. server.js lub app.js) importujemy Express i tworzymy instancję aplikacji. Obiekt aplikacji służy do definiowania route'ów (tras) - endpointów, które będą odpowiadać na różne zapytania HTTP. Każdy route składa się z metody HTTP (GET, POST, PUT, DELETE), ścieżki URL i funkcji obsługującej (handler), która przyjmuje obiekty request i response.

Funkcja app.listen() uruchamia serwer HTTP na określonym porcie. Zwraca ona obiekt serwera, który można wykorzystać do bardziej zaawansowanej konfiguracji (np. HTTPS, WebSockets). Callback w listen() jest opcjonalny, ale przydatny do potwierdzenia uruchomienia serwera.

Typowe pułapki: zapomnienie wywołania app.listen() (aplikacja się nie uruchomi), użycie portu już zajętego (błąd EADDRINUSE), brak obsługi błędów w produkcji. Warto dodać zmienne środowiskowe dla portu (process.env.PORT) dla lepszej konfigurowalności.

Przykład kodu:

// 1. Import Express
const express = require('express');

// 2. Utworzenie instancji aplikacji
const app = express();

// 3. Definicja podstawowych route'ów
app.get('/', (req, res) => {
  res.send('Witaj w Express!');
});

app.get('/api/status', (req, res) => {
  res.json({
    status: 'OK',
    timestamp: new Date().toISOString()
  });
});

// 4. Uruchomienie serwera
const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Serwer działa na http://localhost:${PORT}`);
});

// Pełniejszy przykład z obsługą błędów
const express = require('express');
const app = express();

// Middleware do parsowania JSON
app.use(express.json());

// Route'y
app.get('/', (req, res) => {
  res.send('Strona główna');
});

app.post('/api/data', (req, res) => {
  console.log('Otrzymane dane:', req.body);
  res.status(201).json({ message: 'Dane zapisane' });
});

// Obsługa błędów 404
app.use((req, res) => {
  res.status(404).send('Strona nie znaleziona');
});

// Globalny handler błędów
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Coś poszło nie tak!');
});

// Uruchomienie z obsługą błędów
const PORT = 3000;
const server = app.listen(PORT, () => {
  console.log(`Serwer uruchomiony na porcie ${PORT}`);
});

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM otrzymany, zamykanie serwera...');
  server.close(() => {
    console.log('Serwer zamknięty');
  });
});

Materiały:

Czym jest obiekt aplikacji (app) w Express i jakie ma główne metody?

Odpowiedź w 30 sekund: Obiekt aplikacji Express to główna instancja frameworka, która zarządza routing'iem, middleware i konfiguracją serwera. Najważniejsze metody to app.get/post/put/delete() (routing HTTP), app.use() (middleware), app.listen() (uruchomienie serwera) oraz app.set/get() (konfiguracja).

Odpowiedź w 2 minuty: Obiekt aplikacji (app) jest rdzeniem każdej aplikacji Express - reprezentuje całą aplikację webową i dostarcza API do jej konfiguracji. Tworzy się go poprzez const app = express() i służy jako centralny punkt dla wszystkich operacji.

Metody routingu (app.get(), app.post(), app.put(), app.delete(), app.patch(), app.all()) definiują, jak aplikacja odpowiada na zapytania do konkretnych endpointów. Każda przyjmuje ścieżkę i funkcję handler. app.all() obsługuje wszystkie metody HTTP, przydatne dla middleware specyficznego dla ścieżki.

app.use() to kluczowa metoda do montowania middleware - funkcji wykonywanych w kolejności dla każdego zapytania. Może przyjąć ścieżkę (opcjonalnie) i funkcję/router. Używa się jej do parsowania body, logowania, autoryzacji, obsługi CORS itp.

Metody konfiguracyjne: app.set(name, value) ustawia zmienne aplikacji (np. port, silnik widoków), app.get(name) pobiera je. app.enable()/disable() to skróty dla wartości boolean. app.locals przechowuje zmienne dostępne w całej aplikacji, często używane w template'ach.

app.listen(port, callback) uruchamia serwer HTTP. app.route(path) tworzy łańcuch route'ów dla jednej ścieżki. app.param() definiuje middleware dla parametrów URL.

Przykład kodu:

const express = require('express');
const app = express();

// === KONFIGURACJA (app.set, app.enable) ===
app.set('port', process.env.PORT || 3000);
app.set('view engine', 'ejs');
app.enable('trust proxy'); // Dla aplikacji za proxy/load balancer

// Zmienne globalne dostępne w całej aplikacji
app.locals.siteName = 'Moja Aplikacja';
app.locals.version = '1.0.0';

// === MIDDLEWARE (app.use) ===
// Globalny middleware - wykonuje się dla każdego zapytania
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Middleware z logowaniem
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
  next();
});

// Middleware dla konkretnej ścieżki
app.use('/api', (req, res, next) => {
  res.setHeader('X-API-Version', '1.0');
  next();
});

// === ROUTING (app.get, app.post, etc.) ===
app.get('/', (req, res) => {
  res.send(`Witaj w ${app.locals.siteName}`);
});

app.post('/api/users', (req, res) => {
  res.status(201).json({ message: 'User created' });
});

// app.all() - wszystkie metody HTTP
app.all('/api/secret', (req, res) => {
  res.send('Dostęp do tajnej sekcji');
});

// === PARAMETRY URL (app.param) ===
// Wykonuje się automatycznie gdy route ma parametr :userId
app.param('userId', (req, res, next, id) => {
  console.log(`Przetwarzanie użytkownika ID: ${id}`);
  // Tutaj można załadować użytkownika z bazy
  req.user = { id, name: 'Jan Kowalski' };
  next();
});

app.get('/users/:userId', (req, res) => {
  res.json(req.user);
});

// === ŁAŃCUCH ROUTE'ÓW (app.route) ===
app.route('/api/articles')
  .get((req, res) => {
    res.json({ articles: [] });
  })
  .post((req, res) => {
    res.status(201).json({ message: 'Article created' });
  })
  .delete((req, res) => {
    res.status(204).send();
  });

// === URUCHOMIENIE SERWERA (app.listen) ===
const port = app.get('port');
app.listen(port, () => {
  console.log(`Serwer działa na porcie ${port}`);
});

// Diagram przepływu zapytania przez middleware
graph TD
    A[Zapytanie HTTP] --> B[app.use - JSON parser]
    B --> C[app.use - Logger middleware]
    C --> D{Ścieżka URL?}
    D -->|/api/*| E[app.use - API middleware]
    D -->|/users/:userId| F[app.param - userId validator]
    E --> G[app.get/post - Route handler]
    F --> G
    G --> H[Odpowiedź HTTP]

    style A fill:#e1f5ff
    style H fill:#d4edda
    style G fill:#fff3cd

Materiały:

Jaka jest różnica między Express.js a natywnym modułem HTTP w Node.js?

Odpowiedź w 30 sekund: Natywny moduł HTTP to niskopoziomowe API wymagające ręcznego parsowania URL, body i nagłówków. Express.js to framework abstrakcji oferujący prosty routing, middleware, automatyczne parsowanie oraz rozbudowane obiekty req/res - znacznie redukuje ilość boilerplate'u i przyspiesza development.

Odpowiedź w 2 minuty: Moduł HTTP w Node.js to fundament - minimalistyczne, niskopoziomowe API do tworzenia serwerów. Daje pełną kontrolę, ale wymaga ręcznej implementacji podstawowych funkcjonalności. Parsowanie URL odbywa się przez moduł url, body trzeba zbierać z chunków danych, routing wymaga ręcznych warunków if/else, a każdy nagłówek i status kod musi być ustawiony explicite.

Express.js buduje warstwę abstrakcji rozwiązującą te problemy. Automatycznie parsuje URL i parametry query, oferuje middleware do parsowania różnych formatów body (JSON, URLencoded, multipart). Routing jest deklaratywny i intuicyjny - app.get('/users/:id') zamiast ręcznego parsowania. Obiekty req i res są rozszerzone o dziesiątki pomocnych metod jak res.json(), req.params, res.redirect().

Kluczowa różnica to middleware - Express wprowadza pipeline przetwarzania zapytań, gdzie każde zapytanie przechodzi przez łańcuch funkcji. To umożliwia modularną architekturę - oddzielne middleware dla logowania, autoryzacji, parsowania, kompresji itp. W natywnym HTTP trzeba by to wszystko kodować w jednej funkcji createServer().

Performance: HTTP jest minimalnie szybszy (brak warstwy abstrakcji), ale różnica jest znikoma - Express dodaje ~0.5-1ms overhead. W praktyce Express jest używany w 99% przypadków, a czysty HTTP tylko w ekstremalnych scenariuszach performance lub nauce fundamentów Node.js.

Przykład kodu:

// ==========================================
// NATYWNY HTTP - ROUTING I PARSOWANIE
// ==========================================
const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
  // Ręczne parsowanie URL
  const parsedUrl = url.parse(req.url, true);
  const path = parsedUrl.pathname;
  const query = parsedUrl.query;

  // Ręczny routing
  if (path === '/api/users' && req.method === 'GET') {
    // Ręczne ustawianie nagłówków
    res.writeHead(200, {
      'Content-Type': 'application/json',
      'X-Custom-Header': 'value'
    });
    res.end(JSON.stringify({ users: [], page: query.page }));

  } else if (path === '/api/users' && req.method === 'POST') {
    // Ręczne zbieranie body z chunków
    let body = '';

    req.on('data', chunk => {
      body += chunk.toString();
    });

    req.on('end', () => {
      try {
        const data = JSON.parse(body);
        res.writeHead(201, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ message: 'Created', data }));
      } catch (err) {
        res.writeHead(400, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Invalid JSON' }));
      }
    });

    req.on('error', (err) => {
      res.writeHead(500);
      res.end('Server Error');
    });

  } else {
    // Ręczna obsługa 404
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
});

server.listen(3000, () => {
  console.log('HTTP Server running on port 3000');
});

// ==========================================
// EXPRESS.JS - TA SAMA FUNKCJONALNOŚĆ
// ==========================================
const express = require('express');
const app = express();

// Middleware automatycznie parsuje JSON
app.use(express.json());

// Prosty, deklaratywny routing
app.get('/api/users', (req, res) => {
  // Query params automatycznie dostępne
  const page = req.query.page;

  // Metoda json() automatycznie ustawia nagłówki i stringifuje
  res.set('X-Custom-Header', 'value');
  res.json({ users: [], page });
});

app.post('/api/users', (req, res) => {
  // Body już sparsowane i dostępne w req.body
  const data = req.body;

  // Łatwe ustawianie statusu i odpowiedzi
  res.status(201).json({ message: 'Created', data });
});

// Automatyczna obsługa 404
app.use((req, res) => {
  res.status(404).send('Not Found');
});

// Globalny error handler
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: 'Server Error' });
});

app.listen(3000, () => {
  console.log('Express Server running on port 3000');
});

// ==========================================
// PORÓWNANIE: MIDDLEWARE W EXPRESS
// ==========================================

// HTTP - wszystko w jednej funkcji
const httpServer = http.createServer((req, res) => {
  // Logowanie
  console.log(`${req.method} ${req.url}`);

  // Autoryzacja
  const token = req.headers['authorization'];
  if (!token) {
    res.writeHead(401);
    res.end('Unauthorized');
    return;
  }

  // Parsowanie body
  let body = '';
  req.on('data', chunk => body += chunk);
  req.on('end', () => {
    // Dalsza logika...
  });
});

// Express - modularne middleware
const expressApp = express();

// Logowanie - osobny middleware
expressApp.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

// Autoryzacja - osobny middleware
expressApp.use((req, res, next) => {
  const token = req.headers['authorization'];
  if (!token) {
    return res.status(401).send('Unauthorized');
  }
  next();
});

// Parsowanie body - gotowy middleware
expressApp.use(express.json());

// Routing - czysta logika biznesowa
expressApp.post('/api/data', (req, res) => {
  res.json({ received: req.body });
});
graph LR
    subgraph "Natywny HTTP"
        A1[Zapytanie] --> B1[createServer callback]
        B1 --> C1[Ręczne parsowanie URL]
        C1 --> D1[Ręczne parsowanie body]
        D1 --> E1[Ręczny routing if/else]
        E1 --> F1[Ręczne ustawianie headers]
        F1 --> G1[Odpowiedź]
    end

    subgraph "Express.js"
        A2[Zapytanie] --> B2[Middleware 1]
        B2 --> C2[Middleware 2]
        C2 --> D2[Route handler]
        D2 --> E2[Odpowiedź]
    end

    style A1 fill:#ffe6e6
    style G1 fill:#e6ffe6
    style A2 fill:#ffe6e6
    style E2 fill:#e6ffe6

Materiały:

Jak skonfigurować Express do obsługi plików statycznych?

Odpowiedź w 30 sekund: Express oferuje wbudowane middleware express.static() do serwowania plików statycznych (HTML, CSS, JS, obrazy). Wystarczy wywołać app.use(express.static('public')) podając katalog z plikami - Express automatycznie obsłuży zapytania do tych zasobów bez definiowania osobnych route'ów.

Odpowiedź w 2 minuty: Middleware express.static() to wbudowane rozwiązanie do obsługi plików statycznych. Przyjmuje ścieżkę do katalogu (względną lub bezwzględną) i automatycznie serwuje wszystkie pliki z tego katalogu oraz jego podkatalogów. Nie wymaga definiowania route'ów dla każdego pliku - Express sam mapuje URL na strukturę katalogów.

Można skonfigurować multiple katalogi statyczne przez wielokrotne wywołanie app.use(express.static()). Express sprawdza je w kolejności dodania. Opcjonalnie można dodać prefix URL (app.use('/static', express.static('public'))) - wtedy pliki będą dostępne pod /static/style.css zamiast /style.css.

Opcje konfiguracyjne przekazuje się jako drugi argument: maxAge (cache control), dotfiles (obsługa plików zaczynających się od kropki), index (domyślny plik dla katalogu), redirect (przekierowanie ze ścieżki katalogu), setHeaders (custom nagłówki). Dla produkcji warto ustawić maxAge dla lepszego cache'owania.

Ważne uwagi bezpieczeństwa: nigdy nie serwuj głównego katalogu projektu (app.use(express.static('.'))) - to udostępni pliki wrażliwe jak .env, package.json. Serwuj tylko dedykowany katalog public lub static. Express automatycznie chroni przed path traversal (../../../etc/passwd). W produkcji rozważ użycie CDN lub reverse proxy (nginx) dla lepszej wydajności - serwowanie statycznych plików nie jest najmocniejszą stroną Node.js.

Przykład kodu:

const express = require('express');
const path = require('path');
const app = express();

// ==========================================
// PODSTAWOWA KONFIGURACJA
// ==========================================

// Serwowanie plików z katalogu 'public'
app.use(express.static('public'));

// Struktura katalogów:
// public/
//   ├── index.html
//   ├── css/
//   │   └── style.css
//   ├── js/
//   │   └── app.js
//   └── images/
//       └── logo.png

// Teraz pliki są dostępne:
// http://localhost:3000/index.html
// http://localhost:3000/css/style.css
// http://localhost:3000/js/app.js
// http://localhost:3000/images/logo.png

// ==========================================
// ŚCIEŻKA BEZWZGLĘDNA (ZALECANA)
// ==========================================

// Używaj path.join dla bezpieczeństwa i przenośności
app.use(express.static(path.join(__dirname, 'public')));

// ==========================================
// WIELE KATALOGÓW STATYCZNYCH
// ==========================================

// Express sprawdza katalogi w kolejności dodania
app.use(express.static('public'));
app.use(express.static('files'));
app.use(express.static('uploads'));

// Jeśli plik istnieje w 'public' i 'files', zwróci wersję z 'public'

// ==========================================
// PREFIX URL (VIRTUAL PATH)
// ==========================================

// Pliki z 'public' dostępne pod /static/*
app.use('/static', express.static('public'));
// Teraz: http://localhost:3000/static/css/style.css

// Wiele prefixów dla różnych katalogów
app.use('/assets', express.static('public'));
app.use('/uploads', express.static('user-uploads'));

// ==========================================
// OPCJE KONFIGURACYJNE
// ==========================================

const staticOptions = {
  // Cache Control - pliki cache'owane na 1 dzień (milisekundy)
  maxAge: '1d', // lub 86400000

  // Index file - domyślny plik dla katalogu
  index: 'index.html', // domyślnie

  // Redirect - przekierowanie /katalog na /katalog/
  redirect: true,

  // Dotfiles - obsługa plików zaczynających się od .
  dotfiles: 'ignore', // 'allow', 'deny', 'ignore'

  // ETag - nagłówki dla cache validation
  etag: true,

  // Extensions - automatyczne rozszerzenia plików
  extensions: ['html', 'htm'],

  // Custom headers
  setHeaders: (res, filePath, stat) => {
    // Dodaj custom header dla wszystkich plików
    res.set('X-Custom-Header', 'MyValue');

    // Różne cache dla różnych typów
    if (filePath.endsWith('.html')) {
      res.set('Cache-Control', 'no-cache');
    } else if (filePath.match(/\.(jpg|jpeg|png|gif)$/)) {
      res.set('Cache-Control', 'public, max-age=31536000'); // 1 rok
    }
  }
};

app.use(express.static('public', staticOptions));

// ==========================================
// PRZYKŁAD PRODUKCYJNY
// ==========================================

const express = require('express');
const path = require('path');
const compression = require('compression'); // npm install compression

const app = express();

// Kompresja gzip dla lepszej wydajności
app.use(compression());

// Statyczne pliki z długim cache dla assetsów
app.use('/assets', express.static(path.join(__dirname, 'public/assets'), {
  maxAge: '1y',
  immutable: true // Pliki nigdy się nie zmieniają
}));

// HTML bez cache (często się zmienia)
app.use(express.static(path.join(__dirname, 'public'), {
  maxAge: 0,
  setHeaders: (res, path) => {
    if (path.endsWith('.html')) {
      res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
    }
  }
}));

// Fallback route dla SPA (Single Page Application)
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'public/index.html'));
});

// ==========================================
// BEZPIECZEŃSTWO - CZEGO UNIKAĆ
// ==========================================

// ❌ NIE ROB TEGO - serwuje cały projekt!
// app.use(express.static('.'));
// app.use(express.static(__dirname));

// ✅ DOBRZE - tylko dedykowany katalog
app.use(express.static('public'));

// ❌ NIE ROB TEGO - katalog wrażliwy
// app.use(express.static('node_modules'));

// ✅ DOBRZE - jeśli naprawdę potrzebne, konkretne pliki
app.use('/libs/jquery', express.static('node_modules/jquery/dist'));

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
graph TD
    A[Zapytanie: GET /css/style.css] --> B{express.static middleware}
    B --> C{Czy plik istnieje?}
    C -->|Tak| D[Sprawdź uprawnienia]
    D --> E[Ustaw Content-Type]
    E --> F[Ustaw Cache Headers]
    F --> G[Wyślij plik]
    C -->|Nie| H[next - przekaż dalej]
    H --> I[Następny middleware/route]

    style A fill:#e1f5ff
    style G fill:#d4edda
    style H fill:#fff3cd

    subgraph "Struktura katalogów"
        J[public/]
        J --> K[css/style.css]
        J --> L[js/app.js]
        J --> M[index.html]
    end

Materiały:

Routing

6. Jak działa routing w Express.js i jakie są podstawowe metody routingu?

Odpowiedź w 30 sekund: Routing w Express.js to mechanizm określający, jak aplikacja odpowiada na żądania klienta dla konkretnego endpointu (ścieżki URL) i metody HTTP. Express dostarcza metody odpowiadające metodom HTTP: app.get(), app.post(), app.put(), app.delete(), które łączą ścieżkę z funkcją obsługującą (handler).

Odpowiedź w 2 minuty: Routing w Express.js jest fundamentalnym mechanizmem umożliwiającym mapowanie żądań HTTP na odpowiednie funkcje obsługujące. Każda trasa składa się z trzech elementów: metody HTTP, ścieżki (path) oraz jednej lub więcej funkcji callback, które są wykonywane gdy trasa pasuje do żądania.

Express udostępnia metody odpowiadające wszystkim standardowym metodom HTTP. Najczęściej używane to: app.get() dla pobierania danych, app.post() dla tworzenia zasobów, app.put() lub app.patch() dla aktualizacji, oraz app.delete() dla usuwania. Dodatkowo istnieje metoda app.all(), która obsługuje wszystkie metody HTTP dla danej ścieżki.

Gdy Express otrzymuje żądanie, przechodzi przez zdefiniowane trasy w kolejności ich deklaracji i wykonuje pierwszą pasującą trasę. Jeśli handler wywołuje next(), Express kontynuuje szukanie kolejnych pasujących tras. Ten mechanizm pozwala na tworzenie middleware'ów, które mogą przetwarzać żądanie przed przekazaniem go do finalnego handlera.

Routing w Express jest wysoce elastyczny - obsługuje ścieżki statyczne, parametry dynamiczne, wyrażenia regularne oraz wzorce z wildcards, co pozwala na tworzenie zarówno prostych jak i bardzo złożonych struktur API.

Przykład kodu:

const express = require('express');
const app = express();

// Middleware do parsowania JSON
app.use(express.json());

// GET - pobieranie danych
app.get('/api/users', (req, res) => {
  res.json({ users: ['Anna', 'Jan', 'Katarzyna'] });
});

// POST - tworzenie nowego zasobu
app.post('/api/users', (req, res) => {
  const newUser = req.body;
  // Logika zapisywania użytkownika
  res.status(201).json({
    message: 'Użytkownik utworzony',
    user: newUser
  });
});

// PUT - pełna aktualizacja zasobu
app.put('/api/users/:id', (req, res) => {
  const userId = req.params.id;
  const userData = req.body;
  // Logika aktualizacji użytkownika
  res.json({
    message: `Użytkownik ${userId} zaktualizowany`,
    user: userData
  });
});

// DELETE - usuwanie zasobu
app.delete('/api/users/:id', (req, res) => {
  const userId = req.params.id;
  // Logika usuwania użytkownika
  res.json({ message: `Użytkownik ${userId} usunięty` });
});

// ALL - obsługa wszystkich metod HTTP
app.all('/api/test', (req, res) => {
  res.json({
    message: 'Endpoint obsługuje wszystkie metody',
    method: req.method
  });
});

app.listen(3000, () => {
  console.log('Serwer działa na porcie 3000');
});

Materiały

7. Czym są parametry trasy (route parameters) i jak ich używać?

Odpowiedź w 30 sekund: Parametry trasy (route parameters) to nazwane segmenty w ścieżce URL, które przechwytują wartości z konkretnych pozycji w URLu. Definiuje się je używając dwukropka przed nazwą (np. :id, :username) i są dostępne w obiekcie req.params.

Odpowiedź w 2 minuty: Route parameters to dynamiczne części ścieżki URL, które pozwalają na tworzenie elastycznych tras akceptujących zmienne wartości. Są one kluczowym elementem budowania RESTful API, gdzie często potrzebujemy identyfikować konkretne zasoby poprzez ich ID lub inne unikalne identyfikatory.

Parametry definiuje się poprzez prefiks dwukropka (:) w definicji ścieżki. Express automatycznie wydobywa te wartości z URL i umieszcza je w obiekcie req.params jako właściwości o nazwach odpowiadających nazwom parametrów. Możemy mieć wiele parametrów w jednej trasie, tworząc hierarchiczne struktury URL.

Express pozwala również na używanie wyrażeń regularnych do walidacji parametrów bezpośrednio w definicji trasy, używając składni :param(regex). Dodatkowo, można definiować własne funkcje walidacyjne używając app.param(), które są wywoływane automatycznie gdy określony parametr jest obecny w trasie.

Parametry trasy są idealne do identyfikowania zasobów (np. /users/:userId), podczas gdy query parameters (np. ?sort=name&limit=10) są lepsze do filtrowania, sortowania czy paginacji.

Przykład kodu:

const express = require('express');
const app = express();

// Prosty parametr trasy
app.get('/users/:userId', (req, res) => {
  const userId = req.params.userId;
  res.json({
    message: `Pobieranie użytkownika o ID: ${userId}`,
    userId: userId
  });
});

// Wiele parametrów w jednej trasie
app.get('/users/:userId/posts/:postId', (req, res) => {
  const { userId, postId } = req.params;
  res.json({
    message: `Post ${postId} użytkownika ${userId}`,
    userId,
    postId
  });
});

// Parametr z walidacją wyrażeniem regularnym (tylko liczby)
app.get('/products/:productId(\\d+)', (req, res) => {
  res.json({
    message: 'Produkt znaleziony',
    productId: req.params.productId
  });
});

// Opcjonalny parametr (używając ?)
app.get('/files/:filename.:extension?', (req, res) => {
  const { filename, extension } = req.params;
  res.json({
    filename,
    extension: extension || 'brak rozszerzenia'
  });
});

// Middleware app.param() - walidacja parametru
app.param('userId', (req, res, next, userId) => {
  // Walidacja czy userId jest liczbą
  if (isNaN(userId)) {
    return res.status(400).json({
      error: 'userId musi być liczbą'
    });
  }

  // Możemy dodać dane do req
  req.validatedUserId = parseInt(userId);
  next();
});

// Ta trasa wykorzysta middleware app.param
app.get('/validated/users/:userId', (req, res) => {
  res.json({
    message: 'Użytkownik zwalidowany',
    userId: req.validatedUserId
  });
});

// Przykład z rzeczywistą logiką biznesową
const users = [
  { id: 1, name: 'Anna' },
  { id: 2, name: 'Jan' },
  { id: 3, name: 'Katarzyna' }
];

app.get('/api/users/:id', (req, res) => {
  const userId = parseInt(req.params.id);
  const user = users.find(u => u.id === userId);

  if (!user) {
    return res.status(404).json({
      error: 'Użytkownik nie znaleziony'
    });
  }

  res.json(user);
});

app.listen(3000);

Materiały

8. Jak obsługiwać parametry zapytania (query parameters) w Express?

Odpowiedź w 30 sekund: Query parameters to pary klucz-wartość dodawane do URL po znaku zapytania (np. /api/users?sort=name&limit=10). W Express są one automatycznie parsowane i dostępne w obiekcie req.query. Nie wymagają specjalnej konfiguracji w definicji trasy.

Odpowiedź w 2 minuty: Parametry zapytania (query parameters) są częścią URL używaną do przekazywania dodatkowych informacji do serwera bez zmiany struktury ścieżki. Express automatycznie parsuje query string i udostępnia parametry jako obiekt w req.query, gdzie klucze to nazwy parametrów, a wartości to ich zawartość.

Query parameters są idealne do implementacji funkcjonalności takich jak filtrowanie, sortowanie, paginacja czy wyszukiwanie. W przeciwieństwie do route parameters, które są częścią struktury URL i zazwyczaj identyfikują konkretny zasób, query parameters są opcjonalne i służą do modyfikacji zachowania endpointu.

Ważne jest odpowiednie walidowanie i sanityzowanie query parameters, ponieważ pochodzą od użytkownika. Należy uwzględnić wartości domyślne dla brakujących parametrów oraz obsłużyć nieprawidłowe wartości. Dla złożonych przypadków warto użyć bibliotek walidacyjnych jak express-validator lub joi.

Query parameters mogą być stringami, tablicami (gdy ten sam parametr pojawia się wielokrotnie) lub zagnieżdżonymi obiektami (w zależności od ustawień query parser). Express domyślnie używa prostego parsera, ale można to skonfigurować.

Przykład kodu:

const express = require('express');
const app = express();

// Przykładowa baza danych
const products = [
  { id: 1, name: 'Laptop', category: 'electronics', price: 3000 },
  { id: 2, name: 'Mysz', category: 'electronics', price: 50 },
  { id: 3, name: 'Klawiatura', category: 'electronics', price: 150 },
  { id: 4, name: 'Biurko', category: 'furniture', price: 800 },
  { id: 5, name: 'Krzesło', category: 'furniture', price: 600 }
];

// Podstawowe query parameters
app.get('/api/products', (req, res) => {
  // Dostęp do query parameters
  const { category, minPrice, maxPrice, sort } = req.query;

  let filteredProducts = [...products];

  // Filtrowanie po kategorii
  if (category) {
    filteredProducts = filteredProducts.filter(
      p => p.category === category
    );
  }

  // Filtrowanie po cenie minimalnej
  if (minPrice) {
    const min = parseFloat(minPrice);
    filteredProducts = filteredProducts.filter(
      p => p.price >= min
    );
  }

  // Filtrowanie po cenie maksymalnej
  if (maxPrice) {
    const max = parseFloat(maxPrice);
    filteredProducts = filteredProducts.filter(
      p => p.price <= max
    );
  }

  // Sortowanie
  if (sort) {
    if (sort === 'price-asc') {
      filteredProducts.sort((a, b) => a.price - b.price);
    } else if (sort === 'price-desc') {
      filteredProducts.sort((a, b) => b.price - a.price);
    } else if (sort === 'name') {
      filteredProducts.sort((a, b) =>
        a.name.localeCompare(b.name)
      );
    }
  }

  res.json({
    count: filteredProducts.length,
    products: filteredProducts
  });
});

// Paginacja z query parameters
app.get('/api/products/paginated', (req, res) => {
  // Wartości domyślne
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;

  // Walidacja
  if (page < 1 || limit < 1 || limit > 100) {
    return res.status(400).json({
      error: 'Nieprawidłowe parametry paginacji'
    });
  }

  const startIndex = (page - 1) * limit;
  const endIndex = page * limit;

  const paginatedProducts = products.slice(startIndex, endIndex);

  res.json({
    page,
    limit,
    total: products.length,
    totalPages: Math.ceil(products.length / limit),
    products: paginatedProducts
  });
});

// Wyszukiwanie z query parameter
app.get('/api/search', (req, res) => {
  const { q, fields } = req.query;

  if (!q) {
    return res.status(400).json({
      error: 'Brak zapytania wyszukiwania (parametr q)'
    });
  }

  const searchTerm = q.toLowerCase();
  const searchFields = fields ? fields.split(',') : ['name'];

  const results = products.filter(product => {
    return searchFields.some(field => {
      const value = product[field];
      return value &&
             value.toString().toLowerCase().includes(searchTerm);
    });
  });

  res.json({
    query: q,
    searchFields,
    count: results.length,
    results
  });
});

// Obsługa tablic w query parameters
// URL: /api/filter?categories=electronics&categories=furniture
app.get('/api/filter', (req, res) => {
  let { categories } = req.query;

  // Zapewnienie że categories jest tablicą
  if (categories && !Array.isArray(categories)) {
    categories = [categories];
  }

  const filtered = categories
    ? products.filter(p => categories.includes(p.category))
    : products;

  res.json({
    filters: { categories },
    count: filtered.length,
    products: filtered
  });
});

// Zaawansowane: kombinacja route i query parameters
app.get('/api/categories/:category/products', (req, res) => {
  const { category } = req.params;
  const { sort, limit } = req.query;

  let categoryProducts = products.filter(
    p => p.category === category
  );

  // Sortowanie jeśli podano
  if (sort === 'price') {
    categoryProducts.sort((a, b) => a.price - b.price);
  }

  // Limit jeśli podano
  if (limit) {
    const maxItems = parseInt(limit);
    categoryProducts = categoryProducts.slice(0, maxItems);
  }

  res.json({
    category,
    filters: { sort, limit },
    count: categoryProducts.length,
    products: categoryProducts
  });
});

app.listen(3000);

// Przykładowe URLe:
// /api/products?category=electronics
// /api/products?minPrice=100&maxPrice=1000
// /api/products?category=electronics&sort=price-asc
// /api/products/paginated?page=2&limit=5
// /api/search?q=laptop&fields=name,category
// /api/filter?categories=electronics&categories=furniture

Materiały

9. Czym jest express.Router() i jak organizować trasy w modułach?

Odpowiedź w 30 sekund: express.Router() to mini-aplikacja Express służąca do tworzenia modularnych, montowanych grup tras (handlers). Pozwala na organizowanie tras w osobnych plikach według funkcjonalności (np. users, products), a następnie montowanie ich w głównej aplikacji używając app.use().

Odpowiedź w 2 minuty: Express.Router() jest kluczowym narzędziem do tworzenia skalowalnych aplikacji Express poprzez umożliwienie podziału tras na logiczne moduły. Router działa jak mini-aplikacja - ma własne middleware, trasy i obsługę błędów, ale nie może działać samodzielnie. Musi być zamontowany w głównej aplikacji lub innym routerze.

Organizacja aplikacji przy użyciu routerów pozwala na separację odpowiedzialności (separation of concerns) - każdy moduł odpowiada za konkretną część funkcjonalności. Typowa struktura to osobne pliki dla różnych zasobów API (users.js, products.js, orders.js), które eksportują skonfigurowane routery i są importowane w głównym pliku aplikacji.

Routery mogą mieć własne middleware, które działają tylko dla tras w tym routerze. Można również zagnieżdżać routery - router może montować inne routery, tworząc hierarchiczną strukturę. Ścieżka pod którą montujemy router staje się prefiksem dla wszystkich tras w tym routerze.

Taka architektura ułatwia testowanie (każdy moduł można testować osobno), utrzymanie kodu (łatwo znaleźć kod odpowiedzialny za konkretną funkcjonalność) oraz pracę zespołową (różni deweloperzy mogą pracować nad różnymi modułami bez konfliktów).

Przykład kodu:

// ========================================
// Struktura katalogów:
// project/
// ├── app.js
// ├── routes/
// │   ├── index.js
// │   ├── users.js
// │   ├── products.js
// │   └── orders.js
// └── middleware/
//     └── auth.js
// ========================================

// ========================================
// routes/users.js
// ========================================
const express = require('express');
const router = express.Router();

// Middleware specyficzne dla tego routera
router.use((req, res, next) => {
  console.log('User Route - Time:', Date.now());
  next();
});

// GET /api/users
router.get('/', (req, res) => {
  res.json({
    message: 'Lista wszystkich użytkowników',
    users: [
      { id: 1, name: 'Anna' },
      { id: 2, name: 'Jan' }
    ]
  });
});

// GET /api/users/:id
router.get('/:id', (req, res) => {
  res.json({
    message: `Szczegóły użytkownika ${req.params.id}`,
    user: { id: req.params.id, name: 'Anna' }
  });
});

// POST /api/users
router.post('/', (req, res) => {
  res.status(201).json({
    message: 'Użytkownik utworzony',
    user: req.body
  });
});

// PUT /api/users/:id
router.put('/:id', (req, res) => {
  res.json({
    message: `Użytkownik ${req.params.id} zaktualizowany`,
    user: req.body
  });
});

// DELETE /api/users/:id
router.delete('/:id', (req, res) => {
  res.json({
    message: `Użytkownik ${req.params.id} usunięty`
  });
});

module.exports = router;

// ========================================
// routes/products.js
// ========================================
const express = require('express');
const router = express.Router();

// GET /api/products
router.get('/', (req, res) => {
  res.json({
    message: 'Lista produktów',
    products: [
      { id: 1, name: 'Laptop', price: 3000 },
      { id: 2, name: 'Mysz', price: 50 }
    ]
  });
});

// GET /api/products/:id
router.get('/:id', (req, res) => {
  res.json({
    message: `Produkt ${req.params.id}`,
    product: { id: req.params.id, name: 'Laptop' }
  });
});

// Zagnieżdżony router dla recenzji produktów
const reviewsRouter = express.Router({ mergeParams: true });

// GET /api/products/:id/reviews
reviewsRouter.get('/', (req, res) => {
  res.json({
    productId: req.params.id,
    reviews: [
      { id: 1, rating: 5, comment: 'Świetny produkt!' }
    ]
  });
});

// POST /api/products/:id/reviews
reviewsRouter.post('/', (req, res) => {
  res.status(201).json({
    productId: req.params.id,
    message: 'Recenzja dodana',
    review: req.body
  });
});

// Montowanie zagnieżdżonego routera
router.use('/:id/reviews', reviewsRouter);

module.exports = router;

// ========================================
// routes/orders.js
// ========================================
const express = require('express');
const router = express.Router();

// Middleware autoryzacji (przykładowe)
const requireAuth = (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader) {
    return res.status(401).json({
      error: 'Brak autoryzacji'
    });
  }

  // Tutaj byłaby prawdziwa weryfikacja tokenu
  next();
};

// Wszystkie trasy zamówień wymagają autoryzacji
router.use(requireAuth);

// GET /api/orders
router.get('/', (req, res) => {
  res.json({
    message: 'Twoje zamówienia',
    orders: [
      { id: 1, total: 3050, status: 'wysłane' }
    ]
  });
});

// POST /api/orders
router.post('/', (req, res) => {
  res.status(201).json({
    message: 'Zamówienie złożone',
    order: req.body
  });
});

module.exports = router;

// ========================================
// routes/index.js - agregator wszystkich routerów
// ========================================
const express = require('express');
const router = express.Router();

const usersRouter = require('./users');
const productsRouter = require('./products');
const ordersRouter = require('./orders');

// Montowanie routerów
router.use('/users', usersRouter);
router.use('/products', productsRouter);
router.use('/orders', ordersRouter);

// Można też dodać trasy bezpośrednio w tym routerze
router.get('/health', (req, res) => {
  res.json({ status: 'OK', timestamp: Date.now() });
});

module.exports = router;

// ========================================
// app.js - główny plik aplikacji
// ========================================
const express = require('express');
const app = express();

// Middleware globalne
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Logger
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
});

// Montowanie głównego routera API
const apiRouter = require('./routes');
app.use('/api', apiRouter);

// Można też montować routery bezpośrednio
// const usersRouter = require('./routes/users');
// app.use('/api/users', usersRouter);

// Trasa główna aplikacji
app.get('/', (req, res) => {
  res.json({
    message: 'Witaj w API!',
    version: '1.0.0',
    endpoints: {
      users: '/api/users',
      products: '/api/products',
      orders: '/api/orders',
      health: '/api/health'
    }
  });
});

// Obsługa 404
app.use((req, res) => {
  res.status(404).json({
    error: 'Endpoint nie znaleziony'
  });
});

// Obsługa błędów
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Wystąpił błąd serwera'
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Serwer działa na porcie ${PORT}`);
});

module.exports = app;
graph TD
    A[app.js - Główna aplikacja] --> B[/api - Router główny]
    B --> C[/api/users - Router użytkowników]
    B --> D[/api/products - Router produktów]
    B --> E[/api/orders - Router zamówień]
    B --> F[/api/health - Endpoint health]

    C --> C1[GET /]
    C --> C2[GET /:id]
    C --> C3[POST /]
    C --> C4[PUT /:id]
    C --> C5[DELETE /:id]

    D --> D1[GET /]
    D --> D2[GET /:id]
    D --> D3[/:id/reviews - Router recenzji]

    D3 --> D3A[GET /]
    D3 --> D3B[POST /]

    E --> E1[Middleware: requireAuth]
    E1 --> E2[GET /]
    E1 --> E3[POST /]

    style A fill:#e1f5ff
    style B fill:#fff4e1
    style C fill:#ffe1e1
    style D fill:#e1ffe1
    style E fill:#f0e1ff
    style D3 fill:#e1ffe1

Materiały

10. Jak działa kolejność definiowania tras i dlaczego jest ważna?

Odpowiedź w 30 sekund: Express przetwarza trasy w kolejności ich definiowania (od góry do dołu) i wykonuje pierwszą pasującą trasę. Kolejność jest krytyczna, ponieważ bardziej szczegółowe trasy muszą być zdefiniowane przed ogólniejszymi, a trasy z parametrami przed trasami statycznymi o podobnych wzorcach.

Odpowiedź w 2 minuty: Kolejność definiowania tras w Express jest fundamentalną koncepcją wpływającą na działanie aplikacji. Express używa algorytmu "first-match" - gdy otrzyma żądanie, iteruje przez zdefiniowane trasy w kolejności ich rejestracji i zatrzymuje się na pierwszej pasującej trasie (chyba że handler wywoła next()).

To zachowanie ma kluczowe implikacje praktyczne. Trasy bardziej szczegółowe lub specyficzne muszą być umieszczone przed trasami ogólnymi. Na przykład, trasa /users/admin musi być zdefiniowana przed /users/:id, w przeciwnym razie "admin" zostanie potraktowane jako wartość parametru :id i trasa /users/admin nigdy nie zostanie osiągnięta.

Podobnie, trasy ze statycznymi segmentami powinny być przed trasami z parametrami, a trasy z wieloma parametrami przed trasami z pojedynczymi parametrami. Trasy catch-all (np. używające * lub .all()) powinny być zawsze na końcu, aby nie przechwyciły żądań przeznaczonych dla bardziej specyficznych tras.

Middleware również podlega tej samej zasadzie kolejności. Globalne middleware zdefiniowane przez app.use() będzie działać tylko dla tras zdefiniowanych po nim. Dlatego middleware parsujące body (express.json()) czy logowanie muszą być zdefiniowane przed trasami, które ich potrzebują. Kolejność ma również znaczenie dla obsługi błędów - error handlers powinny być ostatnie.

Przykład kodu:

const express = require('express');
const app = express();

app.use(express.json());

// ========================================
// PRZYKŁAD 1: Znaczenie kolejności - BŁĄD
// ========================================

// ❌ ŹLE - ta kolejność NIE zadziała poprawnie
/*
app.get('/users/:id', (req, res) => {
  // Ta trasa przechwyci WSZYSTKO, włącznie z /users/admin
  res.json({
    message: `Użytkownik o ID: ${req.params.id}`,
    id: req.params.id
  });
});

app.get('/users/admin', (req, res) => {
  // Ta trasa NIGDY nie zostanie osiągnięta!
  res.json({ message: 'Panel administratora' });
});
*/

// ✅ DOBRZE - poprawna kolejność
app.get('/users/admin', (req, res) => {
  // Ta trasa musi być PIERWSZA
  res.json({ message: 'Panel administratora' });
});

app.get('/users/profile', (req, res) => {
  res.json({ message: 'Profil zalogowanego użytkownika' });
});

app.get('/users/:id', (req, res) => {
  // Ta ogólniejsza trasa jest OSTATNIA
  res.json({
    message: `Użytkownik o ID: ${req.params.id}`,
    id: req.params.id
  });
});

// ========================================
// PRZYKŁAD 2: Kolejność middleware
// ========================================

// Middleware logowania - powinno być wcześnie
app.use('/api', (req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
  next();
});

// Middleware autoryzacji dla chronionych tras
const requireAuth = (req, res, next) => {
  const token = req.headers.authorization;

  if (!token || token !== 'Bearer secret-token') {
    return res.status(401).json({ error: 'Nieautoryzowany' });
  }

  next();
};

// Publiczne trasy - przed middleware autoryzacji
app.get('/api/public/info', (req, res) => {
  res.json({ message: 'Informacje publiczne' });
});

// Zastosowanie middleware autoryzacji
app.use('/api/private', requireAuth);

// Chronione trasy - po middleware autoryzacji
app.get('/api/private/data', (req, res) => {
  res.json({
    message: 'Dane chronione',
    secret: 'Poufne informacje'
  });
});

// ========================================
// PRZYKŁAD 3: Hierarchia specyficzności
// ========================================

// 1. Najbardziej specyficzna - konkretna ścieżka
app.get('/products/featured/laptops', (req, res) => {
  res.json({ message: 'Wyróżnione laptopy' });
});

// 2. Średnio specyficzna - jeden parametr
app.get('/products/featured/:category', (req, res) => {
  res.json({
    message: `Wyróżnione: ${req.params.category}`,
    category: req.params.category
  });
});

// 3. Mniej specyficzna - dwa parametry
app.get('/products/:category/:id', (req, res) => {
  const { category, id } = req.params;
  res.json({
    message: `Produkt ${id} w kategorii ${category}`,
    category,
    id
  });
});

// 4. Najmniej specyficzna - pojedynczy parametr
app.get('/products/:id', (req, res) => {
  res.json({
    message: `Produkt o ID: ${req.params.id}`,
    id: req.params.id
  });
});

// ========================================
// PRZYKŁAD 4: Użycie next() do łańcuchowania
// ========================================

// Pierwsze dopasowanie - walidacja
app.get('/search', (req, res, next) => {
  if (!req.query.q) {
    return res.status(400).json({
      error: 'Brak parametru wyszukiwania'
    });
  }

  // Przekaż do następnej pasującej trasy
  next();
});

// Drugie dopasowanie - logika biznesowa
app.get('/search', (req, res) => {
  const results = performSearch(req.query.q);
  res.json({
    query: req.query.q,
    results
  });
});

function performSearch(query) {
  return [`Wynik 1 dla: ${query}`, `Wynik 2 dla: ${query}`];
}

// ========================================
// PRZYKŁAD 5: Trasy catch-all i 404
// ========================================

// Wszystkie specyficzne trasy muszą być PRZED tym

// Catch-all dla API - przed ogólnym 404
app.all('/api/*', (req, res) => {
  res.status(404).json({
    error: 'Endpoint API nie znaleziony',
    path: req.path
  });
});

// Ogólny 404 - zawsze OSTATNI
app.use((req, res) => {
  res.status(404).json({
    error: 'Strona nie znaleziona',
    path: req.path,
    method: req.method
  });
});

// ========================================
// PRZYKŁAD 6: Error handler - zawsze ostatni
// ========================================

// Error handler musi być OSTATNI (po wszystkich trasach)
app.use((err, req, res, next) => {
  console.error('Błąd:', err.message);
  res.status(err.status || 500).json({
    error: err.message || 'Błąd serwera',
    path: req.path
  });
});

// ========================================
// Demonstracja problemu z kolejnością
// ========================================

// Trasa testowa pokazująca problem
app.get('/demo/test', (req, res) => {
  res.json({
    message: 'To jest konkretny endpoint /demo/test',
    matchedBy: 'Specyficzna trasa'
  });
});

// Gdyby ta trasa była pierwsza, przechwyciłaby /demo/test
app.get('/demo/:action', (req, res) => {
  res.json({
    message: `Akcja: ${req.params.action}`,
    matchedBy: 'Parametryczna trasa'
  });
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Serwer działa na porcie ${PORT}`);
  console.log('\nPrzykłady endpointów:');
  console.log('- GET /users/admin (zwróci panel admina)');
  console.log('- GET /users/123 (zwróci użytkownika 123)');
  console.log('- GET /products/featured/laptops');
  console.log('- GET /demo/test (zwróci specyficzną trasę)');
  console.log('- GET /demo/send (zwróci parametryczną trasę)');
});
graph TD
    A[Żądanie HTTP przychodzi] --> B{Czy pasuje do pierwszej trasy?}
    B -->|Tak| C[Wykonaj handler]
    B -->|Nie| D{Czy pasuje do drugiej trasy?}

    C --> E{Czy wywołano next?}
    E -->|Tak| D
    E -->|Nie| F[Zwróć odpowiedź]

    D -->|Tak| G[Wykonaj handler]
    D -->|Nie| H{Czy pasuje do trzeciej trasy?}

    G --> I{Czy wywołano next?}
    I -->|Tak| H
    I -->|Nie| F

    H -->|Tak| J[Wykonaj handler]
    H -->|Nie| K[... dalsze trasy]

    J --> F
    K --> L[404 lub error handler]
    L --> F

    style A fill:#e1f5ff
    style C fill:#ffe1e1
    style G fill:#ffe1e1
    style J fill:#ffe1e1
    style F fill:#e1ffe1
    style L fill:#fff4e1

Materiały

11. Jak obsługiwać wiele metod HTTP dla tej samej ścieżki używając app.route()?

Odpowiedź w 30 sekund: app.route() pozwala na łańcuchowe definiowanie wielu metod HTTP dla tej samej ścieżki, co eliminuje powtarzanie ścieżki i tworzy bardziej zorganizowany kod. Zamiast wielokrotnie pisać app.get('/users'), app.post('/users'), możemy użyć app.route('/users').get().post().put().delete().

Odpowiedź w 2 minuty: Metoda app.route() jest wygodnym sposobem na tworzenie łańcuchowych handlerów dla różnych metod HTTP działających na tej samej ścieżce. Jest to szczególnie przydatne przy budowaniu RESTful API, gdzie jeden zasób (endpoint) obsługuje wiele operacji CRUD poprzez różne metody HTTP.

Główną zaletą app.route() jest eliminacja duplikacji kodu - ścieżka jest definiowana tylko raz, a następnie do niej "przykuwamy" różne metody HTTP. To nie tylko czyni kod bardziej czytelnym i łatwiejszym w utrzymaniu, ale również redukuje ryzyko błędów typu literówek w ścieżkach i ułatwia refaktoryzację.

app.route() zwraca instancję Route, która pozwala na łańcuchowe wywoływanie metod .get(), .post(), .put(), .patch(), .delete(), i .all(). Każda z tych metod przyjmuje jeden lub więcej handlerów (funkcji callback), które mogą zawierać middleware specyficzne dla danej metody.

Podejście to jest szczególnie czytelne i zgodne z zasadami REST, gdzie każdy zasób ma jasno zdefiniowane operacje. Może być również łączone z express.Router() dla jeszcze lepszej organizacji kodu w większych aplikacjach.

Przykład kodu:

const express = require('express');
const app = express();

app.use(express.json());

// ========================================
// PRZYKŁAD 1: Podstawowe użycie app.route()
// ========================================

// Tradycyjne podejście (bez route())
/*
app.get('/users', (req, res) => { ... });
app.post('/users', (req, res) => { ... });
app.put('/users', (req, res) => { ... });
app.delete('/users', (req, res) => { ... });
*/

// Nowoczesne podejście (z route())
app.route('/users')
  .get((req, res) => {
    // GET /users - pobierz wszystkich użytkowników
    res.json({
      message: 'Lista użytkowników',
      users: [
        { id: 1, name: 'Anna', email: 'anna@example.com' },
        { id: 2, name: 'Jan', email: 'jan@example.com' }
      ]
    });
  })
  .post((req, res) => {
    // POST /users - utwórz nowego użytkownika
    const newUser = req.body;
    res.status(201).json({
      message: 'Użytkownik utworzony',
      user: { id: 3, ...newUser }
    });
  })
  .put((req, res) => {
    // PUT /users - aktualizacja zbiorcza (rzadko używane)
    res.status(405).json({
      error: 'Metoda nie dozwolona dla tej ścieżki'
    });
  })
  .delete((req, res) => {
    // DELETE /users - usuń wszystkich (niebezpieczne!)
    res.status(405).json({
      error: 'Metoda nie dozwolona'
    });
  });

// ========================================
// PRZYKŁAD 2: Użycie z parametrami trasy
// ========================================

app.route('/users/:id')
  .get((req, res) => {
    // GET /users/:id - pobierz konkretnego użytkownika
    const userId = req.params.id;
    res.json({
      message: `Szczegóły użytkownika ${userId}`,
      user: {
        id: userId,
        name: 'Anna',
        email: 'anna@example.com'
      }
    });
  })
  .put((req, res) => {
    // PUT /users/:id - aktualizuj całego użytkownika
    const userId = req.params.id;
    const userData = req.body;
    res.json({
      message: `Użytkownik ${userId} zaktualizowany`,
      user: { id: userId, ...userData }
    });
  })
  .patch((req, res) => {
    // PATCH /users/:id - częściowa aktualizacja
    const userId = req.params.id;
    const updates = req.body;
    res.json({
      message: `Użytkownik ${userId} częściowo zaktualizowany`,
      updates
    });
  })
  .delete((req, res) => {
    // DELETE /users/:id - usuń użytkownika
    const userId = req.params.id;
    res.json({
      message: `Użytkownik ${userId} usunięty`
    });
  });

// ========================================
// PRZYKŁAD 3: Middleware w app.route()
// ========================================

// Middleware walidacji
const validateProduct = (req, res, next) => {
  const { name, price } = req.body;

  if (!name || !price) {
    return res.status(400).json({
      error: 'Nazwa i cena są wymagane'
    });
  }

  if (price <= 0) {
    return res.status(400).json({
      error: 'Cena musi być większa od 0'
    });
  }

  next();
};

// Middleware autoryzacji
const requireAdmin = (req, res, next) => {
  const isAdmin = req.headers['x-admin'] === 'true';

  if (!isAdmin) {
    return res.status(403).json({
      error: 'Wymagane uprawnienia administratora'
    });
  }

  next();
};

app.route('/products')
  .get((req, res) => {
    // GET bez middleware - publiczny dostęp
    res.json({
      products: [
        { id: 1, name: 'Laptop', price: 3000 },
        { id: 2, name: 'Mysz', price: 50 }
      ]
    });
  })
  .post(requireAdmin, validateProduct, (req, res) => {
    // POST z dwoma middleware
    res.status(201).json({
      message: 'Produkt utworzony',
      product: { id: 3, ...req.body }
    });
  })
  .delete(requireAdmin, (req, res) => {
    // DELETE tylko z middleware autoryzacji
    res.json({ message: 'Wszystkie produkty usunięte' });
  });

// ========================================
// PRZYKŁAD 4: Kombinacja z Router
// ========================================

const router = express.Router();

// Middleware dla całego routera
router.use((req, res, next) => {
  console.log('Router API Time:', Date.now());
  next();
});

// Użycie route() wewnątrz routera
router.route('/articles')
  .get((req, res) => {
    res.json({
      articles: [
        { id: 1, title: 'Express.js - wprowadzenie' },
        { id: 2, title: 'Routing w Express' }
      ]
    });
  })
  .post((req, res) => {
    res.status(201).json({
      message: 'Artykuł utworzony',
      article: req.body
    });
  });

router.route('/articles/:id')
  .get((req, res) => {
    res.json({
      article: {
        id: req.params.id,
        title: 'Express.js - wprowadzenie',
        content: 'Treść artykułu...'
      }
    });
  })
  .put((req, res) => {
    res.json({
      message: `Artykuł ${req.params.id} zaktualizowany`
    });
  })
  .delete((req, res) => {
    res.json({
      message: `Artykuł ${req.params.id} usunięty`
    });
  });

// Montowanie routera
app.use('/api', router);

// ========================================
// PRZYKŁAD 5: Zaawansowane użycie z all()
// ========================================

app.route('/admin/*')
  .all((req, res, next) => {
    // Middleware dla WSZYSTKICH metod HTTP
    console.log('Admin area accessed:', req.path);

    // Sprawdzenie autoryzacji
    const token = req.headers.authorization;

    if (!token) {
      return res.status(401).json({
        error: 'Wymagana autoryzacja'
      });
    }

    next();
  })
  .get((req, res) => {
    res.json({ message: 'Admin GET' });
  })
  .post((req, res) => {
    res.json({ message: 'Admin POST' });
  });

// ========================================
// PRZYKŁAD 6: Praktyczny przykład RESTful API
// ========================================

// Symulowana baza danych
let books = [
  { id: 1, title: 'JavaScript: The Good Parts', author: 'Douglas Crockford' },
  { id: 2, title: 'Eloquent JavaScript', author: 'Marijn Haverbeke' }
];

let nextId = 3;

app.route('/books')
  .get((req, res) => {
    // Filtrowanie po autorze (opcjonalnie)
    const { author } = req.query;

    const filtered = author
      ? books.filter(b =>
          b.author.toLowerCase().includes(author.toLowerCase())
        )
      : books;

    res.json({
      count: filtered.length,
      books: filtered
    });
  })
  .post((req, res) => {
    const { title, author } = req.body;

    if (!title || !author) {
      return res.status(400).json({
        error: 'Tytuł i autor są wymagane'
      });
    }

    const newBook = { id: nextId++, title, author };
    books.push(newBook);

    res.status(201).json({
      message: 'Książka dodana',
      book: newBook
    });
  });

app.route('/books/:id')
  .get((req, res) => {
    const bookId = parseInt(req.params.id);
    const book = books.find(b => b.id === bookId);

    if (!book) {
      return res.status(404).json({
        error: 'Książka nie znaleziona'
      });
    }

    res.json({ book });
  })
  .put((req, res) => {
    const bookId = parseInt(req.params.id);
    const { title, author } = req.body;

    const index = books.findIndex(b => b.id === bookId);

    if (index === -1) {
      return res.status(404).json({
        error: 'Książka nie znaleziona'
      });
    }

    books[index] = { id: bookId, title, author };

    res.json({
      message: 'Książka zaktualizowana',
      book: books[index]
    });
  })
  .patch((req, res) => {
    const bookId = parseInt(req.params.id);
    const updates = req.body;

    const book = books.find(b => b.id === bookId);

    if (!book) {
      return res.status(404).json({
        error: 'Książka nie znaleziona'
      });
    }

    Object.assign(book, updates);

    res.json({
      message: 'Książka zaktualizowana',
      book
    });
  })
  .delete((req, res) => {
    const bookId = parseInt(req.params.id);
    const index = books.findIndex(b => b.id === bookId);

    if (index === -1) {
      return res.status(404).json({
        error: 'Książka nie znaleziona'
      });
    }

    books.splice(index, 1);

    res.json({
      message: 'Książka usunięta',
      id: bookId
    });
  });

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Serwer działa na porcie ${PORT}`);
});
graph LR
    A[app.route'/users'] --> B[.get]
    A --> C[.post]
    A --> D[.put]
    A --> E[.delete]

    B --> F[Handler GET]
    C --> G[Handler POST]
    D --> H[Handler PUT]
    E --> I[Handler DELETE]

    F --> J[Odpowiedź]
    G --> J
    H --> J
    I --> J

    style A fill:#e1f5ff
    style B fill:#ffe1e1
    style C fill:#ffe1e1
    style D fill:#ffe1e1
    style E fill:#ffe1e1
    style J fill:#e1ffe1

Materiały

12. Czym są wildcards i wyrażenia regularne w trasach Express?

Odpowiedź w 30 sekund: Wildcards (*, +, ?, ()) i wyrażenia regularne pozwalają na tworzenie elastycznych wzorców tras w Express. Znak * pasuje do dowolnej sekwencji znaków, + wymaga co najmniej jednego wystąpienia, ? oznacza opcjonalność, a pełne wyrażenia regularne (regex) dają maksymalną kontrolę nad dopasowaniem ścieżek.

Odpowiedź w 2 minuty: Express obsługuje zaawansowane wzorce tras używając wildcards i wyrażeń regularnych, co pozwala na tworzenie elastycznych reguł routingu bez konieczności definiowania każdej możliwej kombinacji osobno. Te mechanizmy są szczególnie przydatne przy tworzeniu catch-all routes, obsłudze wielu podobnych ścieżek czy walidacji formatów URLi.

Wildcards to specjalne znaki używane w łańcuchach ścieżek: gwiazdka (*) pasuje do dowolnego ciągu znaków (włącznie z pustym), plus (+) wymaga co najmniej jednego wystąpienia poprzedzającego go znaku, znak zapytania (?) czyni poprzedzający znak opcjonalnym, a nawiasy () pozwalają na grupowanie. Te wzorce są prostsze niż regex, ale mniej precyzyjne.

Wyrażenia regularne dają pełną kontrolę nad dopasowaniem ścieżek. W Express można używać regex bezpośrednio zamiast stringa jako ścieżki. Regex pozwala na zaawansowaną walidację - np. sprawdzenie czy ID jest liczbą, czy email ma prawidłowy format, lub dopasowanie tylko określonych wzorców. Express konwertuje stringi ścieżek na regex w tle, używając biblioteki path-to-regexp.

Warto jednak pamiętać, że nadmierne używanie wildcards i regex może uczynić kod trudniejszym do zrozumienia i debugowania. Lepiej jest używać ich z umiarem, preferując explicit routing tam gdzie to możliwe, a wzorce stosować tylko gdy rzeczywiście przynoszą korzyść.

Przykład kodu:

const express = require('express');
const app = express();

// ========================================
// PRZYKŁAD 1: Wildcard * (gwiazdka)
// ========================================

// * pasuje do dowolnej sekwencji znaków
app.get('/files/*', (req, res) => {
  // Pasuje do: /files/document.pdf, /files/images/photo.jpg
  res.json({
    message: 'Obsługa plików',
    path: req.path,
    // req.params[0] zawiera część dopasowaną przez *
    matched: req.params[0]
  });
});

// * na końcu - catch-all dla sekcji
app.get('/blog/*', (req, res) => {
  res.json({
    message: 'Blog catch-all',
    subpath: req.params[0]
  });
});

// Wiele wildcards
app.get('/api/*/users/*', (req, res) => {
  // Pasuje do: /api/v1/users/123, /api/v2/users/admin
  res.json({
    version: req.params[0],  // pierwszy *
    userId: req.params[1]     // drugi *
  });
});

// ========================================
// PRZYKŁAD 2: Znak ? (opcjonalność)
// ========================================

// ? czyni poprzedzający znak opcjonalnym
app.get('/colou?r', (req, res) => {
  // Pasuje do: /color i /colour
  res.json({ message: 'Kolor (US i UK spelling)' });
});

app.get('/files/image.jpe?g', (req, res) => {
  // Pasuje do: /files/image.jpg i /files/image.jpeg
  res.json({ message: 'Obraz JPEG' });
});

// Opcjonalny segment
app.get('/users/:id/posts/:postId?', (req, res) => {
  // Pasuje do: /users/1/posts i /users/1/posts/5
  const { id, postId } = req.params;

  if (postId) {
    res.json({ message: `Post ${postId} użytkownika ${id}` });
  } else {
    res.json({ message: `Wszystkie posty użytkownika ${id}` });
  }
});

// ========================================
// PRZYKŁAD 3: Znak + (jeden lub więcej)
// ========================================

// + wymaga co najmniej jednego wystąpienia
app.get('/wo+w', (req, res) => {
  // Pasuje do: /wow, /woow, /wooow, itd.
  res.json({ message: 'Wow!' });
});

app.get('/ha+', (req, res) => {
  // Pasuje do: /ha, /haa, /haaa, itd.
  res.json({ message: 'Śmiech' });
});

// ========================================
// PRZYKŁAD 4: Nawiasy () (grupowanie)
// ========================================

// Grupowanie znaków
app.get('/ab(cd)?e', (req, res) => {
  // Pasuje do: /abe i /abcde
  res.json({ message: 'Grupowanie' });
});

app.get('/(api/)?users', (req, res) => {
  // Pasuje do: /users i /api/users
  res.json({ message: 'Użytkownicy (z lub bez api/)' });
});

// ========================================
// PRZYKŁAD 5: Wyrażenia regularne - podstawy
// ========================================

// Regex jako obiekt RegExp
app.get(/.*fly$/, (req, res) => {
  // Pasuje do wszystkiego co kończy się na 'fly'
  // /butterfly, /dragonfly, itd.
  res.json({
    message: 'Kończy się na fly',
    path: req.path
  });
});

app.get(/^\/api\/v[0-9]+\//, (req, res) => {
  // Pasuje do: /api/v1/, /api/v2/, /api/v123/, itd.
  res.json({
    message: 'API z wersją numeryczną',
    path: req.path
  });
});

// ========================================
// PRZYKŁAD 6: Walidacja parametrów z regex
// ========================================

// ID musi być liczbą
app.get('/users/:id(\\d+)', (req, res) => {
  // Pasuje tylko do: /users/123, /users/456
  // NIE pasuje do: /users/abc, /users/admin
  res.json({
    message: 'Użytkownik (tylko numeryczne ID)',
    userId: req.params.id
  });
});

// UUID format (uproszczony)
app.get('/products/:id([0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12})',
  (req, res) => {
    res.json({
      message: 'Produkt z UUID',
      productId: req.params.id
    });
  }
);

// Email-like pattern (uproszczony)
app.get('/users/:email([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})',
  (req, res) => {
    res.json({
      message: 'Użytkownik po emailu',
      email: req.params.email
    });
  }
);

// ========================================
// PRZYKŁAD 7: Praktyczne zastosowania
// ========================================

// Obsługa wielu formatów plików
app.get('/download/:filename.:format(pdf|docx|txt)', (req, res) => {
  const { filename, format } = req.params;
  res.json({
    message: `Pobieranie pliku: ${filename}.${format}`,
    allowedFormats: ['pdf', 'docx', 'txt']
  });
});

// Obsługa różnych języków
app.get('/:lang(pl|en|de)?/products', (req, res) => {
  const lang = req.params.lang || 'pl'; // domyślnie polski
  res.json({
    message: 'Produkty',
    language: lang
  });
});

// Data w formacie YYYY-MM-DD
app.get('/reports/:date(\\d{4}-\\d{2}-\\d{2})', (req, res) => {
  const { date } = req.params;
  res.json({
    message: `Raport z dnia: ${date}`,
    format: 'YYYY-MM-DD'
  });
});

// ========================================
// PRZYKŁAD 8: Zaawansowane regex
// ========================================

// Ścieżka do plików z rozszerzeniem
app.get(/^\/static\/(.+)\.(jpg|jpeg|png|gif)$/, (req, res) => {
  // Pasuje do: /static/image.jpg, /static/folder/photo.png
  const filename = req.params[0];  // nazwa pliku bez rozszerzenia
  const extension = req.params[1]; // rozszerzenie

  res.json({
    message: 'Obraz statyczny',
    filename,
    extension
  });
});

// Obsługa wersji API
app.get(/^\/api\/v(\d+(?:\.\d+)?)\/(.+)$/, (req, res) => {
  // Pasuje do: /api/v1/users, /api/v2.5/products
  const version = req.params[0];    // wersja (np. "1" lub "2.5")
  const endpoint = req.params[1];   // endpoint (np. "users")

  res.json({
    apiVersion: version,
    endpoint,
    message: `API v${version} - ${endpoint}`
  });
});

// ========================================
// PRZYKŁAD 9: Catch-all z obsługą błędów
// ========================================

// Wszystkie trasy API (przed catch-all)
app.get('/api/users', (req, res) => {
  res.json({ users: ['Anna', 'Jan'] });
});

// Catch-all dla nieznanych endpointów API
app.all('/api/*', (req, res) => {
  res.status(404).json({
    error: 'Endpoint API nie znaleziony',
    path: req.path,
    method: req.method,
    suggestion: 'Sprawdź dokumentację API'
  });
});

// Ogólny catch-all (musi być ostatni)
app.all('*', (req, res) => {
  res.status(404).json({
    error: 'Strona nie znaleziona',
    path: req.path
  });
});

// ========================================
// PRZYKŁAD 10: Kombinacje różnych wzorców
// ========================================

// Elastyczny routing dla zasobów
app.get('/resources/:type(articles|videos|podcasts)/:id(\\d+)?',
  (req, res) => {
    const { type, id } = req.params;

    if (id) {
      res.json({
        message: `Konkretny ${type}: ${id}`
      });
    } else {
      res.json({
        message: `Lista: ${type}`
      });
    }
  }
);

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Serwer działa na porcie ${PORT}`);
  console.log('\nPrzykłady do testowania:');
  console.log('- /files/document.pdf');
  console.log('- /color lub /colour');
  console.log('- /users/123 (tylko liczby)');
  console.log('- /download/report.pdf');
  console.log('- /pl/products lub /en/products');
  console.log('- /reports/2024-01-15');
  console.log('- /api/v1/users');
});
graph TD
    A[Żądanie HTTP] --> B{Typ wzorca trasy}

    B -->|String prosty| C[Dokładne dopasowanie<br/>/users]
    B -->|Parametr| D[Dopasowanie dynamiczne<br/>/users/:id]
    B -->|Wildcard *| E[Dowolny ciąg<br/>/files/*]
    B -->|Opcjonalny ?| F[Znak opcjonalny<br/>/colou?r]
    B -->|Regex| G[Wyrażenie regularne<br/>/users/\\d+]

    C --> H{Czy ścieżka = /users?}
    D --> I{Czy wzorzec pasuje?}
    E --> J{Czy zaczyna się od /files/?}
    F --> K{Czy pasuje /color lub /colour?}
    G --> L{Czy pasuje regex?}

    H -->|Tak| M[Wykonaj handler]
    H -->|Nie| N[Następna trasa]

    I -->|Tak| M
    I -->|Nie| N

    J -->|Tak| M
    J -->|Nie| N

    K -->|Tak| M
    K -->|Nie| N

    L -->|Tak| M
    L -->|Nie| N

    M --> O[Zwróć odpowiedź]
    N --> P[Szukaj dalej...]

    style A fill:#e1f5ff
    style C fill:#ffe1e1
    style D fill:#ffe1e1
    style E fill:#fff4e1
    style F fill:#fff4e1
    style G fill:#e1ffe1
    style M fill:#e1ffe1
    style O fill:#d1f5d1

Materiały

Middleware

13. Czym jest middleware w Express.js i jak działa łańcuch middleware?

Odpowiedź w 30 sekund: Middleware to funkcje, które mają dostęp do obiektów request (req), response (res) i funkcji next() w cyklu żądanie-odpowiedź aplikacji. Łańcuch middleware działa sekwencyjnie - każda funkcja middleware może przetworzyć żądanie, zmodyfikować obiekty req/res, zakończyć żądanie lub przekazać kontrolę do kolejnego middleware wywołując next().

Odpowiedź w 2 minuty: Middleware w Express.js to fundamentalny wzorzec architektoniczny, który pozwala na modularną obsługę żądań HTTP. Każda funkcja middleware ma dostęp do trzech parametrów: obiektu żądania (req), obiektu odpowiedzi (res) oraz funkcji next(), która przekazuje kontrolę do następnego middleware w łańcuchu.

Łańcuch middleware działa jak pipeline - żądanie przechodzi przez kolejne funkcje middleware w takiej kolejności, w jakiej zostały zarejestrowane w aplikacji. Każde middleware może wykonać dowolny kod, dokonać zmian w obiektach req i res, zakończyć cykl żądanie-odpowiedź wysyłając odpowiedź, lub przekazać kontrolę do następnego middleware poprzez wywołanie next().

Jeśli middleware nie wywoła next() ani nie wyśle odpowiedzi, żądanie zostanie "zawieszone" i klient nie otrzyma odpowiedzi. Express.js wykorzystuje middleware do różnych celów: parsowania body, obsługi CORS, logowania, uwierzytelniania, walidacji, obsługi błędów i wielu innych.

Istotną cechą łańcucha middleware jest jego jednokierunkowość - żądanie przepływa od góry do dołu przez zdefiniowane middleware, a następnie wraca przez stos (jeśli middleware wykonują kod po wywołaniu next()). To pozwala na implementację zarówno pre-processing jak i post-processing logiki.

Przykład kodu:

const express = require('express');
const app = express();

// Middleware 1: Logowanie czasu żądania
app.use((req, res, next) => {
  req.requestTime = new Date().toISOString();
  console.log(`[${req.requestTime}] ${req.method} ${req.url}`);
  next(); // Przekazanie kontroli do następnego middleware
});

// Middleware 2: Dodanie niestandardowego nagłówka
app.use((req, res, next) => {
  res.setHeader('X-Powered-By', 'Express Middleware');
  next();
});

// Middleware 3: Uwierzytelnianie
app.use((req, res, next) => {
  const token = req.headers.authorization;
  if (token === 'secret-token') {
    req.authenticated = true;
    next(); // Użytkownik uwierzytelniony, kontynuuj
  } else {
    res.status(401).json({ error: 'Brak autoryzacji' });
    // Brak wywołania next() - łańcuch przerwany
  }
});

// Route handler - końcowy punkt w łańcuchu
app.get('/api/data', (req, res) => {
  res.json({
    message: 'Dane dla uwierzytelnionego użytkownika',
    requestTime: req.requestTime,
    authenticated: req.authenticated
  });
});

// Diagram przepływu:
// Request → Middleware 1 (log) → Middleware 2 (header) →
// Middleware 3 (auth) → Route Handler → Response

Diagram łańcucha middleware:

graph TD
    A[Żądanie HTTP] --> B[Middleware 1: Logowanie]
    B --> C[next wywołane?]
    C -->|Tak| D[Middleware 2: Parsowanie]
    C -->|Nie| Z[Odpowiedź wysłana]
    D --> E[next wywołane?]
    E -->|Tak| F[Middleware 3: Uwierzytelnianie]
    E -->|Nie| Z
    F --> G[next wywołane?]
    G -->|Tak| H[Route Handler]
    G -->|Nie| Z
    H --> I[Wysłanie odpowiedzi]
    I --> Z[Koniec]

Materiały

14. Jaka jest różnica między middleware aplikacji, routera i obsługi błędów?

Odpowiedź w 30 sekund: Middleware aplikacji (app.use) działa globalnie dla całej aplikacji, middleware routera (router.use) tylko dla określonych tras w obrębie routera, a middleware obsługi błędów ma cztery parametry (err, req, res, next) i obsługuje błędy wyrzucone w poprzednich middleware lub route handlerach.

Odpowiedź w 2 minuty: Express.js rozróżnia trzy główne typy middleware, które różnią się zakresem działania i przeznaczeniem.

Middleware aplikacji jest rejestrowany bezpośrednio na obiekcie app za pomocą app.use() lub metod HTTP (app.get, app.post, etc.). Działa globalnie dla wszystkich żądań do aplikacji lub dla określonych ścieżek. Jest wykonywany w kolejności rejestracji i służy do implementacji funkcjonalności ogólnoapp.licacyjnej jak logowanie, parsowanie body, CORS czy kompresja odpowiedzi.

Middleware routera działa w podobny sposób, ale jest związany z konkretnym obiektem Router i dotyczy tylko tras zdefiniowanych w tym routerze. Pozwala to na modularyzację aplikacji - różne grupy tras mogą mieć różne middleware. Na przykład, trasy API mogą mieć middleware do parsowania JSON, podczas gdy trasy adminowe mogą mieć middleware do uwierzytelniania administratorów.

Middleware obsługi błędów to specjalny typ middleware, który zawsze przyjmuje cztery parametry: (err, req, res, next). Jest wywoływany automatycznie, gdy poprzednie middleware lub route handler wyrzuci błąd lub przekaże błąd przez next(err). Middleware obsługi błędów powinien być zdefiniowany jako ostatni w łańcuchu middleware, aby mógł przechwycić wszystkie błędy z aplikacji.

Przykład kodu:

const express = require('express');
const app = express();

// === MIDDLEWARE APLIKACJI ===
// Działa dla wszystkich żądań w aplikacji
app.use(express.json());

app.use((req, res, next) => {
  console.log('Globalne middleware - każde żądanie');
  next();
});

// Middleware aplikacji dla konkretnej ścieżki
app.use('/admin', (req, res, next) => {
  console.log('Middleware tylko dla /admin/*');
  next();
});

// === MIDDLEWARE ROUTERA ===
const apiRouter = express.Router();
const adminRouter = express.Router();

// Middleware routera - tylko dla tras w apiRouter
apiRouter.use((req, res, next) => {
  console.log('Middleware API Router - tylko trasy API');
  req.apiVersion = '1.0';
  next();
});

apiRouter.use((req, res, next) => {
  // Walidacja tokena API
  const apiKey = req.headers['x-api-key'];
  if (!apiKey) {
    return next(new Error('Brak klucza API'));
  }
  next();
});

// Middleware routera adminowego
adminRouter.use((req, res, next) => {
  console.log('Middleware Admin Router');
  // Sprawdzenie uprawnień admina
  if (req.headers.role !== 'admin') {
    return next(new Error('Brak uprawnień administratora'));
  }
  next();
});

// Trasy routera
apiRouter.get('/users', (req, res) => {
  res.json({ version: req.apiVersion, users: [] });
});

adminRouter.get('/dashboard', (req, res) => {
  res.json({ message: 'Panel administratora' });
});

// Montowanie routerów do aplikacji
app.use('/api', apiRouter);
app.use('/admin', adminRouter);

// === MIDDLEWARE OBSŁUGI BŁĘDÓW ===
// ZAWSZE cztery parametry (err, req, res, next)
// Musi być zdefiniowany jako ostatni
app.use((err, req, res, next) => {
  console.error('Błąd przechwycony:', err.message);

  // Logowanie szczegółów błędu
  console.error(err.stack);

  // Wysłanie odpowiedzi z błędem
  res.status(err.status || 500).json({
    error: {
      message: err.message,
      status: err.status || 500
    }
  });
});

// Middleware 404 (opcjonalny, przed obsługą błędów)
app.use((req, res, next) => {
  const error = new Error('Nie znaleziono zasobu');
  error.status = 404;
  next(error);
});

app.listen(3000, () => {
  console.log('Serwer działa na porcie 3000');
});

Diagram struktury middleware:

graph TD
    A[Żądanie HTTP] --> B[Middleware Aplikacji 1]
    B --> C[Middleware Aplikacji 2]
    C --> D{Ścieżka /api?}
    D -->|Tak| E[Middleware Routera API]
    D -->|Nie| F{Ścieżka /admin?}
    E --> G[Route Handler API]
    F -->|Tak| H[Middleware Routera Admin]
    F -->|Nie| I[Middleware 404]
    H --> J[Route Handler Admin]
    G --> K{Błąd?}
    J --> K
    I --> K
    K -->|Tak| L[Middleware Obsługi Błędów]
    K -->|Nie| M[Odpowiedź sukces]
    L --> N[Odpowiedź błąd]

Materiały

15. Jak działa funkcja next() i kiedy należy ją wywoływać?

Odpowiedź w 30 sekund: Funkcja next() przekazuje kontrolę do następnego middleware w łańcuchu. Należy ją wywołać gdy middleware zakończył swoją pracę i chce przekazać żądanie dalej. Wywołanie next(err) z argumentem przekazuje błąd do middleware obsługi błędów, pomijając pozostałe normalne middleware.

Odpowiedź w 2 minuty: Funkcja next() jest trzecim parametrem funkcji middleware (obok req i res) i stanowi klucz do zarządzania przepływem wykonania w Express.js. Jej głównym zadaniem jest przekazanie kontroli do następnego middleware w stosie.

Kiedy middleware wywołuje next(), Express przechodzi do kolejnej funkcji middleware zarejestrowanej w aplikacji. Jeśli middleware nie wywoła next() ani nie wyśle odpowiedzi (res.send, res.json, etc.), żądanie zostanie "zawieszone" i klient nigdy nie otrzyma odpowiedzi, co spowoduje timeout.

Funkcja next() może być wywołana na kilka sposobów:

  • next() bez argumentów - kontynuuje do następnego middleware w normalnym przepływie
  • next(error) z obiektem błędu - pomija wszystkie pozostałe normalne middleware i przechodzi bezpośrednio do middleware obsługi błędów
  • next('route') - pomija pozostałe middleware w bieżącym routerze i przechodzi do następnej pasującej trasy (tylko w route handlers)
  • next('router') - pomija pozostałe middleware w bieżącym routerze i wychodzi z routera

Ważne jest, aby nie wywoływać next() więcej niż raz w tym samym middleware, ponieważ może to prowadzić do nieprzewidywalnego zachowania. Również nie należy wywoływać next() po wysłaniu odpowiedzi, gdyż może to spowodować błędy.

Przykład kodu:

const express = require('express');
const app = express();

// === PRZYKŁAD 1: Podstawowe użycie next() ===
app.use((req, res, next) => {
  console.log('Middleware 1: Rozpoczęcie');
  req.timestamp = Date.now();
  next(); // Przejdź do następnego middleware
  console.log('Middleware 1: Po next() - kod wykonany po powrocie');
});

app.use((req, res, next) => {
  console.log('Middleware 2: Przetwarzanie');
  next(); // Kontynuuj dalej
});

// === PRZYKŁAD 2: next() z błędem ===
app.use((req, res, next) => {
  const apiKey = req.headers['x-api-key'];

  if (!apiKey) {
    // Przekaż błąd - pominie normalne middleware, przejdzie do obsługi błędów
    const error = new Error('Brak klucza API');
    error.status = 401;
    return next(error); // WAŻNE: return zapobiega dalszemu wykonaniu
  }

  // Wszystko OK, kontynuuj normalnie
  next();
});

// === PRZYKŁAD 3: next('route') - pomiń pozostałe handlery ===
app.get('/user/:id', (req, res, next) => {
  // Jeśli ID to 0, pomiń ten handler i przejdź do następnej trasy
  if (req.params.id === '0') {
    return next('route');
  }

  // Normalnie zwróć użytkownika
  res.json({ id: req.params.id, name: 'Jan Kowalski' });
});

// Ta trasa zostanie wywołana gdy poprzednia użyje next('route')
app.get('/user/:id', (req, res) => {
  res.json({ id: 0, name: 'Użytkownik specjalny' });
});

// === PRZYKŁAD 4: Asynchroniczne middleware ===
app.use(async (req, res, next) => {
  try {
    // Asynchroniczna operacja
    const user = await fetchUserFromDatabase(req.headers.userId);
    req.user = user;
    next(); // Wywołaj next() po zakończeniu operacji async
  } catch (error) {
    next(error); // Przekaż błąd w przypadku niepowodzenia
  }
});

// === PRZYKŁAD 5: Błąd - wywołanie next() po wysłaniu odpowiedzi ===
app.use((req, res, next) => {
  res.json({ message: 'Odpowiedź wysłana' });
  next(); // BŁĄD! Nie wywołuj next() po wysłaniu odpowiedzi
  // To może spowodować błąd "Cannot set headers after they are sent"
});

// === PRZYKŁAD 6: Walidacja z next() ===
function validateUser(req, res, next) {
  const { username, email } = req.body;

  if (!username || username.length < 3) {
    const error = new Error('Username musi mieć minimum 3 znaki');
    error.status = 400;
    return next(error);
  }

  if (!email || !email.includes('@')) {
    const error = new Error('Nieprawidłowy email');
    error.status = 400;
    return next(error);
  }

  // Walidacja OK
  next();
}

app.post('/register', validateUser, (req, res) => {
  res.json({ message: 'Rejestracja pomyślna' });
});

// === MIDDLEWARE OBSŁUGI BŁĘDÓW ===
app.use((err, req, res, next) => {
  console.error('Błąd:', err.message);
  res.status(err.status || 500).json({
    error: err.message
  });
});

// Funkcja pomocnicza (przykład)
async function fetchUserFromDatabase(userId) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: userId, name: 'Jan Kowalski' });
    }, 100);
  });
}

app.listen(3000);

Diagram przepływu next():

graph TD
    A[Middleware 1] --> B{next wywołane?}
    B -->|next| C[Middleware 2]
    B -->|nexterr| D[Middleware Obsługi Błędów]
    B -->|next'route'| E[Następna pasująca trasa]
    B -->|Brak next| F[Żądanie zawieszone - TIMEOUT]
    C --> G{next wywołane?}
    G -->|next| H[Middleware 3]
    G -->|nexterr| D
    H --> I[Route Handler]
    I --> J[Odpowiedź]
    D --> K[Odpowiedź błędu]

Materiały

16. Czym jest body-parser i jak parsować dane JSON oraz formularzy?

Odpowiedź w 30 sekund: Body-parser to middleware do parsowania body żądań HTTP. Od Express 4.16+ jest wbudowany jako express.json() dla JSON i express.urlencoded() dla danych formularzy. Parsuje dane i udostępnia je w req.body, umożliwiając łatwy dostęp do danych wysłanych przez klienta.

Odpowiedź w 2 minuty: Body-parser był wcześniej osobnym pakietem npm używanym do parsowania body żądań HTTP w Express.js. Od wersji Express 4.16.0 jego najważniejsze funkcjonalności zostały wbudowane bezpośrednio w framework jako express.json() i express.urlencoded().

Middleware do parsowania body jest niezbędny, ponieważ dane przychodzące w żądaniu HTTP są strumieniem bajtów. Bez parsera, req.body byłby undefined. Parser konwertuje dane ze strumienia do użytecznych obiektów JavaScript.

express.json() parsuje żądania z nagłówkiem Content-Type: application/json i konwertuje JSON do obiektu JavaScript dostępnego w req.body. Jest to niezbędne przy budowie REST API, które komunikują się za pomocą JSON.

express.urlencoded() parsuje dane formularzy HTML wysyłane z nagłówkiem Content-Type: application/x-www-form-urlencoded. Opcja {extended: true} używa biblioteki qs do parsowania zagnieżdżonych obiektów, podczas gdy {extended: false} używa querystring (tylko proste pary klucz-wartość).

Ważne jest, aby pamiętać o limitach rozmiaru - domyślnie jest to 100kb. W przypadku większych plików (np. upload obrazów) należy użyć dedykowanych bibliotek jak multer. Również warto zabezpieczyć aplikację przed zbyt dużymi payloadami ustawiając odpowiedni limit.

Przykład kodu:

const express = require('express');
const app = express();

// === PARSOWANIE JSON ===
// Parsuje żądania z Content-Type: application/json
app.use(express.json({
  limit: '10mb', // Maksymalny rozmiar body (domyślnie 100kb)
  strict: true, // Akceptuj tylko tablice i obiekty
}));

// === PARSOWANIE FORMULARZY ===
// Parsuje dane formularzy HTML
app.use(express.urlencoded({
  extended: true, // Użyj qs library dla zagnieżdżonych obiektów
  limit: '10mb'
}));

// === PRZYKŁAD 1: Odbieranie danych JSON ===
app.post('/api/users', (req, res) => {
  // req.body zawiera sparsowany obiekt JSON
  const { name, email, age } = req.body;

  console.log('Otrzymane dane JSON:', {
    name,
    email,
    age
  });

  // Walidacja
  if (!name || !email) {
    return res.status(400).json({
      error: 'Name i email są wymagane'
    });
  }

  res.status(201).json({
    message: 'Użytkownik utworzony',
    user: { name, email, age }
  });
});

// Test z curl:
// curl -X POST http://localhost:3000/api/users \
//   -H "Content-Type: application/json" \
//   -d '{"name":"Jan Kowalski","email":"jan@example.com","age":30}'

// === PRZYKŁAD 2: Odbieranie danych z formularza HTML ===
app.post('/submit-form', (req, res) => {
  // req.body zawiera dane formularza
  const { username, password, remember } = req.body;

  console.log('Dane formularza:', {
    username,
    password,
    remember: remember === 'on' // checkbox
  });

  res.send(`<h1>Zalogowano użytkownika: ${username}</h1>`);
});

// HTML form który wysyła dane:
// <form action="/submit-form" method="POST">
//   <input type="text" name="username" required>
//   <input type="password" name="password" required>
//   <input type="checkbox" name="remember">
//   <button type="submit">Wyślij</button>
// </form>

// === PRZYKŁAD 3: Zagnieżdżone obiekty (extended: true) ===
app.post('/api/profile', (req, res) => {
  // extended: true pozwala na zagnieżdżone obiekty
  const {
    user: { name, age },
    address: { city, street }
  } = req.body;

  console.log('Zagnieżdżone dane:', req.body);

  res.json({
    message: 'Profil zaktualizowany',
    profile: req.body
  });
});

// Test z curl:
// curl -X POST http://localhost:3000/api/profile \
//   -H "Content-Type: application/json" \
//   -d '{"user":{"name":"Jan","age":30},"address":{"city":"Warszawa","street":"Marszałkowska"}}'

// === PRZYKŁAD 4: Własny middleware walidacji body ===
function validateJsonBody(req, res, next) {
  // Sprawdź czy body został sparsowany
  if (!req.body || Object.keys(req.body).length === 0) {
    return res.status(400).json({
      error: 'Body żądania jest pusty lub nieprawidłowy'
    });
  }
  next();
}

app.post('/api/data', validateJsonBody, (req, res) => {
  res.json({
    message: 'Dane otrzymane',
    data: req.body
  });
});

// === PRZYKŁAD 5: Różne parsery dla różnych ścieżek ===
const apiRouter = express.Router();
const formRouter = express.Router();

// Router API używa tylko JSON
apiRouter.use(express.json());
apiRouter.post('/users', (req, res) => {
  res.json({ received: req.body });
});

// Router formularzy używa urlencoded
formRouter.use(express.urlencoded({ extended: true }));
formRouter.post('/contact', (req, res) => {
  res.send(`Dziękujemy za wiadomość, ${req.body.name}`);
});

app.use('/api', apiRouter);
app.use('/forms', formRouter);

// === PRZYKŁAD 6: Obsługa błędów parsowania ===
app.use((err, req, res, next) => {
  // Błąd parsowania JSON
  if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
    return res.status(400).json({
      error: 'Nieprawidłowy format JSON'
    });
  }
  next(err);
});

// === PRZYKŁAD 7: Starszy sposób (body-parser jako osobny pakiet) ===
// const bodyParser = require('body-parser'); // NIE ZALECANE w Express 4.16+
// app.use(bodyParser.json());
// app.use(bodyParser.urlencoded({ extended: true }));

app.listen(3000, () => {
  console.log('Serwer nasłuchuje na porcie 3000');
});

Porównanie parserów:

// === TYPY CONTENT-TYPE ===
/*
1. application/json
   - Parsowany przez: express.json()
   - Format: {"key": "value"}
   - Użycie: REST API, AJAX requests

2. application/x-www-form-urlencoded
   - Parsowany przez: express.urlencoded()
   - Format: key1=value1&key2=value2
   - Użycie: standardowe formularze HTML

3. multipart/form-data
   - Parsowany przez: multer (osobny pakiet)
   - Format: boundary-separated data
   - Użycie: upload plików

4. text/plain
   - Parsowany przez: express.text()
   - Format: surowy tekst
   - Użycie: proste dane tekstowe

5. application/octet-stream
   - Parsowany przez: express.raw()
   - Format: surowe bajty (Buffer)
   - Użycie: binarne dane
*/

Materiały

17. Jak utworzyć własny middleware do logowania żądań?

Odpowiedź w 30 sekund: Własny middleware do logowania to funkcja przyjmująca (req, res, next), która wydobywa dane z żądania (metodę HTTP, URL, czas, IP), loguje je do konsoli lub pliku, a następnie wywołuje next(). Można rozszerzyć o logowanie czasu odpowiedzi używając event listenera na res.on('finish').

Odpowiedź w 2 minuty: Tworzenie własnego middleware do logowania żądań to doskonała praktyka dla zrozumienia działania middleware w Express.js. Podstawowy logger przechwytuje informacje o żądaniu (metodę HTTP, URL, timestamp) i loguje je przed przekazaniem kontroli dalej.

Bardziej zaawansowany logger może również śledzić czas odpowiedzi. Można to osiągnąć zapisując timestamp początku żądania i nasłuchując na zdarzenie 'finish' obiektu response, które jest emitowane gdy odpowiedź została wysłana. Różnica czasu daje nam czas przetwarzania żądania.

Dodatkowe funkcjonalności mogą obejmować: logowanie kodu statusu odpowiedzi, rozmiaru odpowiedzi, IP klienta, user-agent, ciała żądania (z filtrowaniem wrażliwych danych jak hasła), kolorowanie outputu dla lepszej czytelności, oraz zapisywanie logów do plików zamiast konsoli.

Przy implementacji logowania należy pamiętać o bezpieczeństwie - nie logować wrażliwych danych jak hasła, tokeny czy numery kart kredytowych. Również warto rozważyć różne poziomy logowania (debug, info, warn, error) i używać sprawdzonych bibliotek jak winston lub morgan w produkcji.

Przykład kodu:

const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();

// === PRZYKŁAD 1: Prosty logger żądań ===
function simpleLogger(req, res, next) {
  const timestamp = new Date().toISOString();
  const method = req.method;
  const url = req.url;

  console.log(`[${timestamp}] ${method} ${url}`);

  next(); // Przekaż kontrolę do następnego middleware
}

app.use(simpleLogger);

// === PRZYKŁAD 2: Logger z czasem odpowiedzi ===
function advancedLogger(req, res, next) {
  const startTime = Date.now();
  const timestamp = new Date().toISOString();

  // Nasłuchuj na zakończenie odpowiedzi
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    const statusCode = res.statusCode;

    console.log(
      `[${timestamp}] ${req.method} ${req.url} ` +
      `${statusCode} - ${duration}ms`
    );
  });

  next();
}

app.use(advancedLogger);

// === PRZYKŁAD 3: Logger z kolorowaniem i szczegółami ===
function colorLogger(req, res, next) {
  const startTime = Date.now();

  // Kolory ANSI
  const colors = {
    reset: '\x1b[0m',
    green: '\x1b[32m',
    yellow: '\x1b[33m',
    red: '\x1b[31m',
    blue: '\x1b[34m',
    cyan: '\x1b[36m'
  };

  res.on('finish', () => {
    const duration = Date.now() - startTime;
    const statusCode = res.statusCode;

    // Wybierz kolor na podstawie statusu
    let statusColor;
    if (statusCode >= 500) statusColor = colors.red;
    else if (statusCode >= 400) statusColor = colors.yellow;
    else if (statusCode >= 300) statusColor = colors.cyan;
    else if (statusCode >= 200) statusColor = colors.green;

    console.log(
      `${colors.blue}[${new Date().toISOString()}]${colors.reset} ` +
      `${req.method.padEnd(6)} ${req.url.padEnd(30)} ` +
      `${statusColor}${statusCode}${colors.reset} ` +
      `${duration}ms ` +
      `${colors.cyan}${req.ip}${colors.reset}`
    );
  });

  next();
}

app.use(colorLogger);

// === PRZYKŁAD 4: Logger zapisujący do pliku ===
function fileLogger(logFilePath) {
  // Upewnij się, że katalog istnieje
  const logDir = path.dirname(logFilePath);
  if (!fs.existsSync(logDir)) {
    fs.mkdirSync(logDir, { recursive: true });
  }

  return function(req, res, next) {
    const startTime = Date.now();
    const timestamp = new Date().toISOString();

    res.on('finish', () => {
      const duration = Date.now() - startTime;
      const logEntry =
        `[${timestamp}] ` +
        `${req.method} ${req.url} ` +
        `${res.statusCode} ${duration}ms ` +
        `IP: ${req.ip} ` +
        `User-Agent: ${req.get('user-agent')}\n`;

      // Dopisz do pliku (async, nie blokuje)
      fs.appendFile(logFilePath, logEntry, (err) => {
        if (err) console.error('Błąd zapisu logu:', err);
      });
    });

    next();
  };
}

app.use(fileLogger('./logs/access.log'));

// === PRZYKŁAD 5: Logger z logowaniem body (z filtrowaniem) ===
function bodyLogger(req, res, next) {
  const startTime = Date.now();

  // Przechwytywanie body (wymaga express.json())
  const body = { ...req.body };

  // Filtruj wrażliwe dane
  const sensitiveFields = ['password', 'token', 'apiKey', 'secret'];
  sensitiveFields.forEach(field => {
    if (body[field]) {
      body[field] = '***FILTERED***';
    }
  });

  res.on('finish', () => {
    const duration = Date.now() - startTime;

    console.log({
      timestamp: new Date().toISOString(),
      method: req.method,
      url: req.url,
      status: res.statusCode,
      duration: `${duration}ms`,
      ip: req.ip,
      userAgent: req.get('user-agent'),
      body: Object.keys(body).length > 0 ? body : undefined
    });
  });

  next();
}

app.use(express.json());
app.use(bodyLogger);

// === PRZYKŁAD 6: Konfigurowalny logger z poziomami ===
function createLogger(options = {}) {
  const {
    level = 'info', // 'debug', 'info', 'warn', 'error'
    logBody = false,
    colorize = true,
    format = 'simple' // 'simple', 'detailed', 'json'
  } = options;

  return function(req, res, next) {
    const startTime = Date.now();

    res.on('finish', () => {
      const duration = Date.now() - startTime;
      const statusCode = res.statusCode;

      // Pomiń na podstawie poziomu
      if (level === 'error' && statusCode < 400) return;
      if (level === 'warn' && statusCode < 300) return;

      // Format simple
      if (format === 'simple') {
        console.log(
          `${req.method} ${req.url} ${statusCode} ${duration}ms`
        );
      }

      // Format detailed
      else if (format === 'detailed') {
        console.log(
          `[${new Date().toISOString()}] ` +
          `${req.method.padEnd(6)} ${req.url.padEnd(30)} ` +
          `Status: ${statusCode} Duration: ${duration}ms ` +
          `IP: ${req.ip}`
        );
      }

      // Format JSON
      else if (format === 'json') {
        console.log(JSON.stringify({
          timestamp: new Date().toISOString(),
          method: req.method,
          url: req.url,
          status: statusCode,
          duration,
          ip: req.ip,
          userAgent: req.get('user-agent'),
          body: logBody ? req.body : undefined
        }));
      }
    });

    next();
  };
}

// Użycie z konfiguracją
app.use(createLogger({
  level: 'info',
  format: 'detailed',
  colorize: true
}));

// === PRZYKŁAD 7: Integracja z istniejącą biblioteką (Morgan) ===
// const morgan = require('morgan');
// app.use(morgan('combined')); // Format Apache combined
// app.use(morgan('dev')); // Format dev (kolorowy)
// app.use(morgan('tiny')); // Minimalny format

// === TRASY TESTOWE ===
app.get('/api/test', (req, res) => {
  res.json({ message: 'Test endpoint' });
});

app.post('/api/users', (req, res) => {
  res.status(201).json({
    message: 'Użytkownik utworzony',
    user: req.body
  });
});

app.get('/error', (req, res) => {
  res.status(500).json({ error: 'Symulowany błąd' });
});

app.listen(3000, () => {
  console.log('Serwer nasłuchuje na porcie 3000');
});

Przykład outputu logów:

// Simple Logger:
[2025-12-28T10:30:45.123Z] GET /api/test

// Advanced Logger:
[2025-12-28T10:30:45.123Z] GET /api/test 200 - 15ms

// Color Logger (z kolorami w terminalu):
[2025-12-28T10:30:45.123Z] GET    /api/test                      200 15ms 127.0.0.1

// JSON Logger:
{"timestamp":"2025-12-28T10:30:45.123Z","method":"GET","url":"/api/test","status":200,"duration":15,"ip":"127.0.0.1","userAgent":"Mozilla/5.0..."}

Materiały

18. Czym jest middleware third-party i jakie są najpopularniejsze pakiety?

Odpowiedź w 30 sekund: Middleware third-party to gotowe pakiety npm stworzone przez społeczność, które dodają funkcjonalność do aplikacji Express. Najpopularniejsze to: morgan (logowanie), helmet (bezpieczeństwo), cors (Cross-Origin Resource Sharing), compression (kompresja gzip), cookie-parser (parsowanie ciasteczek) i multer (upload plików).

Odpowiedź w 2 minuty: Middleware third-party to zewnętrzne biblioteki dostępne poprzez npm, które rozszerzają funkcjonalność Express.js bez konieczności pisania kodu od podstaw. Społeczność Node.js stworzyła setki middleware rozwiązujących typowe problemy webowe.

Morgan to najpopularniejszy logger HTTP, oferujący różne formaty logowania (combined, common, dev, short, tiny) i możliwość zapisu do plików. Helmet to zestaw 15 mniejszych middleware zabezpieczających aplikację poprzez ustawianie odpowiednich nagłówków HTTP (Content Security Policy, X-Frame-Options, etc.).

CORS rozwiązuje problemy Cross-Origin Resource Sharing, pozwalając kontrolować, które domeny mogą komunikować się z API. Compression kompresuje odpowiedzi HTTP używając gzip/deflate, znacząco redukując rozmiar transferu danych.

Cookie-parser parsuje nagłówek Cookie i udostępnia dane w req.cookies. Multer obsługuje multipart/form-data, niezbędny do uploadu plików. Express-validator zapewnia walidację i sanityzację danych wejściowych. Passport to kompleksowe rozwiązanie do uwierzytelniania wspierające 500+ strategii (OAuth, JWT, lokalne).

Inne popularne pakiety to: express-session (zarządzanie sesjami), serve-favicon (obsługa favicon), method-override (nadpisywanie metod HTTP), connect-timeout (timeout żądań). Wybór odpowiednich middleware zależy od wymagań projektu - API może potrzebować CORS i helmet, podczas gdy aplikacja z formularzami potrzebuje multer i express-validator.

Przykład kodu:

const express = require('express');
const app = express();

// === 1. MORGAN - Logowanie HTTP żądań ===
const morgan = require('morgan');

// Format 'dev' - kolorowy output dla development
app.use(morgan('dev'));
// Przykład: GET /api/users 200 15.123 ms - 1234

// Format 'combined' - Apache combined log format (produkcja)
// app.use(morgan('combined'));

// Zapis do pliku
const fs = require('fs');
const path = require('path');
const accessLogStream = fs.createWriteStream(
  path.join(__dirname, 'access.log'),
  { flags: 'a' } // append
);
app.use(morgan('combined', { stream: accessLogStream }));

// === 2. HELMET - Bezpieczeństwo nagłówków HTTP ===
const helmet = require('helmet');

// Podstawowe użycie - włącza wszystkie zabezpieczenia
app.use(helmet());

// Lub selektywnie:
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    scriptSrc: ["'self'", "trusted-cdn.com"]
  }
}));
app.use(helmet.hidePoweredBy()); // Ukrywa X-Powered-By: Express
app.use(helmet.frameguard({ action: 'deny' })); // Zapobiega clickjacking
app.use(helmet.noSniff()); // Zapobiega MIME sniffing
app.use(helmet.xssFilter()); // Podstawowa ochrona XSS

// === 3. CORS - Cross-Origin Resource Sharing ===
const cors = require('cors');

// Zezwól na wszystkie originy (development)
app.use(cors());

// Konfiguracja produkcyjna
app.use(cors({
  origin: ['https://example.com', 'https://app.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true, // Zezwól na cookies
  maxAge: 86400 // Cache preflight przez 24h
}));

// Dynamiczny origin
app.use(cors({
  origin: function(origin, callback) {
    const allowedOrigins = ['https://example.com', 'http://localhost:3000'];
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Niedozwolony origin'));
    }
  }
}));

// === 4. COMPRESSION - Kompresja gzip/deflate ===
const compression = require('compression');

app.use(compression({
  level: 6, // Poziom kompresji 0-9 (6 = domyślny)
  threshold: 1024, // Kompresuj tylko jeśli > 1KB
  filter: (req, res) => {
    // Nie kompresuj jeśli klient wyłączył
    if (req.headers['x-no-compression']) {
      return false;
    }
    return compression.filter(req, res);
  }
}));

// === 5. COOKIE-PARSER - Parsowanie cookies ===
const cookieParser = require('cookie-parser');

// Bez secret (cookies niepodpisane)
app.use(cookieParser());

// Z secret (cookies podpisane dla bezpieczeństwa)
app.use(cookieParser('tajny-klucz-szyfrujacy'));

app.get('/set-cookie', (req, res) => {
  // Ustaw cookie
  res.cookie('username', 'Jan Kowalski', {
    maxAge: 900000, // 15 minut
    httpOnly: true, // Niedostępne dla JavaScript
    secure: true, // Tylko HTTPS
    signed: true // Podpisane
  });
  res.send('Cookie ustawione');
});

app.get('/read-cookie', (req, res) => {
  // Odczytaj niepodpisane cookies
  console.log('Cookies:', req.cookies);
  // Odczytaj podpisane cookies
  console.log('Signed Cookies:', req.signedCookies);
  res.json({ cookies: req.cookies });
});

// === 6. MULTER - Upload plików ===
const multer = require('multer');

// Konfiguracja storage
const storage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, 'uploads/'); // Katalog docelowy
  },
  filename: function(req, file, cb) {
    // Unikalna nazwa pliku
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
  }
});

const upload = multer({
  storage: storage,
  limits: {
    fileSize: 5 * 1024 * 1024 // Max 5MB
  },
  fileFilter: function(req, file, cb) {
    // Akceptuj tylko obrazy
    if (!file.mimetype.startsWith('image/')) {
      return cb(new Error('Tylko pliki graficzne są dozwolone'));
    }
    cb(null, true);
  }
});

// Single file upload
app.post('/upload', upload.single('avatar'), (req, res) => {
  console.log('Plik:', req.file);
  console.log('Body:', req.body);
  res.json({
    message: 'Plik przesłany',
    file: req.file
  });
});

// Multiple files upload
app.post('/upload-multiple', upload.array('photos', 10), (req, res) => {
  console.log('Pliki:', req.files);
  res.json({
    message: `${req.files.length} plików przesłanych`,
    files: req.files
  });
});

// === 7. EXPRESS-VALIDATOR - Walidacja i sanityzacja ===
const { body, validationResult } = require('express-validator');

app.post('/register',
  // Reguły walidacji
  body('email')
    .isEmail()
    .normalizeEmail()
    .withMessage('Nieprawidłowy email'),
  body('password')
    .isLength({ min: 8 })
    .withMessage('Hasło musi mieć min 8 znaków')
    .matches(/\d/)
    .withMessage('Hasło musi zawierać cyfrę'),
  body('username')
    .trim()
    .isLength({ min: 3, max: 20 })
    .isAlphanumeric()
    .withMessage('Username 3-20 znaków alfanumerycznych'),

  // Handler
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    res.json({ message: 'Rejestracja pomyślna' });
  }
);

// === 8. EXPRESS-SESSION - Zarządzanie sesjami ===
const session = require('express-session');

app.use(session({
  secret: 'tajny-klucz-sesji',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true, // Tylko HTTPS
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24 // 24 godziny
  }
}));

app.get('/login', (req, res) => {
  req.session.userId = 123;
  req.session.username = 'Jan';
  res.send('Zalogowano');
});

app.get('/profile', (req, res) => {
  if (req.session.userId) {
    res.json({ user: req.session.username });
  } else {
    res.status(401).send('Niezalogowany');
  }
});

// === 9. PASSPORT - Uwierzytelnianie ===
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

// Konfiguracja strategii lokalnej
passport.use(new LocalStrategy(
  function(username, password, done) {
    // Weryfikacja użytkownika (przykład)
    if (username === 'admin' && password === 'password') {
      return done(null, { id: 1, username: 'admin' });
    } else {
      return done(null, false, { message: 'Nieprawidłowe dane' });
    }
  }
));

passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser((id, done) => {
  done(null, { id: 1, username: 'admin' });
});

app.use(passport.initialize());
app.use(passport.session());

app.post('/login',
  passport.authenticate('local', {
    successRedirect: '/dashboard',
    failureRedirect: '/login'
  })
);

// === 10. RATE-LIMITER - Ograniczanie liczby żądań ===
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minut
  max: 100, // Max 100 żądań na IP
  message: 'Zbyt wiele żądań z tego IP, spróbuj później',
  standardHeaders: true, // Zwróć info o limicie w `RateLimit-*` headers
  legacyHeaders: false
});

// Zastosuj do wszystkich żądań
app.use(limiter);

// Lub tylko do konkretnych tras
const loginLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 godzina
  max: 5, // Max 5 prób logowania
  message: 'Zbyt wiele prób logowania'
});

app.post('/login', loginLimiter, (req, res) => {
  res.send('Login endpoint');
});

// === PRZYKŁADOWA APLIKACJA Z WIELOMA MIDDLEWARE ===
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get('/api/users', (req, res) => {
  res.json({ users: [] });
});

app.listen(3000, () => {
  console.log('Serwer nasłuchuje na porcie 3000');
});

Popularne pakiety middleware:

/*
=== BEZPIECZEŃSTWO ===
- helmet: Zabezpieczenie nagłówków HTTP
- cors: Cross-Origin Resource Sharing
- express-rate-limit: Limitowanie liczby żądań
- csurf: CSRF protection
- hpp: HTTP Parameter Pollution protection

=== PARSOWANIE ===
- body-parser: Parsowanie body (wbudowany w Express 4.16+)
- cookie-parser: Parsowanie cookies
- multer: Upload plików (multipart/form-data)
- express-xml-bodyparser: Parsowanie XML

=== WALIDACJA ===
- express-validator: Walidacja i sanityzacja
- joi: Schema validation

=== LOGOWANIE ===
- morgan: HTTP request logger
- winston: Uniwersalny logger
- pino: Szybki JSON logger

=== UWIERZYTELNIANIE ===
- passport: Autentykacja (500+ strategii)
- jsonwebtoken: JWT tokens
- express-session: Zarządzanie sesjami
- bcrypt: Hashowanie haseł

=== PERFORMANCE ===
- compression: Kompresja gzip/deflate
- response-time: Mierzenie czasu odpowiedzi
- connect-timeout: Timeout żądań

=== INNE ===
- serve-static: Serwowanie plików statycznych
- method-override: Override HTTP methods
- errorhandler: Error handling dla development
- serve-favicon: Obsługa favicon
*/

Materiały

19. Jak ograniczyć middleware do konkretnych ścieżek lub metod HTTP?

Odpowiedź w 30 sekund: Middleware można ograniczyć używając app.use('/path', middleware) dla konkretnych ścieżek lub metod HTTP jak app.get(), app.post(). Można też używać wyrażeń regularnych dla ścieżek, stosować middleware bezpośrednio w route handlers, lub tworzyć własne funkcje sprawdzające warunki przed wykonaniem middleware.

Odpowiedź w 2 minuty: Express.js oferuje wiele sposobów ograniczania middleware do konkretnych ścieżek i metod HTTP, co pozwala na precyzyjną kontrolę nad przepływem żądań.

Najprostszym sposobem jest przekazanie ścieżki jako pierwszego argumentu do app.use('/path', middleware). Middleware będzie wtedy wykonywane tylko dla żądań rozpoczynających się od tej ścieżki. Dla konkretnych metod HTTP używamy dedykowanych metod: app.get(), app.post(), app.put(), app.delete(), które automatycznie filtrują żądania po metodzie.

Można używać wyrażeń regularnych do dopasowania wzorców ścieżek, np. app.use(/^/api/v[0-9]+/, middleware) dla wszystkich wersji API. Express wspiera również parametry ścieżki i wildcards, jak app.use('/users/:id', middleware).

Bardziej zaawansowane techniki obejmują: przekazywanie wielu middleware do jednego route (wykonywane sekwencyjnie), tworzenie warunkowego middleware sprawdzającego req.method lub req.path wewnętrznie, używanie app.all() dla wszystkich metod HTTP na danej ścieżce, lub stosowanie Router do grupowania tras z wspólnym middleware.

Można również stosować middleware inline bezpośrednio w definicji trasy, co jest przydatne dla middleware specyficznego dla jednej trasy (np. uwierzytelnianie administratora tylko na trasach adminowych). Kombinacja tych technik pozwala na budowanie elastycznych i modularnych aplikacji.

Przykład kodu:

const express = require('express');
const app = express();

// === PRZYKŁAD 1: Middleware dla konkretnej ścieżki ===
// Middleware wykonywane tylko dla żądań do /api/*
app.use('/api', (req, res, next) => {
  console.log('Middleware API - tylko /api/*');
  req.apiVersion = '1.0';
  next();
});

app.get('/api/users', (req, res) => {
  res.json({ version: req.apiVersion, users: [] });
});

app.get('/home', (req, res) => {
  // req.apiVersion jest undefined - middleware nie wykonane
  res.send('Home page');
});

// === PRZYKŁAD 2: Middleware dla konkretnych metod HTTP ===
// Tylko GET
app.get('/users',
  (req, res, next) => {
    console.log('Middleware tylko dla GET /users');
    next();
  },
  (req, res) => {
    res.json({ users: [] });
  }
);

// Tylko POST
app.post('/users',
  (req, res, next) => {
    console.log('Middleware tylko dla POST /users');
    next();
  },
  (req, res) => {
    res.json({ message: 'Użytkownik utworzony' });
  }
);

// Dla wszystkich metod HTTP na danej ścieżce
app.all('/admin/*', (req, res, next) => {
  console.log('Każda metoda HTTP na /admin/*');
  // Sprawdź uprawnienia admina
  next();
});

// === PRZYKŁAD 3: Wiele ścieżek dla jednego middleware ===
const authMiddleware = (req, res, next) => {
  console.log('Sprawdzenie uwierzytelnienia');
  const token = req.headers.authorization;
  if (!token) {
    return res.status(401).json({ error: 'Brak autoryzacji' });
  }
  next();
};

// Zastosuj do wielu ścieżek
app.use(['/dashboard', '/profile', '/settings'], authMiddleware);

// === PRZYKŁAD 4: Wyrażenia regularne dla ścieżek ===
// Dopasuj /api/v1, /api/v2, /api/v3, etc.
app.use(/^\/api\/v[0-9]+/, (req, res, next) => {
  console.log('Middleware dla wszystkich wersji API');
  next();
});

// Dopasuj pliki kończące się na .json
app.get(/\.json$/, (req, res, next) => {
  console.log('Żądanie pliku JSON');
  next();
});

// === PRZYKŁAD 5: Parametry ścieżki ===
// Middleware dla /users/:id, /users/123, etc.
app.use('/users/:id', (req, res, next) => {
  console.log(`Middleware dla użytkownika ID: ${req.params.id}`);
  // Walidacja ID
  if (isNaN(req.params.id)) {
    return res.status(400).json({ error: 'ID musi być liczbą' });
  }
  next();
});

app.get('/users/:id', (req, res) => {
  res.json({ userId: req.params.id });
});

// === PRZYKŁAD 6: Warunkowe middleware ===
function conditionalMiddleware(req, res, next) {
  // Wykonaj tylko dla POST i PUT
  if (req.method === 'POST' || req.method === 'PUT') {
    console.log('Middleware dla POST/PUT');
    // Walidacja body
    if (!req.body || Object.keys(req.body).length === 0) {
      return res.status(400).json({ error: 'Body jest wymagane' });
    }
  }
  next();
}

app.use('/api/users', conditionalMiddleware);

// === PRZYKŁAD 7: Wiele middleware w sekwencji dla jednej trasy ===
const validateInput = (req, res, next) => {
  console.log('1. Walidacja inputu');
  if (!req.body.email) {
    return res.status(400).json({ error: 'Email wymagany' });
  }
  next();
};

const checkDuplicate = (req, res, next) => {
  console.log('2. Sprawdzenie duplikatów');
  // Symulacja sprawdzenia w bazie
  next();
};

const hashPassword = (req, res, next) => {
  console.log('3. Hashowanie hasła');
  // Symulacja hashowania
  req.body.password = 'hashed_' + req.body.password;
  next();
};

// Wszystkie middleware wykonane sekwencyjnie
app.post('/register', [validateInput, checkDuplicate, hashPassword], (req, res) => {
  res.json({ message: 'Użytkownik zarejestrowany' });
});

// === PRZYKŁAD 8: Router z ograniczonym middleware ===
const apiRouter = express.Router();
const adminRouter = express.Router();

// Middleware tylko dla apiRouter
apiRouter.use((req, res, next) => {
  console.log('Middleware API Router');
  next();
});

// Middleware tylko dla adminRouter
adminRouter.use((req, res, next) => {
  console.log('Middleware Admin Router');
  // Sprawdź uprawnienia
  if (req.headers.role !== 'admin') {
    return res.status(403).json({ error: 'Brak dostępu' });
  }
  next();
});

apiRouter.get('/users', (req, res) => {
  res.json({ users: [] });
});

adminRouter.get('/dashboard', (req, res) => {
  res.json({ message: 'Panel admina' });
});

app.use('/api', apiRouter);
app.use('/admin', adminRouter);

// === PRZYKŁAD 9: Middleware tylko dla konkretnych ścieżek z wykluczeniami ===
function apiMiddleware(req, res, next) {
  // Pomiń dla /api/public
  if (req.path.startsWith('/public')) {
    return next();
  }

  console.log('Middleware API (z wykluczeniem /public)');
  // Token validation
  const token = req.headers['x-api-token'];
  if (!token) {
    return res.status(401).json({ error: 'Brak tokena API' });
  }
  next();
}

app.use('/api', apiMiddleware);

app.get('/api/public/info', (req, res) => {
  res.json({ message: 'Publiczny endpoint - bez tokena' });
});

app.get('/api/private/data', (req, res) => {
  res.json({ message: 'Prywatny endpoint - wymaga tokena' });
});

// === PRZYKŁAD 10: Factory function dla reużywalnego middleware ===
function createRoleMiddleware(allowedRoles) {
  return function(req, res, next) {
    const userRole = req.headers.role;

    if (!allowedRoles.includes(userRole)) {
      return res.status(403).json({
        error: `Dostęp tylko dla: ${allowedRoles.join(', ')}`
      });
    }

    next();
  };
}

// Użycie
app.get('/admin/users',
  createRoleMiddleware(['admin']),
  (req, res) => {
    res.json({ users: [] });
  }
);

app.get('/moderator/reports',
  createRoleMiddleware(['admin', 'moderator']),
  (req, res) => {
    res.json({ reports: [] });
  }
);

// === PRZYKŁAD 11: Middleware dla różnych metod na tej samej ścieżce ===
app.route('/articles')
  .get((req, res) => {
    res.json({ articles: [] });
  })
  .post(
    express.json(), // Middleware tylko dla POST
    (req, res) => {
      res.status(201).json({ message: 'Artykuł utworzony' });
    }
  )
  .put(
    express.json(),
    (req, res) => {
      res.json({ message: 'Artykuł zaktualizowany' });
    }
  );

// === PRZYKŁAD 12: Middleware z wildcard ===
// Dopasuj /files/*, /files/documents, /files/images, etc.
app.use('/files/*', (req, res, next) => {
  console.log('Middleware dla wszystkich plików');
  next();
});

// Middleware dla wszystkich tras (musi być na końcu)
app.use('*', (req, res) => {
  res.status(404).json({ error: 'Nie znaleziono' });
});

app.use(express.json());

app.listen(3000, () => {
  console.log('Serwer nasłuchuje na porcie 3000');
});

Tabela porównawcza metod:

/*
=== METODY OGRANICZANIA MIDDLEWARE ===

1. app.use('/path', middleware)
   Zakres: Wszystkie żądania do /path/* (wszystkie metody HTTP)
   Przykład: app.use('/api', middleware)

2. app.get('/path', middleware, handler)
   Zakres: Tylko GET /path
   Przykład: app.get('/users', auth, handler)

3. app.post('/path', middleware, handler)
   Zakres: Tylko POST /path
   Przykład: app.post('/users', validate, handler)

4. app.all('/path', middleware)
   Zakres: Wszystkie metody HTTP na /path
   Przykład: app.all('/admin/*', authAdmin)

5. app.use(['/path1', '/path2'], middleware)
   Zakres: Wiele konkretnych ścieżek
   Przykład: app.use(['/api', '/v1'], middleware)

6. app.use(/regex/, middleware)
   Zakres: Ścieżki pasujące do regex
   Przykład: app.use(/^\/api\/v[0-9]+/, middleware)

7. router.use(middleware)
   Zakres: Wszystkie trasy w routerze
   Przykład: apiRouter.use(middleware)

8. Warunkowe middleware
   Zakres: Custom logic wewnątrz middleware
   Przykład: if (req.method === 'POST') { ... }
*/

Materiały

20. Jaka jest różnica między app.use() a app.all()?

Odpowiedź w 30 sekund: app.use() montuje middleware dla wszystkich metod HTTP i działa dla ścieżek rozpoczynających się od podanego wzorca (np. /api dopasuje /api/users). app.all() to route handler dla wszystkich metod HTTP, który wymaga dokładnego dopasowania ścieżki i jest używany do obsługi konkretnych tras, nie do montowania middleware.

Odpowiedź w 2 minuty: Główna różnica między app.use() a app.all() leży w ich przeznaczeniu i sposobie dopasowywania ścieżek.

app.use() jest zaprojektowany do montowania middleware. Dopasowuje ścieżki w sposób prefiksowy - jeśli określisz app.use('/api', middleware), będzie działać dla /api, /api/users, /api/users/123, itd. Jest używany do dodawania funkcjonalności globalnej lub dla grupy tras. Może przyjmować zarówno funkcje middleware, jak i obiekty Router. Nie wymaga dokładnego dopasowania ścieżki i działa bardziej jak "strażnik" dla wszystkich tras zaczynających się od danego prefiksu.

app.all() to metoda routingu podobna do app.get(), app.post(), etc., ale działająca dla wszystkich metod HTTP (GET, POST, PUT, DELETE, etc.). Wymaga dokładnego dopasowania ścieżki - app.all('/api', handler) zadziała tylko dla dokładnie /api, nie dla /api/users. Jest używany do obsługi konkretnych tras, które powinny odpowiadać na wszystkie metody HTTP. Często wykorzystywany do implementacji uniwersalnych handlerów lub middleware specyficznych dla jednej ścieżki.

Kolejna kluczowa różnica: app.use() z podaną ścieżką nie uwzględnia tej ścieżki w req.path wewnątrz middleware (ścieżka jest "obcięta"), podczas gdy app.all() zachowuje pełną ścieżkę. To wpływa na sposób, w jaki można używać parametrów ścieżki i dalszego routingu.

W praktyce: używaj app.use() do montowania middleware i routerów dla grup tras, a app.all() gdy potrzebujesz route handler dla wszystkich metod HTTP na konkretnej ścieżce.

Przykład kodu:

const express = require('express');
const app = express();

// === PRZYKŁAD 1: Dopasowanie ścieżek - app.use() vs app.all() ===

// app.use() - dopasowanie PREFIKSOWE
app.use('/api', (req, res, next) => {
  console.log('app.use /api - path:', req.path);
  console.log('app.use /api - baseUrl:', req.baseUrl);
  next();
});

// app.all() - dopasowanie DOKŁADNE
app.all('/api', (req, res, next) => {
  console.log('app.all /api - path:', req.path);
  next();
});

// Test:
// GET /api       -> app.use ✓, app.all ✓
// GET /api/users -> app.use ✓, app.all ✗

// === PRZYKŁAD 2: Zachowanie req.path ===
app.use('/users', (req, res, next) => {
  console.log('app.use /users:');
  console.log('  req.path:', req.path);        // '/123' (obcięte)
  console.log('  req.baseUrl:', req.baseUrl);  // '/users'
  console.log('  req.originalUrl:', req.originalUrl); // '/users/123'
  next();
});

app.all('/users/*', (req, res, next) => {
  console.log('app.all /users/*:');
  console.log('  req.path:', req.path);        // '/users/123' (pełna)
  next();
});

// GET /users/123
// app.use: path='/123', baseUrl='/users'
// app.all: path='/users/123'

// === PRZYKŁAD 3: Montowanie routerów - tylko app.use() ===
const apiRouter = express.Router();

apiRouter.get('/users', (req, res) => {
  res.json({ users: [] });
});

// ✓ POPRAWNE - app.use() może montować Router
app.use('/api', apiRouter);

// ✗ BŁĄD - app.all() NIE może montować Router
// app.all('/api', apiRouter); // TypeError!

// === PRZYKŁAD 4: Obsługa wszystkich metod HTTP ===

// app.all() - handler dla wszystkich metod na dokładnej ścieżce
app.all('/status', (req, res) => {
  res.json({
    method: req.method,
    message: `Otrzymano ${req.method} na /status`
  });
});

// Test:
// GET /status    -> 200 OK
// POST /status   -> 200 OK
// DELETE /status -> 200 OK

// app.use() - middleware dla wszystkich metod i pod-ścieżek
app.use('/health', (req, res, next) => {
  console.log(`Health check - ${req.method} ${req.path}`);
  next();
});

// === PRZYKŁAD 5: Różnica w użyciu praktycznym ===

// Użycie app.use() - middleware dla grupy tras
app.use('/admin', (req, res, next) => {
  // Middleware wykonane dla WSZYSTKICH tras /admin/*
  console.log('Sprawdzenie uprawnień admina');

  const isAdmin = req.headers.role === 'admin';
  if (!isAdmin) {
    return res.status(403).json({ error: 'Brak uprawnień' });
  }
  next();
});

app.get('/admin/users', (req, res) => {
  res.json({ users: [] });
});

app.get('/admin/settings', (req, res) => {
  res.json({ settings: {} });
});

// Użycie app.all() - handler dla konkretnej trasy
app.all('/webhook', (req, res) => {
  // Handler dla WSZYSTKICH metod HTTP na dokładnie /webhook
  console.log(`Webhook otrzymał: ${req.method}`);

  res.json({
    received: true,
    method: req.method,
    timestamp: Date.now()
  });
});

// === PRZYKŁAD 6: Łańcuch app.all() dla różnych metod ===
app.all('/articles/:id', (req, res, next) => {
  // Wspólna walidacja dla wszystkich metod
  console.log('Walidacja article ID:', req.params.id);

  if (isNaN(req.params.id)) {
    return res.status(400).json({ error: 'ID musi być liczbą' });
  }

  next(); // Przekaż do konkretnego handlera metody
});

app.get('/articles/:id', (req, res) => {
  res.json({ article: { id: req.params.id } });
});

app.put('/articles/:id', (req, res) => {
  res.json({ message: 'Artykuł zaktualizowany' });
});

app.delete('/articles/:id', (req, res) => {
  res.json({ message: 'Artykuł usunięty' });
});

// === PRZYKŁAD 7: app.use() bez ścieżki vs app.all() ze wszystkimi ścieżkami ===

// app.use() bez ścieżki - WSZYSTKIE żądania
app.use((req, res, next) => {
  console.log('Global middleware - każde żądanie');
  next();
});

// app.all('*') - wszystkie ścieżki (zwykle na końcu jako 404)
app.all('*', (req, res) => {
  res.status(404).json({
    error: 'Nie znaleziono',
    path: req.path
  });
});

// === PRZYKŁAD 8: Zagnieżdżenie - app.use() obsługuje pod-ścieżki ===
app.use('/api/v1', (req, res, next) => {
  console.log('API v1 middleware');
  console.log('Path w middleware:', req.path); // '/users' dla /api/v1/users
  next();
});

app.get('/api/v1/users', (req, res) => {
  res.json({ version: 'v1', users: [] });
});

// app.all() wymaga dokładnej ścieżki z wildcards
app.all('/api/v2/*', (req, res) => {
  res.json({
    version: 'v2',
    path: req.path,
    message: 'API v2 endpoint'
  });
});

// === PRZYKŁAD 9: Kolejność wykonania ===
app.use('/test', (req, res, next) => {
  console.log('1. app.use /test');
  next();
});

app.all('/test', (req, res, next) => {
  console.log('2. app.all /test (dokładne)');
  next();
});

app.get('/test', (req, res) => {
  console.log('3. app.get /test');
  res.send('Done');
});

// GET /test wykonuje: 1 -> 2 -> 3

app.use('/test/sub', (req, res, next) => {
  console.log('4. app.use /test/sub');
  next();
});

// GET /test/sub wykonuje: 1 -> 4 (app.all NIE zostanie wykonany)

// === PRZYKŁAD 10: Typowe przypadki użycia ===

// app.use() - Montowanie middleware i routerów
app.use(express.json());                    // Body parser
app.use(express.static('public'));          // Pliki statyczne
app.use('/api', apiRouter);                 // Montowanie routera
app.use((req, res, next) => {               // Global middleware
  req.requestTime = Date.now();
  next();
});

// app.all() - Uniwersalne handlery
app.all('/cors-test', (req, res) => {       // CORS preflight + actual
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');

  if (req.method === 'OPTIONS') {
    res.sendStatus(200);
  } else {
    res.json({ method: req.method });
  }
});

app.all('/redirect', (req, res) => {        // Uniwersalny redirect
  res.redirect('/new-location');
});

app.listen(3000, () => {
  console.log('Serwer nasłuchuje na porcie 3000');
});

Tabela porównawcza:

/*
=== app.use() vs app.all() ===

CECHA                  | app.use()              | app.all()
-----------------------|------------------------|------------------------
Przeznaczenie          | Montowanie middleware  | Route handler
Dopasowanie ścieżki    | Prefiksowe (/api/*)    | Dokładne (/api)
Metody HTTP            | Wszystkie              | Wszystkie
req.path               | Obcięty prefix         | Pełna ścieżka
Montowanie Router      | ✓ Tak                  | ✗ Nie
Parametry ścieżki      | Ograniczone            | ✓ Pełne wsparcie
Typowe użycie          | Middleware, routery    | Universal handlers

PRZYKŁADY DOPASOWANIA:

Definicja              | Żądanie                | app.use() | app.all()
-----------------------|------------------------|-----------|----------
app.use('/api', ...)   | GET /api               | ✓         | -
app.all('/api', ...)   | GET /api               | -         | ✓
app.use('/api', ...)   | GET /api/users         | ✓         | -
app.all('/api', ...)   | GET /api/users         | -         | ✗
app.use('/api', ...)   | POST /api/users/123    | ✓         | -
app.all('/api/*', ...) | POST /api/users/123    | -         | ✓
*/

Materiały

Obsługa żądań i odpowiedzi

21. Jakie właściwości i metody posiada obiekt żądania (req) w Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
22. Jakie metody odpowiedzi (res) są najczęściej używane i czym się różnią?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
23. Jak prawidłowo wysyłać odpowiedzi JSON w Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
24. Czym jest res.locals i jak przekazywać dane między middleware?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
25. Jak obsługiwać przesyłanie plików (file upload) w Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
26. Jak implementować przekierowania i jaka jest różnica między 301 a 302?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Szablony i renderowanie

27. Jak skonfigurować silnik szablonów (template engine) w Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
28. Czym jest res.render() i jak przekazywać dane do widoków?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
29. Jaka jest różnica między popularnymi silnikami szablonów (EJS, Pug, Handlebars)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
30. Jak organizować widoki i layouty w aplikacji Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Obsługa błędów

31. Jak działa obsługa błędów w Express i czym jest error-handling middleware?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
32. Jak tworzyć niestandardowe klasy błędów dla aplikacji Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
33. Jak obsługiwać błędy asynchroniczne w Express (async/await)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
34. Jak zwracać odpowiednie kody statusu HTTP dla różnych typów błędów?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
35. Jak implementować globalne przechwytywanie nieobsłużonych błędów?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Bezpieczeństwo

36. 36. Jakie są najważniejsze praktyki bezpieczeństwa w aplikacjach Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
37. 37. Czym jest Helmet.js i jakie nagłówki bezpieczeństwa ustawia?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
38. 38. Jak zabezpieczyć aplikację Express przed atakami CSRF?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
39. 39. Jak implementować rate limiting w Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
40. 40. Jak bezpiecznie przechowywać i walidować dane sesji oraz ciasteczka?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Wydajność i najlepsze praktyki

41. 41. Jak zoptymalizować wydajność aplikacji Express dla produkcji?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
42. 42. Jak prawidłowo strukturyzować duży projekt Express (architektura folderów)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Zaawansowane Wzorce i Architektura Express.js

43. 43. Jak zaimplementować graceful shutdown w aplikacji Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
44. 44. Jak skalować aplikację Express.js używając modułu cluster i PM2?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
45. 45. Jak implementować zaawansowane wzorce middleware z dependency injection?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
46. 46. Jak obsługiwać streaming dużych plików i backpressure w Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
47. 47. Jak implementować strategie wersjonowania API w Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
48. 48. Jak implementować circuit breaker i retry patterns w Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
49. 49. Jak zintegrować monitoring i distributed tracing w Express?

Odpowiedź dostępna w pełnej wersji. Odblokuj 36 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Chcesz poznać wszystkie odpowiedzi?

Uzyskaj pełny dostęp do 56 pytań rekrutacyjnych z Express.js oraz pozostałych technologii.

Zobacz plany cenowe