Temat 36 · 2 × 45 min
Co dalej: TypeScript i Node.js
Znasz już cały JavaScript, którego potrzebuje przeglądarka. Ta lekcja pokazuje dwa kierunki, w które ten sam język rozrósł się poza nią. TypeScript dodaje do JS typy sprawdzane przed uruchomieniem – dzięki temu edytor podkreśla błąd cena.toFixd(2) w chwili pisania, a nie użytkownik po kliknięciu. Node.js uruchamia JavaScript bez przeglądarki: na twoim komputerze, na serwerze, w narzędziach takich jak Vite czy Vitest, których używałeś w lekcjach 32 i 33. Oba tematy są „zarysem”: po tej lekcji rozumiesz kod, który spotkasz w tutorialach i projektach, i wiesz, od czego zacząć samodzielnie.
Część 1. TypeScript
Czym jest TypeScript
TypeScript (Microsoft, 2012) to JavaScript plus adnotacje typów. Każdy poprawny program JS jest poprawnym programem TS; TS dodaje składnię do opisywania, jakiego typu są wartości, i kompilator tsc, który sprawdza, czy ten opis się zgadza. Typy istnieją tylko w czasie pisania: kompilator usuwa je i produkuje zwykły JavaScript, który uruchamia przeglądarka lub Node. W czasie działania nie ma żadnego sprawdzania – TS łapie błędy wcześniej, nie dodatkowo.
function obliczRate(koszt: number, liczbaRat: number): number {
return koszt / liczbaRat;
}
obliczRate(1800, 3); // OK
obliczRate('1800', 3); // błąd kompilacji: Argument of type 'string' is not assignable to parameter of type 'number'
obliczRate(1800); // błąd: Expected 2 arguments, but got 1Po kompilacji zostaje to, co znasz:
function obliczRate(koszt, liczbaRat) {
return koszt / liczbaRat;
}
console.log(obliczRate(1800, 3)); // 600
console.log(obliczRate('1800', 3)); // 600 – JS sam skonwertował; TS nie pozwoliłby tego napisaćZauważ, że drugie wywołanie w JavaScripcie działa (konwersja napisu na liczbę przy dzieleniu). Z '1800' + 3 byłby już napis '18003' – klasyczny błąd z lekcji 3, który TypeScript wychwyci, zanim uruchomisz kod.
Instalacja i pierwsza kompilacja
mkdir ts-start && cd ts-start
npm init -y
npm install -D typescript # TypeScript 7.x (2026) – kompilator napisany w Go, ok. 10× szybszy od 5.x
npx tsc --init # tworzy tsconfig.json
npx tsc # kompiluje wszystkie .ts w projekcie do .js
npx tsc --watch # kompiluje po każdym zapisietsconfig.json to konfiguracja kompilatora. Najważniejsze pola: target (do której wersji JS tłumaczyć – dla współczesnych przeglądarek es2022 lub esnext), strict (włącza wszystkie ścisłe sprawdzenia – od TypeScript 6 domyślnie włączony, w starszych wersjach ustaw ręcznie true), outDir (folder na wynikowe .js). Do szybkich prób nie potrzebujesz nawet instalacji: TypeScript Playground kompiluje w przeglądarce i pokazuje JS obok TS. W projekcie Vite wystarczy szablon react-ts lub vanilla-ts – kompilację robi Vite.
Typy podstawowe i wnioskowanie
Adnotacja typu to dwukropek po nazwie: let cena: number. Ale TypeScript potrafi wywnioskować typ z wartości początkowej – adnotacje piszesz głównie tam, gdzie wnioskowanie nie ma z czego skorzystać: parametry funkcji, puste tablice, zmienne bez wartości.
let imie = 'Ola'; // wywnioskowane: string
let wiek: number = 17; // adnotacja (tu zbędna – 17 i tak jest number)
let aktywny = true; // boolean
let oceny: number[] = []; // pusta tablica – trzeba powiedzieć, co w niej będzie
let nic: null = null;
imie = 42; // błąd: Type 'number' is not assignable to type 'string'
oceny.push('5'); // błąd: '5' to string| Typ TS | Co oznacza | Przykład |
|---|---|---|
string, number, boolean | typy proste jak w JS (małe litery!) | let x: number = 5 |
number[] lub Array<number> | tablica liczb | [1, 2, 3] |
[string, number] | krotka – tablica o stałej długości i typach | ['Ola', 17] |
null, undefined | brak wartości; w strict nie wchodzą do innych typów | `let a: string |
any | „wyłącz sprawdzanie” – unikaj | wszystko przechodzi |
unknown | „nie wiem, co to” – trzeba sprawdzić przed użyciem | wynik JSON.parse |
void | funkcja nic nie zwraca | (): void => {} |
| `‘tak’ | ‘nie’` | typ literalny – tylko te wartości |
Która linia spowoduje błąd kompilacji TypeScript?
let liczba = 10;
let napis = 'dziesięć';
liczba = liczba + 5; // A
napis = napis + liczba; // B
liczba = napis.length; // C
liczba = napis; // DUnie i zawężanie
Unia (union) A | B mówi: wartość jest typu A lub B. TypeScript nie pozwoli wywołać metody, której nie mają oba typy – najpierw musisz zawęzić typ sprawdzeniem (typeof, ===, in, Array.isArray), a kompilator śledzi te sprawdzenia w kodzie.
function formatujId(id: number | string): string {
// tu id to number | string – toUpperCase niedostępne
if (typeof id === 'string') {
return id.toUpperCase(); // tu TS wie: string
}
return id.toFixed(0); // tu TS wie: number
}
type Status = 'nowe' | 'w toku' | 'zrobione'; // unia literałów = wyliczenie bez enum
function opisz(status: Status): string {
switch (status) {
case 'nowe': return 'Do zrobienia';
case 'w toku': return 'Pracujemy';
case 'zrobione': return 'Gotowe';
}
}
opisz('zrobione'); // OK
opisz('gotowe'); // błąd: Argument of type '"gotowe"' is not assignable to parameter of type 'Status'Literówka w nazwie statusu – w JavaScripcie cichy błąd, który zauważysz dopiero, gdy zadanie nie pojawi się na liście. W TypeScripcie czerwone podkreślenie w edytorze.
Obiekty: type i interface
Kształt obiektu opisujesz aliasem typu (type) albo interfejsem (interface). W codziennym użyciu są wymienne; interface można rozszerzać przez extends i jest częściej używany dla obiektów, type – dla unii i typów złożonych. Znak ? oznacza pole opcjonalne, readonly – tylko do odczytu.
interface Uczen {
readonly id: number;
imie: string;
klasa: string;
oceny: number[];
opiekun?: string; // opcjonalne – może nie istnieć
}
type Produkt = {
nazwa: string;
cena: number;
dostepny: boolean;
};
const ola: Uczen = { id: 1, imie: 'Ola', klasa: '3TP', oceny: [5, 4] };
ola.id = 2; // błąd: readonly
ola.klasa = 4; // błąd: number zamiast string
const bez: Uczen = { id: 2, imie: 'Jan' }; // błąd: brakuje klasa i oceny
function srednia(u: Uczen): number {
if (u.oceny.length === 0) return 0;
return u.oceny.reduce((s, o) => s + o, 0) / u.oceny.length;
}
// interfejs rozszerzony
interface Absolwent extends Uczen {
rokMatury: number;
}Dane z fetch i JSON.parse mają typ any (TS nie zna odpowiedzi serwera), więc ty mówisz, czego się spodziewasz: const dane = (await odp.json()) as Uczen[]. Słowo as to asercja – obietnica wobec kompilatora, nie sprawdzenie w czasie działania. Jeśli serwer przyśle coś innego, błąd pojawi się jak w zwykłym JS.
Funkcje
// parametry z typami, typ zwracany po nawiasie
function rabat(cena: number, procent: number = 10): number {
return cena * (1 - procent / 100);
}
// parametr opcjonalny (?) musi być na końcu
function powitaj(imie: string, tytul?: string): string {
return tytul ? `${tytul} ${imie}` : imie;
}
// funkcja strzałkowa z typem – np. callback dla addEventListener
const naKlik = (e: MouseEvent): void => {
console.log(e.clientX);
};
// typ funkcji jako parametr
function zastosuj(liczby: number[], fn: (x: number) => number): number[] {
return liczby.map(fn);
}
zastosuj([1, 2, 3], (x) => x * 2); // x wywnioskowane jako numberTypeScript odróżnia też funkcję, która zwraca number, od tej, która może zwrócić number | undefined (np. tablica.find(...)). W trybie strict zanim użyjesz wyniku find, musisz sprawdzić, czy nie jest undefined – to dokładnie ten błąd „Cannot read properties of undefined”, który w JS widziałeś w konsoli po fakcie.
Typowanie DOM
API DOM jest w całości opisane typami. document.getElementById zwraca HTMLElement | null – bo element może nie istnieć – a ogólny HTMLElement nie ma pola value. Zawężasz asercją do konkretnego typu i sprawdzasz null:
const pole = document.getElementById('cena') as HTMLInputElement | null;
const wynik = document.querySelector<HTMLParagraphElement>('#wynik');
if (pole && wynik) {
pole.addEventListener('input', () => {
const cena = parseFloat(pole.value); // value: string – jak w JS
wynik.textContent = `Brutto: ${(cena * 1.23).toFixed(2)} zł`;
});
}Irytujące? Trochę. Ale każde z tych sprawdzeń odpowiada realnemu błędowi egzaminacyjnemu z lekcji 19: literówka w id i skrypt, który nie startuje.
Dlaczego TypeScript w trybie strict odrzuci tę linię?
const przycisk = document.getElementById('oblicz');
przycisk.addEventListener('click', licz);Generyki – pierwszy kontakt
Czasem funkcja działa tak samo dla dowolnego typu: „zwróć pierwszy element tablicy”. Zamiast pisać ją trzy razy albo używać any, wprowadzasz parametr typu <T> – zmienną, w którą TypeScript podstawi konkretny typ przy wywołaniu.
function pierwszy<T>(tablica: T[]): T | undefined {
return tablica[0];
}
const a = pierwszy([1, 2, 3]); // a: number | undefined
const b = pierwszy(['x', 'y']); // b: string | undefined
const c = pierwszy<Uczen>([]); // c: Uczen | undefinedGeneryki widzisz w typach wbudowanych: Array<number>, Promise<Uczen[]> (obietnica tablicy uczniów – tak opiszesz wynik async funkcji z fetch), Map<string, number>. Na tym etapie wystarczy, że je czytasz; pisać własne będziesz, gdy zauważysz, że kopiujesz funkcję dla różnych typów.
Tryb strict i any
strict to pakiet reguł: noImplicitAny (każdy parametr bez typu to błąd, nie any), strictNullChecks (null/undefined trzeba obsłużyć), i kilka innych. Nowe projekty zawsze zaczynaj ze strict – od TypeScript 6 jest domyślny. Pokusa „wpiszę any, bo się czerwieni” jest duża; any wyłącza TypeScript dla tej wartości i wszystkiego, co z niej wynika. Jeśli naprawdę nie wiesz, czym coś jest (dane z sieci), użyj unknown i sprawdź.
Część 2. Node.js
Czym jest Node.js
Node.js (2009) to silnik V8 z Chrome wyjęty z przeglądarki i uzupełniony o to, czego przeglądarka nie daje: dostęp do plików, sieci, procesów. Ten sam język, te same const, Promise, async/await, fetch, JSON – ale bez document, window, alert i DOM, bo nie ma strony. Zamiast nich moduły wbudowane: node:fs (pliki), node:http (serwer), node:path, process (argumenty, zmienne środowiskowe).
Wersje (październik 2026): Node 24 „Krypton” to aktualne LTS – wersja do projektów; Node 26 jest w fazie Current i stanie się LTS w październiku 2026; Node 22 jest jeszcze wspierane. Sprawdź swoją: node --version. Node masz zainstalowany od lekcji 32 – bez niego nie działałby ani npm, ani Vite.
Pierwszy skrypt i process.argv
const imie = process.argv[2] ?? 'nieznajomy'; // [0] = node, [1] = ścieżka skryptu, [2] = pierwszy argument
console.log(`Cześć, ${imie}! Node ${process.version}`);node powitanie.js Ola
# Cześć, Ola! Node v24.11.0
node powitanie.js
# Cześć, nieznajomy! Node v24.11.0process.argv to tablica napisów z wiersza poleceń – argumenty liczbowe konwertujesz sam (Number(process.argv[3])), tak jak input.value w przeglądarce. console.log pisze do terminala. Ten mechanizm możesz zasymulować w zwykłym bloku:
// tak wygląda process.argv dla: node raport.js oceny.json 3
const argv = ['/usr/bin/node', '/home/ola/raport.js', 'oceny.json', '3'];
const [plik, prog] = argv.slice(2); // pomijasz node i ścieżkę skryptu
console.log('plik:', plik, '| próg:', Number(prog));Moduły ES w Node
Node obsługuje dwa systemy modułów: starszy CommonJS (require, module.exports – zobaczysz go w wielu tutorialach) i standardowe moduły ES (import/export) z lekcji 32. Żeby pliki .js były traktowane jako moduły ES, wpisz w package.json "type": "module" (albo nadaj plikom rozszerzenie .mjs). Moduły wbudowane importujesz z przedrostkiem node:.
{
"name": "raport-ocen",
"type": "module",
"scripts": {
"start": "node raport.js oceny.json",
"dev": "node --watch raport.js oceny.json",
"api": "json-server db.json"
}
}export function srednia(liczby) {
if (liczby.length === 0) return 0;
return liczby.reduce((s, x) => s + x, 0) / liczby.length;
}import { readFile } from 'node:fs/promises';
import { srednia } from './narzedzia.js'; // w Node rozszerzenie .js jest obowiązkowe
const sciezka = process.argv[2] ?? 'oceny.json';
const tekst = await readFile(sciezka, 'utf8'); // top-level await działa w modułach ES
const uczniowie = JSON.parse(tekst);
for (const u of uczniowie) {
console.log(`${u.imie.padEnd(10)} ${srednia(u.oceny).toFixed(2)}`);
}[
{ "imie": "Ola", "oceny": [5, 4, 6] },
{ "imie": "Bartek", "oceny": [3, 3, 4, 2] },
{ "imie": "Kasia", "oceny": [] }
]npm start
# Ola 5.00
# Bartek 3.00
# Kasia 0.00Trzy rzeczy do zapamiętania: readFile z node:fs/promises zwraca Promise (stąd await), drugi argument 'utf8' daje napis zamiast surowych bajtów, a wczytany JSON parsujesz tak samo jak odpowiedź z fetch. Zapis jest symetryczny: await writeFile('wynik.json', JSON.stringify(dane, null, 2)).
Co wypisze ten skrypt uruchomiony poleceniem node suma.js 4 5?
const [a, b] = process.argv.slice(2);
console.log(a + b);Skrypty npm i tryb —watch
Sekcja scripts w package.json to nazwane polecenia projektu: npm run dev, npm run api (start i test działają bez run). Dzięki niej każdy w zespole uruchamia projekt tak samo, a w skryptach możesz używać paczek z node_modules bez npx. Flaga node --watch plik.js restartuje skrypt po każdej zmianie pliku – odpowiednik odświeżania Vite.
Mini serwer HTTP
Serwer w Node to funkcja wywoływana dla każdego żądania: dostaje req (co przyszło: adres, metoda) i res (co odsyłasz: status, nagłówki, treść). Poniżej serwer, który pod /api/zadania zwraca JSON, a wszędzie indziej 404 – wystarczający, żeby frontend z lekcji 31 miał z czym rozmawiać.
import { createServer } from 'node:http';
const zadania = [
{ id: 1, tekst: 'Powtórzyć pętle', zrobione: true },
{ id: 2, tekst: 'Zrobić arkusz 01.2025', zrobione: false },
];
const serwer = createServer((req, res) => {
console.log(req.method, req.url); // log każdego żądania w terminalu
res.setHeader('Access-Control-Allow-Origin', '*'); // CORS – pozwól stronie z innego portu
if (req.method === 'GET' && req.url === '/api/zadania') {
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(zadania));
return;
}
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Nie znaleziono');
});
serwer.listen(3000, () => console.log('Serwer działa: http://localhost:3000/api/zadania'));Po node serwer.js wejdź w przeglądarce na http://localhost:3000/api/zadania – zobaczysz JSON. Z frontu pobierzesz go zwykłym fetch('http://localhost:3000/api/zadania'). Obsługa POST (czytanie treści żądania), wielu tras i bazy danych to już zadanie dla frameworka (Express, Fastify, Hono) – w INF.03 tę rolę pełni PHP.
json-server: REST w jedną minutę
Do ćwiczenia frontendu nie piszesz własnego serwera. json-server zamienia plik JSON w pełne REST API z GET/POST/PUT/PATCH/DELETE:
npm install -D json-server
npx json-server db.json
# JSON Server started on PORT :3000
# http://localhost:3000/zadania{
"zadania": [
{ "id": "1", "tekst": "Powtórzyć pętle", "zrobione": true },
{ "id": "2", "tekst": "Zrobić arkusz 01.2025", "zrobione": false }
]
}Każda tablica w db.json staje się zasobem: GET /zadania, GET /zadania/1, POST /zadania (z JSON w treści), DELETE /zadania/2. Zmiany zapisują się do pliku. Aktualna wersja 1.x (beta) nadaje identyfikatorom typ napisu – stąd "id": "1". To ten sam json-server, którego używałeś w lekcji 31; teraz wiesz, że to zwykły skrypt Node.
Node uruchamia TypeScript
Od Node 22.18 i 23.6 (a stabilnie od 24.12) polecenie node skrypt.ts działa bez instalowania czegokolwiek: Node usuwa typy (type stripping) i wykonuje kod. Ważne ograniczenia:
- Node nie sprawdza typów – literówka w typie przejdzie; do sprawdzania nadal potrzebujesz
tsc(lub edytora, który uruchamia go w tle), - działa tylko składnia „wymazywalna”: adnotacje,
interface,type;enum,namespacez kodem i parametry-właściwości w konstruktorze kończą się błędemERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(w Node 22–25 dało się je włączyć eksperymentalną flagą--experimental-transform-types, ale Node 26 tę flagę usunął – zamiastenumużywaj unii literałów), - w importach podajesz pełne rozszerzenie:
import { f } from './plik.ts', - typy importujesz ze słowem
type:import type { Uczen } from './typy.ts'– inaczej Node szuka nieistniejącej wartości.
import { readFile } from 'node:fs/promises';
interface Uczen {
imie: string;
oceny: number[];
}
const uczniowie = JSON.parse(await readFile('oceny.json', 'utf8')) as Uczen[];
for (const u of uczniowie) {
const s = u.oceny.length ? u.oceny.reduce((a, b) => a + b, 0) / u.oceny.length : 0;
console.log(`${u.imie}: ${s.toFixed(2)}`);
}node raport.ts # Node 24+: działa od razu
npx tsc --noEmit # a tu sprawdzasz typy, bez generowania .jsGdy potrzebujesz pełnej składni TS (np. enum), popularne jest narzędzie tsx (npx tsx raport.ts) – ono jednak też nie sprawdza typów, tylko je przekształca; sprawdzanie zawsze robi tsc. W projektach Vite nic z tego nie konfigurujesz – robi to szablon.
Gdzie to wszystko się spotyka
| Miejsce | Co uruchamia JS | Typowe narzędzia |
|---|---|---|
| przeglądarka użytkownika | twój front (lekcje 1–35) | Vite buduje, React renderuje |
| twój komputer – narzędzia | Node | npm, Vite, Vitest, ESLint, tsc |
| serwer aplikacji | Node (INF.04) lub PHP/Python (INF.03) | Express/Fastify; json-server do ćwiczeń |
| wszystko powyżej | TypeScript → JS | tsc, szablony -ts w Vite |
Pułapki – podsumowanie
| Błąd | Objaw | Poprawka |
|---|---|---|
any „żeby przestało się czerwienić” | TS nic nie sprawdza dalej | właściwy typ albo unknown + sprawdzenie |
brak sprawdzenia null po getElementById | błąd strict / TypeError w JS | if (el) lub el?. |
String, Number wielką literą w typach | działa, ale to typy obiektów opakowujących | zawsze string, number |
mieszanie require i import w jednym projekcie | ReferenceError: require is not defined w module ES | "type": "module" i tylko import |
import './plik' bez rozszerzenia w Node | ERR_MODULE_NOT_FOUND | ./plik.js / ./plik.ts |
process.argv[2] + 1 | sklejony napis | Number(...) |
readFileSync w serwerze | blokuje wszystkie żądania | fs/promises + await |
enum w pliku uruchamianym przez node plik.ts | ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX | unia literałów albo tsx (flaga --experimental-transform-types działała tylko do Node 25) |
__dirname w module ES | undefined | import.meta.dirname (Node 20.11+) |
Ściąga
| Składnia | Co robi | Przykład |
|---|---|---|
let x: number | adnotacja typu | let cena: number = 9.99 |
number[], Array<T> | tablica | const oceny: number[] = [] |
| `A | B` | unia |
| `‘a’ | ‘b’` | typ literalny |
interface N { pole: T; opc?: T } | kształt obiektu | interface Uczen { imie: string } |
type N = {…} | alias typu | type Punkt = { x: number; y: number } |
function f(a: T): R | typy funkcji | (cena: number): string |
wartosc as Typ | asercja (bez sprawdzania!) | el as HTMLInputElement |
function f<T>(x: T): T | generyk | pierwszy<Uczen>(lista) |
npx tsc / --watch / --noEmit | kompilacja / na bieżąco / tylko sprawdzenie | npx tsc --noEmit |
node plik.js / node plik.ts | uruchomienie skryptu | .ts od Node 22.18/23.6 |
process.argv.slice(2) | argumenty wiersza poleceń (napisy) | node s.js a b → ['a','b'] |
import { readFile } from 'node:fs/promises' | pliki | await readFile('x.json', 'utf8') |
"type": "module" | moduły ES w Node | w package.json |
"scripts": { "dev": "…" } | skrypty npm | npm run dev |
createServer((req, res) => …).listen(3000) | serwer HTTP | node:http |
npx json-server db.json | REST API z pliku | GET /zadania/1 |
Zadania
Otypuj kalkulator
Poniższe funkcje z lekcji 14 są w czystym JS. Przepisz je do TypeScriptu: dodaj typy parametrów i wartości zwracanych, a koszt z undefined zamień na parametr opcjonalny z wartością domyślną. Sprawdź w TypeScript Playground, że nie ma błędów, a wywołanie obliczRate('1800', 3) jest odrzucane.
function obliczKoszt(ceny, wybrane) {
let suma = 0;
for (const nazwa of wybrane) suma += ceny[nazwa];
return suma;
}
function obliczRate(koszt, liczbaRat) {
return Math.round((koszt / liczbaRat) * 100) / 100;
}
function komunikat(miasto, koszt, liczbaRat, rata) {
return `Kurs odbędzie się w ${miasto}. Koszt całkowity: ${koszt} zł. Płacisz ${liczbaRat} rat po ${rata} zł`;
}Pokaż rozwiązanie
// Cennik: klucz to nazwa kursu, wartość to cena – typ indeksowy
type Cennik = Record<string, number>; // to samo co { [nazwa: string]: number }
function obliczKoszt(ceny: Cennik, wybrane: string[]): number {
let suma = 0;
for (const nazwa of wybrane) suma += ceny[nazwa] ?? 0; // ?? 0 – nieznany kurs nie daje NaN
return suma;
}
function obliczRate(koszt: number, liczbaRat: number = 1): number {
return Math.round((koszt / liczbaRat) * 100) / 100;
}
function komunikat(miasto: string, koszt: number, liczbaRat: number, rata: number): string {
return `Kurs odbędzie się w ${miasto}. Koszt całkowity: ${koszt} zł. Płacisz ${liczbaRat} rat po ${rata} zł`;
}
const ceny: Cennik = { HTML: 500, CSS: 400, JavaScript: 900 };
const koszt = obliczKoszt(ceny, ['HTML', 'JavaScript']);
console.log(komunikat('Poznań', koszt, 3, obliczRate(koszt, 3)));
// obliczRate('1800', 3); // błąd: Argument of type 'string' is not assignable to parameter of type 'number'Po usunięciu typów to dokładnie ten sam kod, co w kalkulator.js – możesz to sprawdzić w prawej kolumnie Playgroundu. Wynik działania:
function obliczKoszt(ceny, wybrane) {
let suma = 0;
for (const nazwa of wybrane) suma += ceny[nazwa] ?? 0;
return suma;
}
function obliczRate(koszt, liczbaRat = 1) {
return Math.round((koszt / liczbaRat) * 100) / 100;
}
function komunikat(miasto, koszt, liczbaRat, rata) {
return `Kurs odbędzie się w ${miasto}. Koszt całkowity: ${koszt} zł. Płacisz ${liczbaRat} rat po ${rata} zł`;
}
const ceny = { HTML: 500, CSS: 400, JavaScript: 900 };
const koszt = obliczKoszt(ceny, ['HTML', 'JavaScript']);
console.log(komunikat('Poznań', koszt, 3, obliczRate(koszt, 3)));Interfejs Zadanie i filtrowanie po statusie
Zdefiniuj typ Status jako unię literałów 'nowe' | 'w toku' | 'zrobione' oraz interfejs Zadanie z polami: id (liczba, readonly), tytul (napis), status (Status), termin (napis, opcjonalny). Napisz funkcje:
wgStatusu(zadania: Zadanie[], status: Status): Zadanie[],zmienStatus(zadanie: Zadanie, nowy: Status): Zadanie– zwraca nowy obiekt (bez mutacji, jak w React),podsumowanie(zadania: Zadanie[]): Record<Status, number>– ile zadań ma każdy status.
Sprawdź, że zmienStatus(z, 'gotowe') nie kompiluje się.
Pokaż rozwiązanie
type Status = 'nowe' | 'w toku' | 'zrobione';
interface Zadanie {
readonly id: number;
tytul: string;
status: Status;
termin?: string;
}
function wgStatusu(zadania: Zadanie[], status: Status): Zadanie[] {
return zadania.filter((z) => z.status === status);
}
function zmienStatus(zadanie: Zadanie, nowy: Status): Zadanie {
return { ...zadanie, status: nowy }; // spread tworzy nowy obiekt, id pozostaje readonly
}
function podsumowanie(zadania: Zadanie[]): Record<Status, number> {
// zaczynasz od zer, żeby brakujący status też był w wyniku
const wynik: Record<Status, number> = { nowe: 0, 'w toku': 0, zrobione: 0 };
for (const z of zadania) wynik[z.status]++;
return wynik;
}
const lista: Zadanie[] = [
{ id: 1, tytul: 'Powtórzyć pętle', status: 'zrobione' },
{ id: 2, tytul: 'Arkusz 01.2025', status: 'w toku', termin: '2026-11-05' },
{ id: 3, tytul: 'README projektu', status: 'nowe' },
];
console.log(wgStatusu(lista, 'nowe').map((z) => z.tytul)); // ['README projektu']
console.log(podsumowanie(lista)); // { nowe: 1, 'w toku': 1, zrobione: 1 }
console.log(zmienStatus(lista[2], 'w toku').status); // 'w toku'
// zmienStatus(lista[2], 'gotowe'); // błąd: '"gotowe"' is not assignable to parameter of type 'Status'
// lista[0].id = 9; // błąd: Cannot assign to 'id' because it is a read-only propertyRecord<Status, number> to wbudowany typ generyczny: obiekt, którego kluczami są wszystkie wartości unii Status, a wartościami liczby. Gdybyś zapomniał jednego klucza w wynik, kompilator by to zgłosił.
Raport z pliku JSON w Node
Napisz skrypt raport.js (moduł ES) uruchamiany poleceniem node raport.js uczniowie.json 3.5, który:
- czyta plik podany jako pierwszy argument (
fs/promises), a przy braku argumentu wypisuje „Użycie: node raport.js plik.json [próg]” i kończy działanie z kodem 1 (process.exit(1)), - drugi argument to próg średniej (domyślnie 2.0),
- dla każdego ucznia wypisuje imię, średnią z dwoma miejscami i słowo „ZAGROŻONY”, jeśli średnia jest poniżej progu,
- na końcu zapisuje do
zagrozeni.jsontablicę imion uczniów poniżej progu, - błąd odczytu pliku (np. zła nazwa) łapie w
try/catchi wypisuje czytelny komunikat zamiast stosu wywołań.
Przykładowy plik:
[
{ "imie": "Ola", "oceny": [5, 4, 6] },
{ "imie": "Bartek", "oceny": [3, 2, 2, 2] },
{ "imie": "Kasia", "oceny": [] }
]Pokaż rozwiązanie
import { readFile, writeFile } from 'node:fs/promises';
const [plik, progArg] = process.argv.slice(2);
if (!plik) {
console.error('Użycie: node raport.js plik.json [próg]');
process.exit(1); // kod ≠ 0 oznacza błąd dla systemu / skryptów npm
}
const prog = Number(progArg ?? 2); // argumenty są napisami
function srednia(oceny) {
if (oceny.length === 0) return 0;
return oceny.reduce((suma, o) => suma + o, 0) / oceny.length;
}
try {
const uczniowie = JSON.parse(await readFile(plik, 'utf8'));
const zagrozeni = [];
for (const u of uczniowie) {
const s = srednia(u.oceny);
const flaga = s < prog ? ' ZAGROŻONY' : '';
console.log(`${u.imie.padEnd(10)} ${s.toFixed(2)}${flaga}`);
if (s < prog) zagrozeni.push(u.imie);
}
await writeFile('zagrozeni.json', JSON.stringify(zagrozeni, null, 2));
console.log(`Zapisano ${zagrozeni.length} imion do zagrozeni.json`);
} catch (e) {
// ENOENT = brak pliku; SyntaxError = zepsuty JSON
console.error(`Nie udało się przetworzyć pliku „${plik}”: ${e.message}`);
process.exit(1);
}Logikę raportu bez części plikowej możesz sprawdzić tutaj:
const uczniowie = [
{ imie: 'Ola', oceny: [5, 4, 6] },
{ imie: 'Bartek', oceny: [3, 2, 2, 2] },
{ imie: 'Kasia', oceny: [] },
];
const prog = Number('3.5');
const srednia = (o) => (o.length ? o.reduce((s, x) => s + x, 0) / o.length : 0);
const zagrozeni = [];
for (const u of uczniowie) {
const s = srednia(u.oceny);
console.log(`${u.imie.padEnd(10)} ${s.toFixed(2)}${s < prog ? ' ZAGROŻONY' : ''}`);
if (s < prog) zagrozeni.push(u.imie);
}
console.log(JSON.stringify(zagrozeni));Mini API zadań w node:http
Rozbuduj serwer z lekcji o pełną obsługę listy zadań w pamięci:
GET /api/zadania– wszystkie zadania (JSON),GET /api/zadania/2– jedno zadanie lub 404 z{ "blad": "Nie ma zadania 2" },POST /api/zadania– treść żądania to JSON{ "tekst": "..." }; serwer nadajeidizrobione: false, odpowiada 201 i nowym obiektem; pustytekst→ 400,DELETE /api/zadania/2– usuwa, odpowiada 204 bez treści,- obsłuż
OPTIONS(preflight CORS), żebyfetchze strony na innym porcie działał dla POST/DELETE.
Treść żądania w node:http przychodzi strumieniem: zbierz ją przez for await (const czesc of req) dane += czesc;. Przetestuj z przeglądarki frontendem z lekcji 31 albo poleceniem curl -X POST -H "Content-Type: application/json" -d '{"tekst":"Test"}' http://localhost:3000/api/zadania.
Pokaż rozwiązanie
import { createServer } from 'node:http';
let zadania = [
{ id: 1, tekst: 'Powtórzyć pętle', zrobione: true },
{ id: 2, tekst: 'Zrobić arkusz 01.2025', zrobione: false },
];
let nastepneId = 3;
// pomocnik: odpowiedź JSON z kodem statusu
function odpowiedz(res, status, dane) {
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(dane === undefined ? undefined : JSON.stringify(dane));
}
// pomocnik: treść żądania jako obiekt (lub null, gdy nie jest JSON-em)
async function wczytajJson(req) {
let dane = '';
for await (const czesc of req) dane += czesc;
try {
return JSON.parse(dane || '{}');
} catch {
return null;
}
}
const serwer = createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') { // preflight – przeglądarka pyta, czy może wysłać POST/DELETE
res.writeHead(204);
return res.end();
}
const url = new URL(req.url, 'http://localhost');
const czesci = url.pathname.split('/').filter(Boolean); // '/api/zadania/2' → ['api', 'zadania', '2']
const id = czesci[2] !== undefined ? Number(czesci[2]) : null;
if (czesci[0] !== 'api' || czesci[1] !== 'zadania') {
return odpowiedz(res, 404, { blad: 'Nie znaleziono' });
}
if (req.method === 'GET' && id === null) return odpowiedz(res, 200, zadania);
if (req.method === 'GET') {
const z = zadania.find((x) => x.id === id);
return z ? odpowiedz(res, 200, z) : odpowiedz(res, 404, { blad: `Nie ma zadania ${id}` });
}
if (req.method === 'POST' && id === null) {
const dane = await wczytajJson(req);
const tekst = typeof dane?.tekst === 'string' ? dane.tekst.trim() : '';
if (tekst === '') return odpowiedz(res, 400, { blad: 'Pole tekst jest wymagane' });
const nowe = { id: nastepneId++, tekst, zrobione: false };
zadania.push(nowe);
return odpowiedz(res, 201, nowe);
}
if (req.method === 'DELETE' && id !== null) {
const bylo = zadania.length;
zadania = zadania.filter((x) => x.id !== id);
return bylo === zadania.length ? odpowiedz(res, 404, { blad: `Nie ma zadania ${id}` }) : odpowiedz(res, 204);
}
odpowiedz(res, 405, { blad: 'Niedozwolona metoda' });
});
serwer.listen(3000, () => console.log('API: http://localhost:3000/api/zadania'));Dane żyją w pamięci – po restarcie serwera wracają do stanu początkowego. Następny krok (poza tą lekcją) to zapis do pliku przez writeFile po każdej zmianie albo baza danych – w Node 24 jest do tego wbudowany, wciąż oznaczony jako eksperymentalny moduł node:sqlite.
Test powtórkowy
Test powtórkowy
Format jak w części pisemnej INF.04: 4 odpowiedzi, 1 poprawna.
Co robi kompilator tsc z adnotacjami typów?
Która deklaracja jest poprawna w TypeScript?
Jaki typ ma zmienna wynik?
function znajdz(lista: string[], szukany: string) {
return lista.find((x) => x === szukany);
}
const wynik = znajdz(['a', 'b'], 'c');Które przypisanie odrzuci kompilator?
type Rozmiar = 'S' | 'M' | 'L';
let r: Rozmiar;Co oznacza ? w deklaracji opiekun?: string wewnątrz interfejsu?
Czego NIE ma w Node.js w porównaniu z przeglądarką?
Co wypisze skrypt uruchomiony jako node skrypt.js oceny.json?
console.log(process.argv.length, process.argv[2]);Który zapis poprawnie odczytuje plik tekstowy w module ES w Node?
W package.json jest skrypt dev: node --watch app.js. Którym poleceniem go uruchomisz?
Dlaczego node raport.ts może zakończyć się błędem, mimo że tsc kompiluje plik bez zarzutu?