1. Wprowadzenie do Node.js i środowiska pracy – Node.js jako środowisko uruchomieniowe JavaScript po stronie serwera. Architektura sterowana zdarzeniami (Event Loop). Konfiguracja środowiska programistycznego i inicjalizacja projektu.


WPROWADZENIE — JAK NARODZIŁ SIĘ NODE.JS?

Wyobraź sobie świat w 2009 roku:

  • JavaScript działa tylko w przeglądarce.
  • Backend to PHP, Java, Python — każdy request blokuje serwer.
  • Aplikacje są wolne, toporne, nie skalują się.
  • Programiści muszą znać dwa różne światy: JS na froncie, inny język na backendzie.

I wtedy pojawia się Ryan Dahl — młody programista, który patrzy na to wszystko i mówi:

„Dlaczego serwery muszą się blokować? Dlaczego JavaScript nie może działać na serwerze? Dlaczego nie możemy mieć jednego języka na frontend i backend?”

Ryan bierze silnik V8 z Google Chrome, łączy go z biblioteką libuv (asynchroniczne I/O) i tworzy:

Node.js — rewolucja w programowaniu webowym.

To nie jest „kolejny język”. To jest nowy sposób myślenia o programowaniu.

Czym jest Node.js?

Node.js to:

  • silnik V8 (kompiluje JS do kodu maszynowego)
  • Event Loop (dyrygent orkiestry zadań)
  • non‑blocking I/O (nic nie blokuje programu)
  • NPM (największy ekosystem paczek na świecie)
  • środowisko uruchomieniowe JS poza przeglądarką

Node.js potrafi obsłużyć dziesiątki tysięcy połączeń jednocześnie, podczas gdy klasyczne serwery blokujące obsługują jedno połączenie na raz.

Event Loop — serce Node.js

Najważniejszy mechanizm, który musisz zrozumieć!

Event Loop to serce Node.js. To on decyduje, kiedy i w jakiej kolejności wykonywane są zadania.

REPL to skrót od Read-Eval-Print Loop (w tłumaczeniu: pętla Czytaj-Wykonaj-Wypisz). Jest to prosty, interaktywny program komputerowy, który działa w pętli i pozwala na natychmiastowe testowanie kodu.

Działa to w czterech powtarzających się krokach:

  1. Read (Czytaj): Program pobiera kod, który wpiszesz z klawiatury.
  2. Eval (Wykonaj): Komputer przetwarza i uruchamia ten kod.
  3. Print (Wypisz): Na ekranie od razu pojawia się wynik działania kodu.
  4. Loop (Pętla): Program wraca do początku i czeka na Twoją kolejną instrukcję.

Gdzie to znajdziesz?

  • W przeglądarce: Gdy wciśniesz klawisz F12 w Chrome lub Firefox i wejdziesz w zakładkę Konsola (Console), to co tam wpisujesz, to właśnie REPL dla JavaScriptu.
  • W terminalu: Jeśli masz zainstalowany Node.js, wpisz w swoim terminalu samo słowo node. Uruchomi się konsola REPL, gdzie możesz pisać i testować czysty JavaScript bez tworzenia jakichkolwiek plików. Aby opuścić konsolę można wpisać .exit lub Ctrl+D lub dwukrotnie wcisnąć ctrl+c

To idealne narzędzie do szybkiej nauki i sprawdzania, jak działają konkretne funkcje czy operacje matematyczne.

Wyobraź sobie:

Kuchnia w restauracji

  • Kelner przyjmuje zamówienie (kod synchroniczny).
  • Przekazuje je do kuchni (zadanie asynchroniczne).
  • Wraca do obsługi innych klientów (Event Loop).
  • Gdy danie jest gotowe, kuchnia daje sygnał (callback).
  • Kelner dostarcza je do stolika (wykonanie callbacku).

Kelner nie stoi bezczynnie. Node.js nie stoi bezczynnie.

Przykład działania Event Loop

console.log("Start");

setTimeout(() => {
  console.log("Timeout");
}, 0);

Promise.resolve().then(() => {
  console.log("Promise");
});

console.log("Koniec");

