Express alapok · 6. tananyag
Már használtuk, csak nem neveztük néven: az express.json()
is middleware. Most jön a rendszer mögé. A middleware az Express szíve — és egyetlen szabályból
fakad szinte minden buktatója: a sorrend számít.
next(), válaszküldés,
next(hiba);req objektumot kiegészítőt, ellenőrzőt;express.json(),
express.urlencoded(), express.static()) és ismerni
a beállításaikat;next(), rossz helyen álló
express.json(), rossz helyen álló hibakezelő.Az eddigi fejezetekben már használtunk middleware-t, csak nem neveztük néven:
app.use(express.json()) — beolvassa a kérés törzsét (5. fejezet);app.delete("/api/products/:id", bejelentkezveKell, kezelo)
első függvénye;A middleware egy függvény, amely a kérés és a válasz között fut le. Három paramétert kap:
function naplozo(req, res, next) { console.log(`${req.method} ${req.originalUrl}`); next(); // ← továbbadjuk a következőnek } app.use(naplozo);
| Paraméter | Mi ez? |
|---|---|
req | A kérés — ugyanaz, amit a kezelőfüggvényben látsz, és módosíthatod |
res | A válasz — ezzel akár le is zárhatod a kérést |
next | Egy függvény, amit meghívva továbbadod a vezérlést a láncban következőnek |
A next a lényeg. Enélkül a middleware nem különbözne egy sima
kezelőfüggvénytől.
Igazából ugyanaz. Az egyetlen különbség a szándék:
| Middleware | Kezelőfüggvény (route handler) | |
|---|---|---|
| Mit csinál? | Előkészít, ellenőriz, naplóz | Válaszol a kérésre |
| Hogyan végződik? | next() hívással | Válaszküldéssel (res.json()…) |
| Hol áll? | A lánc elején (vagy középen) | A lánc végén |
Ezért írhattuk a 4. fejezetben, hogy az útvonalnak több függvénye is lehet: az első néhány middleware-ként viselkedik, az utolsó válaszol.
Az Express minden kérésnél sorban végigmegy a regisztrált függvényeken, felülről lefelé:
const app = express(); app.use(morgan("dev")); // 1. naplóz, majd next() app.use(express.json()); // 2. beolvassa a törzset, majd next() app.use(hitelesites); // 3. ellenőriz, majd next() vagy hibát ad app.get("/api/products", kezelo); // 4. VÁLASZOL — itt megáll a lánc app.use(nemTalalhato); // 5. csak akkor fut le, ha semmi nem válaszolt app.use(hibakezelo); // 6. csak hiba esetén fut le
next(hiba) pedig átugrik a hibakezelőhöz.A nyomvonal (egy naplózó middleware jegyezte fel a lépéseket):
GET /api/x → ["1 globális", "2 csak /api alatt", "3 útvonal-middleware", "4 kezelő"]
GET /nincs → ["1 globális", "404-kezelő"]
Figyeld meg a második sort: a /nincs kérésnél a globális middleware
lefutott, csak nem talált végpontot, ezért a lánc végén álló 404-kezelőhöz jutott.
Adott egy lánc abban a sorrendben, ahogy a kódban áll, és egy beérkező kérés. Válaszd ki, melyik függvény fut le elsőként, illetve melyik utolsóként — a kérdés mindig megmondja, melyik érdekel.
Minden middleware pontosan egyféleképpen fejeződhet be. Ha egyiket sem teszi meg, a kérés örökre lóg.
next() — továbbadásapp.use((req, res, next) => { req.kertIdo = new Date(); // hozzáteszünk valamit a kéréshez next(); // és megyünk tovább });
app.use((req, res, next) => { if (!req.get("Authorization")) { return res.status(401).json({ error: { message: "Bejelentkezés szükséges." } }); } next(); });
Figyeld meg a return-t: enélkül a next() is lefutna a
válasz után. Ugyanaz a szabály, mint az 5. fejezetben — egy kérésre egy válasz.
next(hiba) — ugrás a hibakezelőhözapp.use((req, res, next) => { const id = Number(req.query.id); if (Number.isNaN(id)) { return next(new Error("Az azonosító nem szám.")); } next(); });
Az eredmény: a next(new Error("kézzel dobott hiba")) hívás után
a lánc átugorja az összes közbeeső middleware-t és útvonalat, és egyenesen a
négyparaméteres hibakezelőhöz megy — onnan 500-as válasz születik.
next() ÉS A next(hiba) KÜLÖNBSÉGE MINDÖSSZE EGY PARAMÉTER
Ha a next bármilyen értéket kap (a "route" szó
kivételével), az Express hibának tekinti. Ezért soha ne írj ilyet:
next(termek) — abból hiba lesz.
app.get("/lefagy", (req, res, next) => { console.log("ide eljutok"); // nincs next(), nincs válasz });
Kipróbálva: a kérés időtúllépésig lóg, és nincs hibaüzenet. A böngésző pörög, a terminál néma. Ez a middleware-ek leggyakoribb, legnehezebben megtalálható hibája.
next() HÍVÁST
Minden ágon vagy next()-nek, vagy válaszküldésnek kell lennie — a korai
kilépéseknél is.
// 1) GLOBÁLIS — minden kérésre app.use(naplozo); // 2) ELŐTAGHOZ KÖTÖTT — csak a /api alatt app.use("/api", hitelesites); // 3) ÚTVONAL-SZINTŰ — csak ezen az egy végponton app.delete("/api/products/:id", adminKell, torloKezelo); // 4) ROUTEREN BELÜLI — a router minden útvonalára const router = express.Router(); router.use(termekLetezik);
| Hatókör | Írásmód | Mikor használd? |
|---|---|---|
| Globális | app.use(fv) | Naplózás, törzs-feldolgozás, biztonsági fejlécek |
| Előtaghoz kötött | app.use(elotag, fv) | „Az egész /api mögé bejelentkezés kell” |
| Útvonal-szintű | app.get(minta, fv1, fv2) | Egyetlen végpont extra ellenőrzése |
| Routeren belüli | router.use(fv) | Egy erőforrás összes végpontjára |
req.url-rőlHa előtaghoz kötött middleware-t vagy routert használsz, a middleware nem látja az előtagot:
app.use("/elotag", (req, res) => { res.json({ url: req.url, baseUrl: req.baseUrl, originalUrl: req.originalUrl }); });
Az eredmény a GET /elotag/melyebb/utvonal kérésre:
{ "url": "/melyebb/utvonal", "baseUrl": "/elotag", "originalUrl": "/elotag/melyebb/utvonal" }
Ez ugyanaz a viselkedés, amit a 4. fejezetben a routereknél láttunk — és pontosan ezért működik a router relatív útvonalakkal.
req.originalUrl
A req.url egy előtaghoz kötött middleware-ben csonkolt, és félrevezető
naplósorokat adna.
Az Express hármat hoz magával. Külön csomag egyikhez sem kell.
| Middleware | Mit csinál? | Hol vettük? |
|---|---|---|
express.json() | A JSON törzset dolgozza fel a req.body-ba | 5. fejezet |
express.urlencoded() | Ugyanez űrlapadathoz | 5. fejezet |
express.static() | Fájlokat szolgál ki egy mappából | itt és a 7. fejezetben |
express.static()app.use(express.static("public"));
Ettől a public/proba.txt fájl a GET /proba.txt címen
érhető el (kipróbálva). Vedd észre: a mappa neve nem része az URL-nek.
Ha mégis kell előtag:
app.use("/statikus", express.static("public")); // → GET /statikus/proba.txt
| Kulcs | Lehetséges értékek | Alapérték | Mit csinál? |
|---|---|---|---|
index | fájlnév vagy false | "index.html" | Melyik fájlt adja vissza, ha mappát kérnek. false esetén semmit |
maxAge | ezredmásodperc vagy "1d", "1h" | 0 | Meddig gyorsítótárazhatja a böngésző (Cache-Control fejléc) |
dotfiles | "allow" / "deny" / "ignore" | "ignore" | Mi legyen a ponttal kezdődő fájlokkal (pl. .env). Az "ignore" úgy tesz, mintha nem lennének |
extensions | kiterjesztések tömbje | false | Ha nincs találat, kipróbálja ezekkel is (pl. ["html"] → a /rolunk megtalálja a rolunk.html-t) |
redirect | true / false | true | Mappa kérésénél tegyen-e záró perjelet a végére |
dotfiles ALAPÉRTÉKE JÓ
Az "ignore" miatt a public/.env fájl
nem kerül ki a hálózatra. Ezt soha ne állítsd "allow"-ra.
A fenti express.static("public") írásmódnak van egy csapdája: az útvonalat
nem a fájlhoz, hanem az indítási mappához képest értelmezi. A következő fejezetben
megnézzük, miért, és hogyan kerüljük el.
export function naplozo(req, res, next) { const kezdet = Date.now(); res.on("finish", () => { // amikor a válasz elment const ido = Date.now() - kezdet; console.log(`${req.method} ${req.originalUrl} ${res.statusCode} — ${ido} ms`); }); next(); }
| Elem | Mit csinál? |
|---|---|
Date.now() | Az aktuális idő ezredmásodpercben — a kezdet és a vég különbsége adja az időtartamot |
res.on("finish", fv) | A válasz elküldése után hívja meg a függvényt — csak ekkor ismert a státuszkód |
next() a végén | Enélkül a kérés soha nem jutna el a végponthoz |
A next() azonnal lefut, nem várja meg a
finish eseményt. A naplósor később, a válasz után íródik ki.
req kiegészítéseapp.use((req, res, next) => { req.kertIdo = new Date().toISOString(); next(); }); app.get("/api/idopont", (req, res) => { res.json({ mikor: req.kertIdo }); // működik });
Ez a minta lesz a hitelesítés alapja a 4. blokkban: a token ellenőrzése után a middleware
req.felhasznalo = { id: 5, szerep: "admin" } alakban teszi be a bejelentkezett
felhasználót.
A req.adat vagy a req.data könnyen ütközhet egy
külső csomag által használt névvel. A req.kertIdo,
req.felhasznalo sokkal biztonságosabb.
res.locals — adat a válaszhozapp.use((req, res, next) => { res.locals.valaszIdo = new Date().toISOString(); next(); });
A res.locals egy objektum, amely egyetlen kérés-válasz párhoz
tartozik. Példa:
{ "kertIdo": "2026-08-29T12:00:00.000Z", "locals": { "felhasznalo": "teszt" } }
req.sajatMezo | res.locals | |
|---|---|---|
| Mire szánták? | A kérésről szóló adat (ki küldte, mikor) | A válaszhoz szánt adat |
| Kik látják? | A későbbi middleware-ek és a kezelő | Ugyanazok, plusz a sablonmotorok |
| Melyiket használjuk? | Ezt — mi nem használunk sablonmotort | Ritkábban |
export function ervenyesAzonosito(req, res, next) { const id = Number(req.params.id); if (!Number.isInteger(id) || id < 1) { return res.status(400).json({ error: { message: "Az azonosító csak pozitív egész szám lehet.", code: "ERVENYTELEN_ID" } }); } req.termekId = id; // átadjuk a már átalakított értéket next(); }
router.get("/:id", ervenyesAzonosito, (req, res) => { const termek = termekek.find(t => t.id === req.termekId); // már szám! // … });
Ezzel a 4. fejezet buktatóját egyszer és mindenkorra megoldottuk: a kezelőfüggvényben már számot kapunk, és a hibás azonosító ide sem jut el.
Mindhárom ingyenes, MIT-licencű npm-csomag, teljes funkcionalitással.
npm install morgan cors helmet
Ellenőrzött verziók: morgan 1.12.0, cors 2.8.6, helmet 8.3.0.
import morgan from "morgan"; app.use(morgan("dev"));
Kattints a kérésekre, és nézd, hogyan gyűlnek a naplósorok. Ez az egyetlen hely a fejezetben, ahol terminált szimulálunk — a napló időben bővülő kimenet, statikus kódblokk nem adja vissza jól.
A morgan „dev” formátuma: metódus · útvonal · státuszkód · idő · válaszméret.
| Rész | Mit jelent? |
|---|---|
GET /locals | Metódus és útvonal |
200 | Státuszkód — színesen: zöld 2xx, sárga 4xx, piros 5xx |
4.923 ms | Mennyi ideig tartott a kérés kiszolgálása |
- 71 | A válasz törzsének mérete bájtban |
| Formátum | Mit ad? | Mikor használd? |
|---|---|---|
"dev" | Rövid, színes sor | Fejlesztés közben — ezt használjuk |
"tiny" | A legrövidebb, szín nélkül | Ha sok kérés van, és csak áttekintés kell |
"common" | Szabványos webszerver-naplóformátum | Ha naplóelemző eszközbe megy |
"combined" | Mint a common, plusz hivatkozó és böngésző | Éles rendszerben |
import cors from "cors"; app.use(cors());
Ez hozzáadja az Access-Control-Allow-Origin: * fejlécet, vagyis
bármelyik weboldal hívhatja az API-dat.
Cross-Origin Resource Sharing — böngészőbeli biztonsági szabály. A böngésző alapból
megtiltja, hogy a http://localhost:5173 címen futó frontend
adatot kérjen a http://localhost:3000 címen futó API-tól, mert más a
forrás (origin: séma + host + port). A szervernek kell engedélyt adnia — ezt teszi ez a
middleware.
| Kulcs | Lehetséges értékek | Alapérték | Mit csinál? |
|---|---|---|---|
origin | "*", konkrét cím, címek tömbje, vagy függvény | "*" | Ki hívhatja az API-t |
methods | metódusnevek tömbje vagy szövege | "GET,HEAD,PUT,PATCH,POST,DELETE" | Mely metódusok engedélyezettek |
allowedHeaders | fejlécnevek tömbje | a kliens kérése szerint | Mely fejléceket küldhet a kliens (pl. Authorization) |
credentials | true / false | false | Küldhet-e a böngésző sütiket a kéréssel |
Éles rendszerben ne hagyd "*"-on:
app.use(cors({ origin: "https://webshop.example.com" }));
import helmet from "helmet"; app.use(helmet());
Egyetlen sorral tizenegy biztonsági fejlécet állít be. A lista (helmet 8.3.0):
| Fejléc | Értéke | Mi ellen véd? |
|---|---|---|
Content-Security-Policy | default-src 'self'; … | Idegen forrásból betöltött szkriptek |
Cross-Origin-Opener-Policy | same-origin | Más lapok hozzáférése az ablakodhoz |
Cross-Origin-Resource-Policy | same-origin | Az erőforrásaid beágyazása idegen oldalba |
Origin-Agent-Cluster | ?1 | Más oldalakkal közös memóriakezelés |
Referrer-Policy | no-referrer | Az URL kiszivárgása a hivatkozott oldalnak |
X-Content-Type-Options | nosniff | A böngésző „kitalálja” a fájltípust |
X-DNS-Prefetch-Control | off | Előzetes DNS-lekérdezés, ami adatot szivárogtat |
X-Download-Options | noopen | Régi Internet Explorer letöltési viselkedése |
X-Frame-Options | SAMEORIGIN | Az oldalad idegen keretbe ágyazása (clickjacking) |
X-Permitted-Cross-Domain-Policies | none | Régi Flash- és PDF-kliensek hozzáférése |
X-XSS-Protection | 0 | Szándékosan kikapcsolva — a régi böngészőszűrő maga volt biztonsági rés |
helmet() ALAPBEÁLLÍTÁSA JÓ
A Content-Security-Policy főleg HTML-oldalaknál számít; JSON-válaszoknál nem
zavar. Ha statikus HTML-t is kiszolgálsz, és a lapod nem tölt be semmit idegen forrásból, azt érdemes
külön beállítani — de ez már túlmutat a mi tananyagunkon.
const app = express(); app.use(helmet()); // 1. biztonsági fejlécek — mindenre app.use(cors()); // 2. CORS-fejlécek app.use(morgan("dev")); // 3. naplózás app.use(express.json()); // 4. törzs-feldolgozás app.use("/api/products", termekRouter); // 5. útvonalak app.use(nemTalalhato); // 6. 404 app.use(hibakezelo); // 7. hibakezelő — MINDIG a legvégén
Hat middleware. Döntsd el mindegyikről, a lánc melyik szakaszába való.
Az Express egyetlen dolog alapján ismeri fel: négy paramétere van, és az első a hiba.
app.use((err, req, res, next) => { // ← NÉGY paraméter! console.error(err); res.status(err.status || 500).json({ error: { message: err.message, code: "SZERVERHIBA" } }); });
| Paraméter | Mi ez? |
|---|---|
err | A hiba, ami a next(err) hívásból vagy egy throw-ból származik |
req, res | A szokásos kérés és válasz |
next | Továbbadás egy másik hibakezelőnek — ritkán kell |
Ha lehagyod a next-et, az Express háromparaméteres,
közönséges middleware-nek nézi, és soha nem adja át neki a hibákat. A szerkesztő
figyelmeztethet a nem használt paraméterre — itt ez rendben van.
Ez a fejezet második nagy buktatója. Kipróbálva mindkét sorrend:
app.use((err, q, s, n) => …); // ❌ app.get("/hiba", () => { throw … });
500 <!DOCTYPE html> … <pre>Error: bumm at …</pre>
app.get("/hiba", () => { throw … }); app.use((err, q, s, n) => …); // ✅
500 {"hol":"helyes hibakezelo","uzenet":"bumm"}
Miért? Mert a hibakezelő is a láncban van. Ha az útvonalak előtt regisztrálod, a hiba később keletkezik, mint ahol ő áll — és a lánc nem megy visszafelé.
Egyedi hibaosztályok, státuszkódok, éles környezet, aszinkron hibák — mindez a következő utáni fejezet témája. Itt csak a szerkezetet (négy paraméter) és a helyét (a lánc legvégén) kellett megérteni.
app.use((req, res, next) => { console.log("naplózás"); // ❌ elmaradt a next() });
Tünet: minden kérés időtúllépésre fut, a terminál néma.
Megoldás: minden ágon next() vagy válaszküldés.
req.body az undefined — pedig ott az express.json()app.post("/korai", kezelo); // ❌ ez ELŐBB van app.use(express.json()); app.post("/kesoi", kezelo); // ✅ ez már látja a törzset
Az eredmény:
POST /korai → {"tipus":"undefined"}
POST /kesoi → {"body":{"a":1},"tipus":"object"}
Megoldás: az express.json() a fájl elejére, minden útvonal elé.
Tünet: a hibaválasz HTML, nem a saját JSON-formátumod.
Ok: a hibakezelő az útvonalak előtt áll, vagy csak három paramétere van.
Megoldás: négy paraméter, és a lánc legvégére.
next() a válasz utánapp.get("/utana", (req, res, next) => { res.json({ elso: true }); next(); // ❌ });
Kipróbálva: a kliens megkapja az első választ (200), de a lánc
továbbmegy a 404-kezelőhöz, ami újra válaszolni próbál — a terminálban
ERR_HTTP_ jelenik meg. A szerver fut tovább, tehát
semmi nem hívja fel rá a figyelmedet.
Megoldás: return res.json(…).
app.use("/api", …)app.use("/api", hitelesites); app.get("/api/products", kezelo); // ✅ ide kell a hitelesítés app.get("/allapot", kezelo); // ✅ ide nem — jó, hogy kimarad
Ha az előtagot lehagyod, a /allapot végpont is bejelentkezést követelne —
beleértve azt az állapot-ellenőrző végpontot, aminek épp bejelentkezés nélkül kell működnie.
next(valami) hibának minősülnext(termek); // ❌ ebből hiba lesz, nem adatátadás!
Megoldás: adatot a req objektumon adj tovább
(req.termek = termek; next();).
Elméleti
Sorold fel a middleware háromféle befejezését, és mondj mindegyikre egy konkrét példát a webshop API-ból!
Adott ez a sorrend. Írd le, mely függvények futnak le a GET /api/products
és mely a GET /allapot kérésre!
app.use(naplozo); app.use("/api", hitelesites); app.get("/allapot", allapotKezelo); app.get("/api/products", termekKezelo); app.use(nemTalalhato);
Miért undefined a req.body?
const app = express(); app.post("/api/products", (req, res) => res.json(req.body)); app.use(express.json()); app.listen(3000);
Mi a hiba, és mi a tünete?
app.use((req, res, next) => { if (req.get("Authorization")) { next(); } });
Írd le a webshop API teljes middleware-láncát a helyes sorrendben, a biztonsági fejlécektől a hibakezelőig. Mindegyik sorhoz írj egy mondatot arról, miért pont ott van a helye!
Gyakorlati
Írj middleware-t, amely minden kérésnél kiírja a metódust, a teljes útvonalat, a státuszkódot és a
kiszolgálás idejét. Hasonlítsd össze a morgan("dev") kimenetével!
Írj middleware-t, amely minden kéréshez egyedi azonosítót rendel
(req.kerelemAzonosito), beteszi a válasz X-Request-Id
fejlécébe, és a naplósorban is megjeleníti. Az azonosítót a beépített
crypto.randomUUID() függvénnyel állítsd elő!
Emeld ki a 4. fejezet azonosító-ellenőrzését külön middleware-be
(ervenyesAzonosito), és használd mind a GET /:id,
PUT /:id, DELETE /:id végpontban. Bizonyítsd be,
hogy a hibás azonosító már a kezelőfüggvény előtt megáll!
Telepítsd és állítsd be a morgan, cors és
helmet csomagokat. REST Clientben nézd meg a válasz fejléceit
előtte és utána, és készíts táblázatot arról, mi változott!
Írj middleware-t, amely megköveteli az
Authorization: Bearer titok123 fejlécet minden
/api alatti kérésnél. Hiányzó fejléc → 401,
rossz érték → 403, egységes hibaformátumban. Az
/allapot végpont maradjon szabadon elérhető! (A valódi JWT-alapú megoldás a
4. blokkban jön.)
(req, res, next). A kezelőfüggvénytől csak a szándék különbözteti meg.next() (tovább), válaszküldés
(megáll), next(hiba) (ugrás a hibakezelőhöz). Ha egyik sem történik meg,
a kérés örökre lóg — hibaüzenet nélkül.req.url-ről.express.json(),
express.urlencoded(),
express.static() — mindhez tartoznak beállítások, de az
alapértékek jók.req objektumon
adunk tovább adatot (req.termekId, később
req.felhasznalo).| Fogalom | Jelentés |
|---|---|
| Middleware | A kérés és a válasz között lefutó függvény, (req, res, next) paraméterekkel |
| Lánc (stack) | A regisztrált middleware-ek és útvonalak sorrendje |
next() | Továbbadja a vezérlést a láncban következőnek |
next(hiba) | Átugrik a hibakezelőhöz |
next("route") | Kihagyja az adott útvonal többi függvényét, és a következő illeszkedő útvonalra ugrik |
| Hibakezelő middleware | Négy paraméteres függvény: (err, req, res, next) |
app.use(fv) | Globális middleware |
app.use(előtag, fv) | Csak az előtag alá eső kérésekre |
req.originalUrl | A teljes kért útvonal — naplózáshoz ezt használjuk |
res.locals | Egyetlen kérés-válasz párhoz tartozó adatok objektuma |
express.static() | Beépített middleware fájlok kiszolgálására |
| morgan | Naplózó middleware-csomag |
| CORS | Cross-Origin Resource Sharing — böngészőbeli szabály a más forrásból érkező kérésekre |
| helmet | Biztonsági HTTP-fejléceket beállító csomag |