To jest darmowy podgląd

To jest próbka 15 pytań z naszej pełnej kolekcji 49 pytań rekrutacyjnych. Uzyskaj pełny dostęp do wszystkich pytań ze szczegółowymi odpowiedziami i przykładami kodu.

Kup pełny dostęp

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:

↑ Powrót na górę

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

↑ Powrót na górę

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

↑ Powrót na górę

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

Odpowiedź w 30 sekund: Konfiguracja silnika szablonów w Express wymaga zainstalowania odpowiedniego pakietu npm (np. ejs, pug, handlebars), ustawienia silnika za pomocą app.set('view engine', 'nazwa') oraz określenia folderu z widokami za pomocą app.set('views', './ścieżka'). Express automatycznie rozpozna i skonfiguruje większość popularnych silników.

Odpowiedź w 2 minuty: Konfiguracja silnika szablonów w Express składa się z kilku prostych kroków. Najpierw należy zainstalować wybrany silnik za pomocą npm (np. npm install ejs). Następnie w aplikacji Express ustawiamy dwa kluczowe parametry: view engine określający typ silnika oraz views wskazujący folder z szablonami.

Express automatycznie obsługuje najpopularniejsze silniki szablonów, więc nie trzeba ich manualnie importować. Wystarczy ustawić nazwę silnika, a Express znajdzie odpowiedni moduł w node_modules. Domyślnym folderem dla widoków jest ./views, ale można to zmienić zgodnie z strukturą projektu.

Dla silników, które nie są natywnie obsługiwane przez Express (np. Handlebars), może być konieczne dodatkowe skonfigurowanie przez ustawienie właściwości engine za pomocą app.engine(). To pozwala na użycie dowolnego silnika szablonów, nawet jeśli Express go nie rozpoznaje automatycznie.

Po konfiguracji, szablony można renderować za pomocą metody res.render(), która automatycznie użyje skonfigurowanego silnika do przetworzenia pliku widoku i wysłania wyniku do klienta.

Przykład kodu:

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

// Konfiguracja EJS (automatycznie rozpoznawany)
app.set('view engine', 'ejs');
app.set('views', './views'); // domyślny folder

// Konfiguracja Pug (automatycznie rozpoznawany)
app.set('view engine', 'pug');
app.set('views', './templates');

// Konfiguracja Handlebars (wymaga dodatkowej konfiguracji)
const exphbs = require('express-handlebars');

// Rejestracja silnika Handlebars
app.engine('handlebars', exphbs.engine({
  defaultLayout: 'main',
  layoutsDir: './views/layouts/',
  partialsDir: './views/partials/'
}));

app.set('view engine', 'handlebars');
app.set('views', './views');

// Przykładowa trasa używająca szablonu
app.get('/', (req, res) => {
  res.render('index', {
    title: 'Strona główna',
    message: 'Witaj w Express!'
  });
});

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

Materiały

↑ Powrót na górę

Bezpieczeństwo

↑ Powrót na górę

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:

↑ Powrót na górę

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:

↑ Powrót na górę

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:

↑ Powrót na górę

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:

↑ Powrót na górę

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

↑ Powrót na górę

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

↑ Powrót na górę

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

↑ Powrót na górę

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

↑ Powrót na górę

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

↑ Powrót na górę

Chcesz więcej pytań?

Uzyskaj dostęp do 800+ pytań z 13 technologii - JavaScript, React, TypeScript, Node.js, SQL i więcej. Natychmiastowy dostęp na 30 dni.

Kup pełny dostęp za 49,99 zł