Co się dzieje krok po kroku:

  1. console.log("Start");
    • kod synchroniczny, wykonuje się od razu, wypisuje „Start”.
  2. setTimeout(() => { console.log("Timeout"); }, 0);
    • rejestrujesz zadanie asynchroniczne (callback),
    • funkcja trafia do kolejki callbacków (callback queue),
    • nie wykonuje się od razu.
  3. Promise.resolve().then(() => { console.log("Promise"); });
    • tworzysz promisa, który jest od razu spełniony,
    • funkcja w then trafia do kolejki microtasks (microtask queue),
    • też nie wykonuje się od razu.
  4. console.log("Koniec");
    • kod synchroniczny, wykonuje się od razu, wypisuje „Koniec”.
  5. Event Loop kończy wykonywanie kodu synchronicznego. Teraz:
    • najpierw wykonuje microtasks → Promise,
    • potem wykonuje callback queue → Timeout.

Wynik:

Start
Koniec
Promise
Timeout

Dlaczego?

  • Kod synchroniczny → Start, Koniec
  • Microtasks → Promise
  • Callback queue → Timeout

Instalacja Node.js — krok po kroku

  1. Wejdź na https://nodejs.org
  2. Pobierz wersję LTS
  3. Zainstaluj
  4. Sprawdź wersję:
node -v
npm -v

Jeśli widzisz numery wersji — Node.js i NPM działają.

NPM — największy ekosystem paczek na świecie

NPM (Node Package Manager) to:

  • menedżer pakietów,
  • repozytorium bibliotek,
  • narzędzie do automatyzacji,
  • system skryptów.

Przykładowe komendy:

npm install express
npm install nodemon --save-dev
npm install mysql2
npm install bcrypt
npm install jsonwebtoken
  • express — framework do tworzenia API,
  • nodemon — automatyczne restartowanie serwera po zmianie pliku,
  • mysql2 — obsługa bazy MySQL,
  • bcrypt — hashowanie haseł,
  • jsonwebtoken — tokeny JWT.

Inicjalizacja projektu — npm init i plik package.json

1. Po co w ogóle jest „inicjalizacja projektu”?

Kiedy zaczynasz pracę nad projektem w Node.js, nie wystarczy mieć jeden plik app.js. Projekt potrzebuje:

  • informacji o sobie (nazwa, wersja),
  • listy bibliotek, których używa (dependencies),
  • skryptów, które można uruchamiać (npm startnpm run dev),
  • konfiguracji, którą rozumie NPM.

To wszystko trzyma w jednym miejscu plik package.json. Bez niego NPM „nie wie”, że folder jest projektem, nie wie, jakie paczki są potrzebne, nie wie, jakie skrypty ma uruchamiać.

2. Jak zainicjalizować projekt?

Krok po kroku:

  1. Tworzysz nowy folder, np. my-app.
  2. Wchodzisz do niego w terminalu:
cd my-app
  1. Uruchamiasz:
npm init -y

Co się dzieje?

  • npm init — mówi NPM: „utwórz plik package.json dla tego projektu”.
  • -y — oznacza „użyj domyślnych wartości, nie zadawaj mi pytań”.

Po tej komendzie w folderze pojawia się plik:

my-app/
  package.json

3. Co jest w package.json? Rozpiska linia po linii

Przykładowa zawartość:

