4. blokk · Node felé · 14. fejezet
Eddig minden kód egy fájlban fért el. Egy valódi Node-projekt tíz, száz fájlból áll, és ezek modulokként
adnak át egymásnak függvényeket, osztályokat, adatokat — export-tal és import-tal.
A fejezet első fele ezt tanítja meg, és felismerteti a régebbi CommonJS-szintaxist is. A második fele egy záró feladatsor:
az egész kurzus anyaga egy kis webshop-készletkezelőn, tömb-metódusokkal, osztállyal, hibakezeléssel.
as) és * as-szel — ./ és .js kiterjesztéssel.require / module.exports) kódot, és át tudod írni ES-modulra.Egy 300 soros fájlban még elboldogulsz. Egy 3000 sorosban már nem találod, hol van a kosár összegzése, és ha két ember dolgozik rajta, folyton egymás sorait írják felül. A megoldás: egy fájl = egy jól körülhatárolt felelősség.
kosar.js-ben csak kosár-logika van; a fájlnév maga a dokumentáció.penz.js formázóját a kosár, a rendelés és a jelentés is importálja — egyszer írtad meg.Az ehhez szükséges nyelvi eszköz az ES-modul (ESM): két kulcsszó, export és import.
Node-ban minden fájl külön modul; a modulok egymást fájlútvonallal hivatkozzák.
Egy modulból alapból semmi nem látszik kifelé. Amit más fájlnak is használnia kell, azt export-tal jelölöd meg.
Két fajtája van.
// keszlet.js export const AFA = 1.27; // a deklaráció elé írva… export function keszletErtek(lista) { return lista.reduce((s, t) => s + t.ar * t.keszlet, 0); } function kifogyottak(lista) { return lista.filter(t => t.keszlet === 0).map(t => t.nev); } function segedSzamitas() { /* belső, nem exportáljuk */ } export { kifogyottak }; // …vagy a fájl végén, listában
// penz.js export default function formazFt(osszeg) { return `${osszeg} Ft`; } // vagy egy osztály: // export default class Kosar { ... }
| Named | Default | |
|---|---|---|
| Hány lehet fájlonként | akármennyi | egy |
| Importálás | import { nev } — a név kötött | import barmi — te választod a nevet |
| Mikor | segédfüggvények, konstansok gyűjteménye | ha a modul „egy dologról" szól (egy osztály, egy fő függvény) |
A kettő keverhető is egy fájlban (egy default + több named), de a projektek többsége az egyszerűség kedvéért inkább csak named exportot használ. A lényeg: csak azt exportáld, ami kifelé kell — a többi marad a modul belügye.
Az import a fájl tetején áll, és megmondja, mit honnan kérsz. A négy alak:
// index.js import { keszletErtek, kifogyottak } from "./keszlet.js"; // named: kapcsos zárójel, pontos nevek import { AFA as afaSzorzo } from "./keszlet.js"; // alias: átnevezés as-szel import formazFt from "./penz.js"; // default: nincs { }, a név szabad import * as keszlet from "./keszlet.js"; // minden named export egy objektumba console.log(keszlet.keszletErtek(termekek)); // a * as-nél pont-jelöléssel
Saját fájlt relatív útvonallal hivatkozol: "./keszlet.js" (ugyanaz a mappa) vagy
"../utils/penz.js" (egy szinttel feljebb). A ./ nélküli "keszlet.js"-t
Node telepített csomagnak hinné, és nem találná. A .js kiterjesztést Node ESM-ben ki kell írni —
ez az egyik leggyakoribb kezdő hiba („Cannot find module"). Csomagot viszont csak névvel importálsz: import express from "express".
Minden modulnak saját hatóköre van: a benne deklarált const/let/function
csak ott létezik, akkor is, ha nem exportálod — két fájlban nyugodtan lehet ugyanaz a termekek változó.
Csak az importált nevek jönnek át, és azok csak olvashatók: az importált változónak nem adhatsz új értéket (az objektum
tartalmát viszont módosíthatod). A modul kódja egyszer fut le, akárhány fájl importálja — ezért egy modulban tartott
const tetelek = [] az egész program közös állapota lehet.
Az ES-modul 2015-ös; Node előtte már saját modulrendszert használt, a CommonJS-t (CJS). Rengeteg régi kód, példa és dokumentáció ebben íródott, ezért fel kell ismerned — írni már ESM-ben fogsz:
// keszlet.js function keszletErtek(lista) { /* … */ } module.exports = { keszletErtek }; // index.js const { keszletErtek } = require("./keszlet.js"); const fs = require("fs");
// keszlet.js export function keszletErtek(lista) { /* … */ } // index.js import { keszletErtek } from "./keszlet.js"; import fs from "fs";
| CommonJS | ES-modul | |
|---|---|---|
| Exportálás | module.exports = { a, b } vagy exports.a = … | export function a, export { a, b }, export default |
| Importálás | const { a } = require("./x.js") — sima függvényhívás | import { a } from "./x.js" — csak a fájl tetején |
| „Fő" export | module.exports = fuggveny | export default fuggveny |
| Kiterjesztés | elhagyható ("./x") | kötelező ("./x.js") |
| Mikor fut a betöltés | ott, ahol a require áll (akár feltételesen) | a fájl futása előtt, statikusan |
| Node-ban a fájl | alapértelmezés, vagy .cjs | "type": "module" a package.json-ban, vagy .mjs |
A "type": "module" egyetlen sor a projekt package.json fájljában, amitől Node az összes
.js fájlt ES-modulként olvassa. A package.json-t és az npm-et a Node-kurzus
első órája részletezi — most annyit jegyezz meg, hogy ez a sor kell, vagy a .mjs kiterjesztés.
Így néz ki a kurzus végigfutó példája három modulra bontva. Az adat, a logika és a „főprogram" külön fájl:
$ ls index.js kosar.js termekek.js package.json ← benne: { "type": "module" }
// ---------- termekek.js — az adat és a hozzá tartozó kis segédek ---------- export const termekek = [ { nev: "Egér", ar: 6990, keszlet: 3 }, { nev: "Kábel", ar: 1990, keszlet: 0 }, { nev: "Monitor", ar: 54990, keszlet: 2 } ]; export function keres(nev) { return termekek.find(t => t.nev === nev); }
// ---------- kosar.js — egy osztály, default exportként ---------- import { keres } from "./termekek.js"; export default class Kosar { #tetelek = []; hozzaad(nev, db = 1) { const termek = keres(nev); if (!termek) throw new Error(`Nincs ilyen termék: ${nev}`); if (termek.keszlet < db) throw new Error(`Nincs elég készlet: ${nev}`); this.#tetelek.push({ nev, ar: termek.ar, db }); } osszeg() { return this.#tetelek.reduce((s, t) => s + t.ar * t.db, 0); } }
// ---------- index.js — a belépési pont: összerakja a részeket ---------- import Kosar from "./kosar.js"; import { termekek } from "./termekek.js"; const kosar = new Kosar(); kosar.hozzaad("Egér", 2); try { kosar.hozzaad("Kábel"); } catch (e) { console.error("Nem került a kosárba:", e.message); } console.log(`${termekek.length} termék, kosár: ${kosar.osszeg()} Ft`);
$ node index.js
Nem került a kosárba: Nincs elég készlet: Kábel
3 termék, kosár: 13980 Ft
Figyeld meg az irányt: az index.js importál a kosar.js-ből, az a termekek.js-ből —
a „mélyebb" modul nem tud a felette lévőkről. Ez a rétegzés teszi a részeket külön-külön érthetővé és cserélhetővé.
Az itteni böngészős gyakorló egyetlen kódrészletet futtat, fájlok nélkül — import-ot és export-ot
ezért nem tud végrehajtani (az élő kód chipjei egy objektummal szimulálják a modult, hogy a gondolat kipróbálható legyen).
A modulos feladatok ezért kéziek: a saját gépeden oldod meg őket.
package.json fájlt ezzel a tartalommal: { "type": "module" }.termekek.js, keszlet.js, index.js).node index.js. Ha „Cannot find module"-t kapsz: hiányzik a ./ vagy a .js.Ha nincs kéznél Node, a RunKit (runkit.com) böngészőben futtat Node-kódot — de az igazi tanulás a saját gépen történik, mert a Node-kurzuson is ott dolgozol majd.
Az Express-kurzus végig ES-modulokat használ: a szerver első sora import express from 'express'.
A projekt rétegekre bomlik, és minden réteg külön modul: a router (routes/termekek.js) csak az URL-eket
ismeri, a controller a kérést dolgozza fel, a service (services/termekService.js)
az üzleti logikát és az adatelérést tartalmazza — az utóbbi tipikusan egy osztály vagy named exportok gyűjteménye, pont mint a fenti
kosar.js. A require-t a régi példakódokban és egyes csomagok dokumentációjában látod majd.
A fejezet feladatai az egész kurzust összefoglalják egy webshop-készletkezelőn. Nincs új nyelvi elem — az a kérdés,
hogy a tanultakat együtt tudod-e használni: destrukturálás egy reduce-ban, saját hiba egy osztály metódusában,
többsoros szöveg map + join-nal. Három csoportra oszlanak:
| Csoport | Feladatok | Mit gyakorol |
|---|---|---|
Kézi (modulos) — a saját gépeden, node-dal | 14.1 Bontsd modulokra! · 14.2 Default export · 14.3 CommonJS-ből ES-modul | export/import, fájlszerkezet, a CJS felismerése |
| Függvények a terméklistán — automatikus ellenőrzés | 14.4 készletérték · 14.5 kifogyottak ábécében · 14.6 keresés a nevekben · 14.7 általános csoportosítás · 14.8 rendelés-összesítő · 14.9 kategória-statisztika · 14.10 kuponos kedvezmény · 14.11 rendelés validálása · 14.13 készletjelentés szövegként | map/filter/reduce/sort, localeCompare, destrukturálás, ??=, Object.entries/fromEntries, switch, throw, saját hibaosztály, template literal + join |
| Osztály — automatikus ellenőrzés | 14.12 Raktar osztály Map-pel | osztály + Map + hibadobás egy helyen |
A függvényes feladatok többsége ugyanazt a közös terméklistát kapja (név, ár, készlet, kategória). Ha elakadsz, a Tipp gomb a megfelelő fejezet eszközét nevezi meg — érdemes előbb visszalapozni, és csak utána a Megoldást megnézni.
Az első két chip a modul gondolatát szimulálja (egy objektum mint „exportált felület", egy függvény-hatókör mint „modul-hatókör");
a többi a záró feladatsor építőköveit mutatja: reduce-os összegzés és csoportosítás, osztály Map-pel,
validálás saját hibával, többsoros jelentés.
Válassz mintát vagy írj sajátot — majd Futtatás (Ctrl+Enter) ↓
Az első három feladat kézi: itt nincs ellenőrzés, a saját gépeden oldod meg, és ha lefutott, a Megoldottam gombbal
pipálod ki. A többi a megszokott módon megy: függvényt (a 14.12-nél osztályt) írsz, az ellenőrző több bemenettel hívja, és a visszatérési
értéket — vagy a dobott hiba üzenetét — hasonlítja a várttal. Figyelj a pontos formátumokra: ábécérend localeCompare-rel,
egészre kerekítés, karakterre egyező szövegek.
⭐ alap · ⭐⭐ haladó · ⭐⭐⭐ kihívás — a haladásod ebben a böngészőben megmarad.
Írd le egy-egy sorban, milyen köztes adat kell (szűrt lista → nevek → rendezve), és minden lépéshez válaszd ki a hozzá illő tömb-metódust. A hibaeseteket guard clause-okkal a függvény elején intézd el, és csak utána jöjjön a „boldog út". Ha egy teszt piros, a „KAPOTT" sor megmutatja, melyik lépés csúszott el.
node fajl.js); a kimenet a console.log.const alapból, let ha változik; szám, szöveg, logikai, null, undefined, objektum; typeof.=== mindig; && / || / !; %; feltételes ? :; truthy/falsy.if / else if / else, switch + break, for / while / for…of; számlálás, összegzés, keresés minta.return, alapértelmezett érték, nyílfüggvény, hatókör; a függvény érték — átadható.length, push / pop / includes / indexOf / slice, bejárás for…of-fal.Object.keys / values / entries, referencia vs másolat.trim / toLowerCase / includes / split / join / padStart, localeCompare...., opcionális láncolás ?., ?? és ??=.map / filter / reduce / find / some / every / sort — láncolva, az eredetit nem módosítva.stringify / parse; Map = szótár, Set = egyedi értékek; hónap 0-tól; Math.round és társai.class, constructor, this, metódus, get / set, static, extends + super, #privát.try / catch / finally, throw new Error, guard clause, saját hibaosztály extends Error, instanceof, a stack trace olvasása.export (named/default) és import ./-tal és .js-sel; saját hatókör; CommonJS require / module.exports felismerése; "type": "module".Végigértél a JavaScript alapjain: a nyelv minden eszközét ismered, amire egy szerveroldali alkalmazás épül. A következő állomás a
Node.js / Express-kurzus, amely pontosan itt veszi fel a fonalat: az első fejezete az aszinkron JavaScript
(Promise, async / await) — ezt szándékosan hagytuk oda, mert ott van rá igazi szükség (fájl, adatbázis, hálózat).
Utána jön a package.json és az npm, az Express-szerver, a route-ok és a middleware-ek, köztük a hibakezelő,
amelyhez a 13. fejezet hibaosztályait már most ismered. Ha valamelyik fejezet feladatai nem mentek simán, most érdemes visszalapozni —
a Node-kurzuson mindez alapnak számít.