{
  "name": "my-app",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Wyjaśnienie:

  • "name": "my-app" Nazwa projektu. Używana np. przy publikacji paczki w NPM (u Ciebie głównie informacyjnie).
  • "version": "1.0.0" Wersja projektu. Format major.minor.patch (np. 1.0.0, 1.1.0, 1.1.1). Przydaje się przy wersjonowaniu i publikacji.
  • "description": "" Krótki opis projektu. Możesz tu wpisać np. "Simple Node.js server".
  • "main": "app.js" Główny plik projektu (wejście). Niektóre narzędzia używają tego pola, żeby wiedzieć, który plik uruchomić jako pierwszy.
  • "scripts": { "start": "node app.js" } To jest bardzo ważne. Definiujesz tu komendy, które możesz uruchamiać przez npm run ....
    W tym przykładzie:
    • "start": "node app.js" oznacza, że gdy wpiszesz w terminalu: npm start NPM uruchomi komendę node app.js, czyli odpali Twój plik app.js w Node.js.
  • "keywords": [] Słowa kluczowe (np. "node", "server"). Przydaje się przy publikacji paczki, u Ciebie może zostać puste.
  • "author": "" Autor projektu (np. "Kama Kaczmarek").
  • "license": "ISC" Licencja projektu (np. ISC, MIT). Na razie możesz zostawić domyślną.

4. Co się dzieje, gdy instalujesz paczki?

Załóżmy, że instalujesz Express:

npm install express

Po tej komendzie:

  1. Tworzy się folder node_modules — tam lądują wszystkie zainstalowane paczki.
  2. package.json pojawia się sekcja dependencies:
"dependencies": {
  "express": "^4.19.2"
}

Co to znaczy?

  • "express" — nazwa paczki,
  • "^4.19.2" — wersja (z ^, czyli „zgodna z wersją 4.x.x”).

Dzięki temu:

  • gdy ktoś inny sklonuje Twój projekt,
  • wystarczy, że uruchomi npm install,
  • NPM przeczyta package.json,
  • zainstaluje wszystkie paczki z dependencies.

Nie trzeba kopiować node_modules — wystarczy package.json.

5. dependencies vs devDependencies

Masz dwa typy zależności:

  • "dependencies" — paczki potrzebne w czasie działania aplikacji (np. expressmysql2).
  • "devDependencies" — paczki potrzebne tylko w czasie developmentu (np. nodemoneslint).

Przykład:

npm install nodemon --save-dev

Doda:

"devDependencies": {
  "nodemon": "^3.0.0"
}

Dlaczego to ważne?

  • na produkcji nie potrzebujesz nodemon,
  • ale potrzebujesz express,
  • rozdzielenie tych paczek pozwala budować lżejsze środowisko produkcyjne.

6. Jak wygląda typowy workflow z package.json?

  1. Tworzysz projekt:
npm init -y
  1. Instalujesz paczki:
npm install express
npm install nodemon --save-dev
  1. Dodajesz skrypty:
"scripts": {
  "start": "node app.js",
  "dev": "nodemon app.js"
}
  1. Uruchamiasz:
  • w trybie produkcyjnym:
npm start
  • w trybie developerskim (auto‑restart po zmianach):
npm run dev

Pierwszy skrypt Node.js

Plik: app.js

console.log("Hello from Node.js!");

Uruchom:

node app.js

W terminalu zobaczysz:

Hello from Node.js!

Pierwszy serwer HTTP — Twój pierwszy backend

Do tej pory JavaScript działał u Ciebie tylko w przeglądarce — w React, w konsoli, w prostych skryptach. Ale prawdziwe aplikacje webowe potrzebują backendu, czyli programu, który:

  • nasłuchuje na porcie,
  • odbiera żądania od przeglądarki,
  • przetwarza dane,
  • zwraca odpowiedź (HTML, JSON, pliki, statusy),
  • komunikuje się z bazą danych,
  • obsługuje logikę biznesową.

W klasycznych technologiach backendowych (PHP, Python, Java) serwer jest tworzony przez framework. W Node.js możesz stworzyć serwer od zera, bez żadnych bibliotek — tylko za pomocą modułu http.

To pozwala Ci zrozumieć:

  • jak działa komunikacja klient → serwer,
  • czym jest żądanie (req) i odpowiedź (res),
  • jak działa routing,
  • jak zwracać różne typy danych,
  • jak działa port i nasłuchiwanie.

To jest fundament, na którym później zbudujesz Express, API REST, autoryzację, upload plików, bazy danych itd.

Jak działa serwer HTTP w Node.js?

Serwer HTTP to program, który:

  1. nasłuchuje na porcie (np. 3000),
  2. czeka na żądania od przeglądarki,
  3. odbiera żądanie (np. GET /),
  4. tworzy odpowiedź (np. HTML, JSON),
  5. wysyła odpowiedź do klienta.

W Node.js serwer działa w jednym wątku, ale dzięki Event Loop może obsługiwać tysiące połączeń jednocześnie.

const http = require("http");

const server = http.createServer((req, res) => {
  res.writeHead(200, {"Content-Type": "text/plain; charset=utf-8"});
  res.end("Witaj w Node.js!");
});

server.listen(3000);

Wyjaśnienie linia po linii:

  1. const http = require("http");
    • importujesz moduł http wbudowany w Node.js,
    • ten moduł pozwala tworzyć serwery HTTP.
  2. const server = http.createServer((req, res) => { ... });
    • tworzysz serwer,
    • req — obiekt żądania (informacje o tym, co przysłał klient),
    • res — obiekt odpowiedzi (to, co wyślesz do klienta).
  3. res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
    • ustawiasz status odpowiedzi na 200 (OK),
    • ustawiasz nagłówek Content-Type na text/plain (zwykły tekst) i dodajemy dla polskich znaków diakrytycznych charset=utf-8.
  4. res.end("Witaj w Node.js!");
    • kończysz odpowiedź,
    • wysyłasz tekst do klienta.
  5. server.listen(3000);
    • serwer nasłuchuje na porcie 3000,
    • możesz wejść w przeglądarce na http://localhost:3000.

Właśnie stworzyłeś backend, który działa jak prawdziwy serwer.

Zwracanie HTML i JSON — krok po kroku

Serwer HTTP może zwracać różne typy danych. Najczęściej:

  • HTML — gdy chcesz wyświetlić stronę,
  • JSON — gdy tworzysz API.

HTML:

res.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
res.end("<h1>Witaj!</h1>");
  • Content-Type: text/html — przeglądarka wie, że to HTML.
  • res.end("<h1>Witaj!</h1>"); — wysyłasz prosty dokument HTML.
  • Przeglądarka wyświetli nagłówek <h1>.

JSON:

res.writeHead(200, {"Content-Type": "application/json; charset=utf-8"});
res.end(JSON.stringify({status: "ok"}));
  • Content-Type: application/json — klient wie, że to JSON.
  • JSON.stringify(...) — zamienia obiekt JS na tekst JSON.
  • res.end(...) — wysyłasz JSON do klienta.

To jest fundament API — backend zwraca JSON, frontend go odbiera.

Node.js vs inne technologie

TechnologiaModel pracyZaletyWady
Node.jsEvent Loopszybkość, skalowalność, JS wszędziewymaga zrozumienia asynchroniczności
PHPblokującyprosty startwolniejszy, każdy request osobno
Pythonblokujący (domyślnie)ogromny ekosystemGIL ogranicza wątki
Javawielowątkowystabilność, enterprisecięższy start

Mini‑projekt: Twój pierwszy mini‑backend

W tym mini‑projekcie tworzysz pierwszy backend, który:

  • obsługuje trzy różne ścieżki,
  • zwraca trzy różne typy odpowiedzi,
  • pokazuje jak działa routing,
  • pokazuje jak działa protokół HTTP,
  • pokazuje jak działa serwer bez frameworków,
  • pokazuje jak działa komunikacja klient → serwer → klient.

To jest pierwszy backend, który zachowuje się jak prawdziwa aplikacja:

  • reaguje na różne adresy,
  • zwraca różne dane,
  • obsługuje błędy,
  • działa na porcie,
  • komunikuje się z przeglądarką.

To jest fundament pod:

  • Express,
  • API REST,
  • autoryzację,
  • bazy danych,
  • upload plików,
  • pełne aplikacje backendowe.

Architektura mini‑projektu — co dokładnie zbudujesz?

Twój serwer będzie obsługiwał trzy ścieżki:

ŚcieżkaTyp odpowiedziCo zwracaPo co?
/HTMLStronę głównąpokazuje zwracanie HTML
/api/productsJSONlistę produktówpokazuje API
/infotekstinformację o serwerzepokazuje zwykły tekst
innetekst + status 404błądpokazuje obsługę błędów

To jest pełny routing, a nie tylko „tekst / HTML / JSON”.

Pełny kod mini‑projektu

Plik: server.js

const http = require("http");

// --- 1. Lista produktów (dane przykładowe) ---
const products = [
  { id: 1, name: "Kawa", price: 19.99 },
  { id: 2, name: "Herbata", price: 12.50 },
  { id: 3, name: "Czekolada", price: 8.99 }
];

// --- 2. Tworzenie serwera ---
const server = http.createServer((req, res) => {

  // --- 3. Routing: strona główna ---
  if (req.url === "/") {
    res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
    res.end(`
      <h1>Witaj w moim pierwszym backendzie!</h1>
      <p>Przejdź do <a href="/api/products">/api/products</a>, aby zobaczyć dane JSON.</p>
      <p>Przejdź do <a href="/info">/info</a>, aby zobaczyć informacje o serwerze.</p>
    `);
    return;
  }

  // --- 4. Routing: API zwracające JSON ---
  if (req.url === "/api/products") {
    res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
    res.end(JSON.stringify(products));
    return;
  }

  // --- 5. Routing: zwykły tekst ---
  if (req.url === "/info") {
    res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
    res.end("To jest prosty backend napisany w Node.js.");
    return;
  }

  // --- 6. Routing: 404 dla innych ścieżek ---
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
  res.end("404 — Nie znaleziono");
});

// --- 7. Nasłuchiwanie na porcie ---
server.listen(3000);

Wyjaśnienie linia po linii

1. Import modułu HTTP

const http = require("http");
  • Ładujesz moduł http wbudowany w Node.js.
  • Ten moduł pozwala tworzyć serwery HTTP bez żadnych bibliotek.
  • To jest najniższy poziom backendu — „goły” serwer.

2. Dane przykładowe — lista produktów

const products = [
  { id: 1, name: "Kawa", price: 19.99 },
  { id: 2, name: "Herbata", price: 12.50 },
  { id: 3, name: "Czekolada", price: 8.99 }
];
  • Tworzysz tablicę obiektów.
  • To jest „udawana baza danych”.
  • Dzięki temu /api/products zwraca prawdziwe dane JSON.

3. Tworzenie serwera

const server = http.createServer((req, res) => {
  • Tworzysz serwer HTTP.
  • req — żądanie od klienta.
  • res — odpowiedź, którą wyślesz.

4. Routing — strona główna

if (req.url === "/") {
  • Sprawdzasz, czy użytkownik wszedł na /.
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
  • Ustawiasz status 200 OK.
  • Ustawiasz typ odpowiedzi na HTML.
res.end(`
  <h1>Witaj w moim pierwszym backendzie!</h1>
  <p>Przejdź do <a href="/api/products">/api/products</a>, aby zobaczyć dane JSON.</p>
  <p>Przejdź do <a href="/info">/info</a>, aby zobaczyć informacje o serwerze.</p>
`);
  • Wysyłasz HTML.
  • Widzisz, że backend może zwracać prawdziwe strony.

5. Routing — API zwracające JSON

if (req.url === "/api/products") {
  • Sprawdzasz ścieżkę /api/products.
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
  • Ustawiasz typ odpowiedzi na JSON.
res.end(JSON.stringify(products));
  • Zamieniasz tablicę obiektów na JSON.
  • Wysyłasz ją do klienta.

To jest prawdziwe API.

6. Routing — zwykły tekst

if (req.url === "/info") {
  • Sprawdzasz ścieżkę /info.
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end("To jest prosty backend napisany w Node.js.");
  • Wysyłasz zwykły tekst.

7. Routing — 404

res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
res.end("404 — Nie znaleziono");
  • Każda inna ścieżka → błąd 404.
  • To jest podstawowa obsługa błędów.

8. Nasłuchiwanie na porcie

server.listen(3000);
  • Serwer działa na porcie 3000.
  • Wchodzisz w przeglądarce:

Kod

http://localhost:3000

5. Ćwiczenia praktyczne

Ćwiczenie 1 — Event Loop (analiza kolejności)

Kod:

console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");

Oczekiwany wynik:

A
D
C
B

Dlaczego?

  • synchroniczne: A, D
  • microtasks: C
  • callback queue: B

Ćwiczenie 2 — Twój pierwszy projekt Node.js

Zadanie:

  1. Utwórz folder projektu.
  2. Zainicjalizuj package.json:
npm init -y
  1. Dodaj skrypt:
"scripts": {
  "start": "node app.js"
}
  1. Utwórz plik app.js:
console.log("Pierwszy projekt Node.js!");
  1. Uruchom:
npm start

Ćwiczenie 3 — Serwer HTTP

Zadanie:

Stwórz serwer, który zwraca:

  • tekst na /text,
  • HTML na /html,
  • JSON na /json.

Co dalej?

Po tym module będziesz umieć:

  • pracować z modułami,
  • używać fs i path,
  • czytać i zapisywać pliki,
  • pracować ze strumieniami,
  • tworzyć API w Express,
  • łączyć Node.js z bazą danych,
  • tworzyć pełne backendy.

Kryteria sukcesu

  • Rozumiem Event Loop
  • Umiem zainstalować Node.js
  • Umiem stworzyć projekt Node.js
  • Umiem uruchomić skrypt
  • Umiem stworzyć serwer HTTP
  • Umiem zwrócić HTML i JSON