Vezérlőpult
7. Statikus fájlok és projektszerkezet · Express alapok

Express alapok · 7. tananyag

Statikus fájlok és projektszerkezet

Két dolog kerül egy fejezetbe, mert mindkettő ugyanarról szól: hol legyen a kód és a tartalom. A második fele a fontosabb — eddig minden az app.js-ben volt, és ez már kezd fájni.

2 tanóraexpress.static()route · controller · service

A fejezet végén tudni fogod

1Statikus fájlok kiszolgálása

import express from "express";
import path from "node:path";

const app = express();

app.use(express.static(path.join(import.meta.dirname, "public")));

Ettől a public mappa tartalma közvetlenül elérhető lesz.

A FÁJLRENDSZERBEN public/ index.html stilus.css kepek/ logo.svg .titok a ponttal kezdődő fájlokat nem adja ki AZ URL-EN a „public” szó SEHOL nem jelenik meg GET / vagy /index.html GET /stilus.css GET /kepek/logo.svg GET /.titok → 404 a nem létező fájlok is 404-et kapnak — a lánc továbbmegy
1. ábra — A mappa neve nem része az URL-nek, a rejtett fájlok pedig nem kerülnek ki.

Az eredmények:

KérésVálaszContent-Type
GET /a public/index.html tartalmatext/html; charset=utf-8
GET /index.htmlugyanaztext/html; charset=utf-8
GET /stilus.cssa public/stilus.csstext/css; charset=utf-8
GET /kepek/logo.svga public/kepek/logo.svgimage/svg+xml
GET /.titok404 — a ponttal kezdődő fájlokat nem adja ki—
GET /nincsilyen.css404 — továbbmegy a láncban—

Amit magától elintéz

Ez a három dolog kézzel is megírható lenne (a 2. fejezetben pont ezt soroltuk fel a hiányzó funkciók közt), de nem kell:

Amit hozzáteszÉrtékMire jó?
MIME-típustext/css, image/svg+xml, text/html…A böngésző tudja, mit kezdjen a fájllal
Last-Modifieda fájl módosítási idejeA böngésző rákérdezhet: „változott azóta?”
Cache-Controlpublic, max-age=0Gyorsítótárazható, de minden használat előtt ellenőrizni kell
MIT JELENT A max-age=0?

Azt, hogy a böngésző eltárolhatja a fájlt, de mielőtt újra használná, meg kell kérdeznie a szervert, változott-e. A szerver a Last-Modified alapján válaszolhat 304 Not Modified kóddal — ilyenkor a fájl nem megy át újra a hálózaton. Fejlesztéskor ez tökéletes: mindig a friss fájlt látod, de nem terheled feleslegesen a hálózatot.

A csapda: a relatív útvonal

Ez a fejezet első felének legfontosabb buktatója. A rövidebb írásmód működni látszik:

app.use(express.static("public"));       // ⚠️ csak néha jó

A gond: az express.static("public") az útvonalat nem a fájlhoz képest értelmezi, hanem ahhoz a mappához, ahonnan a szervert elindítottad (process.cwd()).

app.use("/relativ", express.static("public")) app.use("/abszolut", express.static(path.join(import.meta.dirname, "public"))) A PROJEKT MAPPÁJÁBÓL INDÍTVA cd webshop-api && node index.js process.cwd() = …/webshop-api GET /relativ/stilus.css 200 ✓ GET /abszolut/stilus.css 200 ✓ EGGYEL FELJEBBRŐL INDÍTVA node webshop-api/index.js process.cwd() = a SZÜLŐ mappa GET /relativ/stilus.css 404 ✗ GET /abszolut/stilus.css 200 ✓ Tapasztalat: a relatív útvonal a saját gépeden működik, a szerveren viszont elszáll — ezért mindig abszolút útvonalat adunk meg.
2. ábra — Ugyanaz a kód, két indítási mód. A relatív útvonal az egyikben elhasal.

A megoldás mindig ugyanaz: abszolút útvonal.

import path from "node:path";

app.use(express.static(path.join(import.meta.dirname, "public")));    // ✅ mindig jó
ElemMit ad?
import.meta.dirnameAnnak a fájlnak a mappája, amelyikben a sor áll — sosem változik
path.join(a, b)Összefűzi az útvonalrészeket a rendszernek megfelelő elválasztóval (Windows: \, Linux/macOS: /)
AZ import.meta.dirname ÚJ ÉS KÉNYELMES

Régebbi példákban ezt a két sort látod helyette: import { fileURLToPath } from "node:url" … const __dirname = path.dirname(fileURLToPath(import.meta.url)). Ugyanaz, csak sokkal körülményesebb. A Node 24-ben már nem kell.

MIÉRT NE ÍRJUK BE KÉZZEL A PERJELEKET?

Mert a "public/kepek" írásmód Windowson is működik ugyan, de fordítva ("public\\kepek") Linuxon már nem. A path.join("public", "kepek") mindenhol helyes eredményt ad — és a projektjeink éles környezetben Linuxon futnak majd.

Előtag, több mappa, mappa index nélkül

HelyzetKódEredmény
Előtag alá helyezésapp.use("/eszkozok", express.static(…))A public/stilus.css a /eszkozok/stilus.css címen
Két statikus mappakét app.use(express.static(…)) egymás utánAz első találat nyer — ugyanaz a szabály, mint az útvonalaknál
Mappa index.html nélkülGET /kepek301 átirányítás a /kepek/ alakra, majd 404

Hol a helye a láncban?

app.use(helmet());
app.use(express.json());

app.use(express.static(path.join(import.meta.dirname, "public")));   // ← ide

app.use("/api/products", termekRouter);

app.use(nemTalalhato);

A statikus kiszolgálás az API-útvonalak előtt áll. Így a fájlkérések azonnal kiszolgálódnak, és nem futnak végig feleslegesen az útvonal-kereséssel. Ha nincs egyező fájl, a middleware csendben továbbadja a kérést — ezért nem zavarja az API-t.

A BEÁLLÍTÁSI KULCSOKAT A 6. FEJEZETBEN VETTÜK

index, maxAge, dotfiles, extensions, redirect — ott van mindegyikhez a lehetséges értékek és a magyarázat. Az alapértékek jók.

Egy fájl kiszolgálása: res.sendFile()

app.get("/dokumentacio", (req, res) => {
  res.sendFile(path.join(import.meta.dirname, "docs", "api.html"));
});
A res.sendFile() ABSZOLÚT ÚTVONALAT VÁR

Relatívval hibát dob, hacsak nem adsz meg { root: … } beállítást. Itt is az import.meta.dirname a megoldás.

Milyen URL-en érhető el?

Adott egy express.static() hívás és egy fájl a mappában. Válaszd ki, milyen címen éri el a kliens!

1 / 5Helyes: 0
…

2Miért nem elég egyetlen fájl?

Nézzük meg, mi minden van egy „ömlesztett” végpontban:

app.get("/api/products/:id", (req, res) => {
  const id = Number(req.params.id);                                    // 1) bemenet ellenőrzése
  if (Number.isNaN(id)) return res.status(400).json({ /* … */ });      // 2) HTTP-válasz

  const termek = termekek.find(t => t.id === id);                      // 3) adathozzáférés
  if (!termek) return res.status(404).json({ /* … */ });               // 4) HTTP-válasz

  res.json(termek);                                                    // 5) HTTP-válasz
});

Öt sorban három különböző felelősség keveredik. Ez rövid példán nem gond, de:

ProblémaMikor fáj?
Nem tesztelhető különA terméklogikát csak HTTP-kérésen keresztül tudod kipróbálni
Nem újrafelhasználhatóHa egy másik végpontnak is kell a keresés, másolnod kell
Nehezen cserélhetőHa tömb helyett adatbázis lesz, minden végpontot át kell írni
Nehezen olvashatóÖtszáz sor után nem találod, hol van a „mi az akciós ár” szabály
A HARMADIK SOR A LEGFONTOSABB

A 3. blokkban ugyanezt az API-t Prismára ültetjük át. Ha az adathozzáférés külön rétegben van, csak azt az egy fájlt kell átírni — a végpontok, a státuszkódok, a hibaformátum mind marad.

3A három réteg

A KÉRÉS ERRE HALAD → KÉRÉS GET /api/ products/42 ROUTE termek.routes.js ✓ útvonalminta → vezérlő ✗ nincs benne logika ✗ nem küld választ CONTROLLER termek.controller.js ✓ bemenet beolvasása ✓ HTTP-válasz küldése ✗ nem tud a tárolásról SERVICE termek.service.js ✓ üzleti logika ✓ adatkezelés ✗ NINCS req és res! ← A VÁLASZ VISSZAFELÉ A LEGFONTOSABB SZABÁLY: A SERVICE NEM LÁTJA A HTTP-T Sima függvények, amik adatot kapnak és adatot adnak vissza. Ezért tesztelhetők külön, és ezért cserélhető alattuk az adatforrás.
3. ábra — A három réteg. A service dobozában szándékosan nincs req és res.
RétegFájlMit csinál?Mit NEM csinál?
Routeroutes/termek.routes.jsÖsszeköti az útvonalmintát a vezérlővelNincs benne logika, se válaszküldés
Controller (vezérlő)controllers/termek.controller.jsBeolvassa a bemenetet, hívja a service-t, HTTP-választ küldNem tud az adattárolásról
Service (szolgáltatás)services/termek.service.jsAz üzleti logika és az adatkezelésNem ismeri a req-et és a res-t!

Ugyanaz a végpont ömlesztve és rétegekre bontva

Ömlesztve — egy fájlban3 FELELŐSSÉG
app.get("/api/products/:id", (req, res) => {
  const id = Number(req.params.id);
  if (Number.isNaN(id))
    return res.status(400).json({…});

  const termek = termekek
    .find(t => t.id === id);
  if (!termek)
    return res.status(404).json({…});

  res.json(termek);
});
Rétegekre bontva3 FÁJL
// routes/termek.routes.js
router.get("/:id", vezerlo.egy);

// controllers/termek.controller.js
export function egy(req, res) {
  const id = Number(req.params.id);
  // …ellenőrzés + válasz…
}

// services/termek.service.js
export function termekAzonositoval(id) {
  return termekek.find(t => t.id === id) ?? null;
}

Route — csak összekötés

// src/routes/termek.routes.js
import express from "express";
import * as vezerlo from "../controllers/termek.controller.js";

const router = express.Router();

router.get("/", vezerlo.listaz);
router.get("/:id", vezerlo.egy);
router.post("/", vezerlo.letrehoz);

export default router;

Ez a fájl egy pillantással áttekinthető: azonnal látod, milyen végpontjai vannak az erőforrásnak. Ez önmagában is nagy nyereség.

Controller — a HTTP nyelve

// src/controllers/termek.controller.js
import * as szolgaltatas from "../services/termek.service.js";

export function listaz(req, res) {
  const categoryId = req.query.categoryId ? Number(req.query.categoryId) : undefined;
  res.json(szolgaltatas.osszesTermek({ categoryId }));
}

export function egy(req, res) {
  const id = Number(req.params.id);

  if (Number.isNaN(id)) {
    return res.status(400).json({
      error: { message: "Az azonosító csak szám lehet.", code: "ERVENYTELEN_ID" }
    });
  }

  const termek = szolgaltatas.termekAzonositoval(id);

  if (!termek) {
    return res.status(404).json({
      error: { message: "Nincs ilyen termék.", code: "TERMEK_NEM_TALALHATO" }
    });
  }

  res.json(termek);
}

export function letrehoz(req, res) {
  const termek = szolgaltatas.ujTermek(req.body);
  res.location(`/api/products/${termek.id}`).status(201).json(termek);
}

A vezérlő feladata a fordítás: a HTTP-kérésből kiveszi az adatot, átadja a service-nek, majd az eredményt HTTP-válasszá alakítja — a helyes státuszkóddal.

A res.location(…).status(201).json(…) LÁNCOLHATÓ

Mindhárom metódus a res objektumot adja vissza, ezért egy sorba fűzhetők. Kipróbálva: a válasz 201-es kóddal és Location: /api/products/44 fejléccel érkezik.

Service — az üzleti logika

// src/services/termek.service.js
const termekek = [
  { id: 42, nev: "Billentyűzet", ar: 12990, categoryId: 3 },
  { id: 43, nev: "Egér",         ar: 4990,  categoryId: 3 }
];
let kovetkezoId = 44;

export function osszesTermek({ categoryId } = {}) {
  if (categoryId === undefined) return termekek;
  return termekek.filter(t => t.categoryId === categoryId);
}

export function termekAzonositoval(id) {
  return termekek.find(t => t.id === id) ?? null;
}

export function ujTermek(adat) {
  const termek = { ...adat, id: kovetkezoId++ };   // az id a spread UTÁN áll — lásd lentebb!
  termekek.push(termek);
  return termek;
}

Figyeld meg, mi nincs benne: se req, se res, se státuszkód. Ha nincs találat, null-t ad vissza — hogy abból 404 lesz-e vagy valami más, a vezérlő dolga.

VESZÉLY — A SORREND SZÁMÍT A SPREADNÉL

Az objektumban a később álló mező nyer. Ha { id: kovetkezoId++, ...adat } alakot írnál, és a kliens {"nev":"X","ar":1,"id":9999} törzset küldene, az ...adat felülírná a szerver által adott azonosítót — a termék id-ja 9999 lenne. Ezért áll az id a spread után.

Ez ugyanaz az elv, mint az 1. blokk 1. fejezetében: a klienstől érkező adatot nem hisszük el. A 9. fejezetben ennél is szigorúbb megoldást használunk: ott mezőnként felsoroljuk, mit veszünk át — így az ismeretlen mezők (admin, szerep) be sem kerülnek az adatba.

MIÉRT ?? null ÉS NEM CSAK A find EREDMÉNYE?

A find nem találat esetén undefined-ot ad. A null kifejezett jelzés: „megkerestem, nincs ilyen”. Az undefined inkább azt sugallja: „nem is tudom, mi ez”. Kis különbség, de olvashatóbbá teszi a vezérlőt.

Melyik rétegbe való?

Nyolc kódrészlet a webshop API-ból. Döntsd el mindegyikről, melyik rétegben a helye — ez az a döntés, amit a 9. fejezetben végig alkalmazni fogunk.

1 / 8Helyes: 0
…

4A webshop mappaszerkezete

webshop-api/
  package.json
  .env
  .gitignore
  index.js                        ← a szerver indítása
  public/                         ← statikus fájlok
    index.html
    stilus.css
  src/
    app.js                        ← az alkalmazás felépítése
    config.js                     ← beállítások egy helyen
    routes/
      termek.routes.js
      kategoria.routes.js
    controllers/
      termek.controller.js
      kategoria.controller.js
    services/
      termek.service.js
      kategoria.service.js
    middlewares/
      naplozo.js
      nem-talalhato.js
      hibakezelo.js
    utils/
      valaszok.js                 ← a hiba() segédfüggvény az 5. fejezetből

Az app.js

// src/app.js
import express from "express";
import path from "node:path";
import termekRouter from "./routes/termek.routes.js";

const app = express();

app.use(express.json());
app.use(express.static(path.join(import.meta.dirname, "..", "public")));

app.use("/api/products", termekRouter);

app.use((req, res) => {
  res.status(404).json({ error: { message: "Nincs ilyen végpont.", code: "NINCS_VEGPONT" } });
});

export default app;

A ".." azért kell, mert az app.js az src/ mappában van, a public/ pedig eggyel feljebb.

EZ A TELJES SZERKEZET KIPRÓBÁLVA MŰKÖDIK

A fenti fájlokból felépített API mind a hat vizsgált kérésre helyesen válaszolt: 200 a listára és az egy termékre, 400 a hibás azonosítóra, 404 a nem létezőre, 201 + Location fejléc a létrehozásra, és a statikus stilus.css is kiszolgálódott.

5Egy kérés útja végig a rétegeken

Kövessük a GET /api/products/42 kérést:

GET /api/products/42 2. express.json() — nincs törzs, továbbmegy app.js 3. express.static() — nincs ilyen fájl, továbbmegy app.js 4-5. a /api/products előtag illeszkedik → router.get("/:id") termek.routes.js 6. Number("42") → 42, nem NaN, mehet tovább termek.controller.js 7. termekAzonositoval(42) → megtalálja az objektumot termek.service.js 8. res.json(termek) → 200 termek.controller.js ÉS HA /999? Az 1–6. lépés ugyanaz. A 7. lépés null-t ad… …a 8. pedig 404-et küld, TERMEK_NEM_TALALHATO
4. ábra — Nyolc lépés, öt fájl. A döntés arról, hogy 200 vagy 404 legyen, a vezérlőben születik.
#Hol?Mi történik?
1index.js(már lefutott indításkor) elindította a szervert
2app.jsexpress.json() — nincs törzs, továbbmegy
3app.jsexpress.static() — nincs ilyen fájl, továbbmegy
4app.jsapp.use("/api/products", termekRouter) — illeszkedik, belép a routerbe
5termek.routes.jsrouter.get("/:id") illeszkedik → vezerlo.egy
6termek.controller.jsNumber("42") → 42, nem NaN, mehet tovább
7termek.service.jstermekAzonositoval(42) → megtalálja az objektumot
8termek.controller.jsres.json(termek) → 200

Az eredmény:

GET /api/products/42     →  200  {"id":42,"nev":"Billentyűzet","ar":12990,"categoryId":3}
GET /api/products/alma   →  400  {"error":{"message":"Az azonosító csak szám lehet.", …}}
GET /api/products/999    →  404  {"error":{"message":"Nincs ilyen termék.", …}}

6Elnevezési szokások

Nincs kőbe vésett szabály, de legyél következetes. Amit mi használunk:

ElemÍrásmódPélda
Fájlnévkisbetű, kötőjellel, a szerep a végéntermek.routes.js, nem-talalhato.js
Mappanévkisbetű, többes számroutes/, controllers/, services/
Függvénynévmagyar, beszédes igelistaz, termekAzonositoval
Router-változómindig router a saját fájljábanconst router = express.Router()
MIÉRT termek.routes.js ÉS NEM termekRoutes.js?

Mert a fájlnevek kis- és nagybetű kezelése rendszerfüggő: Windowson mindegy, Linuxon nem. A csupa kisbetűs, kötőjeles vagy pontos írásmóddal soha nem futsz bele abba, hogy a saját gépeden működik, a szerveren nem.

Import: névvel vagy csillaggal?

import * as vezerlo from "../controllers/termek.controller.js";   // ✅ így írjuk
router.get("/:id", vezerlo.egy);

import { egy, listaz } from "../controllers/termek.controller.js"; // ugyanaz, másképp
router.get("/:id", egy);

Mindkettő helyes. Mi a csillagosat használjuk, mert a hívás helyén látszik, honnan jön a függvény (vezerlo.egy, szolgaltatas.osszesTermek) — ez hosszabb fájlokban sokat segít.

7Konfiguráció külön fájlba

A 3. fejezetben a portot már környezeti változóból vettük. Ha több ilyen beállítás lesz, érdemes egy helyre gyűjteni:

// src/config.js
export const config = {
  port:        Number(process.env.PORT) || 3000,
  kornyezet:   process.env.NODE_ENV || "development",
  oldalMeret:  Number(process.env.OLDAL_MERET) || 20,
  torzsMeret:  process.env.TORZS_MERET || "100kb"
};
// index.js
import app from "./src/app.js";
import { config } from "./src/config.js";

app.listen(config.port, () => {
  console.log(`A szerver fut (${config.kornyezet}): http://localhost:${config.port}`);
});
ElőnyMagyarázat
Egy helyen látodNem kell process.env-et vadászni tíz fájlban
Az átalakítás egyszer történikA Number(…) konverzió és az alapérték itt van, nem szétszórva
Könnyen dokumentálhatóA .env.example fájl ugyanezeket a kulcsokat sorolja fel
A .env FÁJL SOHA NEM MEGY VERZIÓKEZELŐBE

(1. blokk, 3. fejezet.) Helyette egy .env.example fájlt teszünk mellé, üres vagy példaértékekkel — abból tudja a munkatársad, mit kell beállítania.

8Gyakori hibák

A statikus fájl 404-es, pedig ott van

app.use(express.static("public"));    // ❌ a CWD-hez képest keresi

Tünet: a saját gépeden működik, a szerveren vagy más indítási módnál nem.
Megoldás: path.join(import.meta.dirname, "public").

res.sendFile() hibát dob

Tünet: TypeError: path must be absolute or specify root to res.sendFile.
Megoldás: res.sendFile(path.join(import.meta.dirname, "docs", "api.html")).

A statikus mappa „elnyeli” az API-t

Ha véletlenül van egy public/api mappa, akkor a /api/… kérések oda futnak be. Ritka, de alattomos.
Megoldás: a statikus tartalmat tedd előtag alá (app.use("/eszkozok", …)), vagy ügyelj rá, hogy a public mappában ne legyen az API-útvonalakkal ütköző név.

Körkörös import

// termek.service.js   →  importálja a controllert
// termek.controller.js →  importálja a service-t

Tünet: valamelyik import undefined lesz, és hívásnál TypeError: … is not a function.
Megoldás: a rétegek csak lefelé hivatkoznak: route → controller → service. Visszafelé soha. Ha mégis kellene, az azt jelzi, hogy rossz rétegbe került egy függvény.

Cannot find module './routes/termek.routes'

Tünet: hiányzik a .js kiterjesztés az importból.
Megoldás: ESM-ben kötelező kiírni: from "./routes/termek.routes.js".

Az útvonalminta kétszer szerepel

app.use("/api/products", termekRouter);
router.get("/api/products/:id", …);      // ❌

A 4. fejezetből ismerős: a routerben relatív útvonal áll ("/:id").

9Feladatok

Elméleti

F1⭐Melyik rétegbe való?

Sorold be mindegyiket route, controller vagy service rétegbe:

  • a) res.status(404).json(…)
  • b) egy termék keresése azonosító alapján
  • c) router.get("/:id", …)
  • d) Number(req.params.id) átalakítás
  • e) az akciós ár kiszámítása
  • f) a Location fejléc beállítása
F2⭐Miért 404?

Egy munkatársad ezt írta, és nála működik, a szerveren nem. Miért?

app.use(express.static("public"));
F3⭐⭐Mi hiányzik a service-ből?

Miért hibás ez a függvény, és hogyan javítanád?

// termek.service.js
export function termekAzonositoval(req, res) {
  const termek = termekek.find(t => t.id === Number(req.params.id));
  if (!termek) return res.status(404).json({ error: { message: "Nincs ilyen." } });
  return termek;
}
F4⭐⭐Kövesd a kérést!

Írd le lépésről lépésre, mi történik egy POST /api/products kéréssel a 4. szakasz szerkezetében, a szerver belépésétől a válaszig. Nevezd meg minden lépésnél a fájlt is!

F5⭐⭐⭐Tervezz réteget

A webshopba jön egy új szabály: 20 000 Ft fölött ingyenes a szállítás. Melyik rétegbe kerül ez a szabály, és miért? Mi történne, ha a vezérlőbe írnád? Mi történne, ha a kliensoldali kódba?

Gyakorlati

G1⭐Statikus kezdőlap

Készíts egy public/index.html fájlt, amely felsorolja az API végpontjait, és szolgáld ki az express.static() middleware-rel. Ellenőrizd, hogy a / címen megjelenik!

G2⭐⭐Bizonyítsd be a csapdát

Írd meg mindkét változatot ("public" és path.join(import.meta.dirname, "public")) külön előtag alá, majd indítsd a szervert egyszer a projekt mappájából, egyszer eggyel feljebbről. Készíts táblázatot a négy eredményről!

G3⭐⭐Rétegekre bontás

Vedd az 5. fejezet POST /api/products végpontját, és bontsd szét route, controller és service fájlokra a 4. szakasz szerkezete szerint. A service-ben ne legyen se req, se res!

G4⭐⭐Második erőforrás

Készítsd el ugyanígy a kategóriákat: kategoria.routes.js, kategoria.controller.js, kategoria.service.js, végpontok: GET /api/categories, GET /api/categories/:id.

G5⭐⭐⭐Konfiguráció

Emeld ki a portot, az alapértelmezett oldalméretet és a törzs méretkorlátját egy config.js fájlba, és használd őket a megfelelő helyeken. Készíts hozzá .env.example fájlt, és bizonyítsd be, hogy a .env átírása tényleg hat a működésre!

10Önellenőrző kvíz

0 / 10 megválaszolva

11Összegzés

  1. Az express.static() egyetlen sorral kiszolgál egy egész mappát, és magától beállítja a MIME-típust, a Last-Modified és a Cache-Control fejlécet.
  2. A mappa neve nem része az URL-nek, és a ponttal kezdődő fájlokat (.env) alapból nem adja ki.
  3. Mindig abszolút útvonalat adj meg: path.join(import.meta.dirname, "public"). A relatív írásmód az indítási mappához képest keres, és más indítási módnál 404-et ad.
  4. A statikus kiszolgálás az API-útvonalak előtt áll a láncban; ha nincs egyező fájl, csendben továbbadja a kérést.
  5. Az „ömlesztett” végpontban három felelősség keveredik. A megoldás a rétegekre bontás: route → controller → service.
  6. A service nem ismeri a HTTP-t: nincs benne req, res és státuszkód. Ezért tesztelhető külön, és ezért cserélhető alatta az adatforrás.
  7. A controller fordít: kéréssé alakítja a bemenetet, válasszá az eredményt — a helyes státuszkóddal és hibaformátummal.
  8. A rétegek csak lefelé hivatkoznak. Visszafelé mutató import → körkörös hivatkozás → hiba.
  9. A konfiguráció egy fájlba kerül (config.js), a titkok pedig a .env-be, ami soha nem megy verziókezelőbe.

12Fogalomtár

FogalomJelentés
Statikus fájlVáltozatlan tartalom, amit a szerver egy az egyben visszaad (kép, CSS, HTML)
express.static()Beépített middleware statikus fájlok kiszolgálására
import.meta.dirnameAnnak a fájlnak a mappája, amelyikben a sor áll
path.join()Útvonalrészek összefűzése a rendszernek megfelelő elválasztóval
process.cwd()Az a mappa, ahonnan a programot elindították
res.sendFile()Egyetlen fájl kiszolgálása; abszolút útvonalat vár
Réteg (layer)A kód egy jól elhatárolt felelősségi köre
Route-rétegAz útvonalmintát köti a vezérlőhöz
Controller (vezérlő)A HTTP-kérésből adatot vesz, és HTTP-választ küld
Service (szolgáltatás)Az üzleti logika és az adatkezelés — HTTP nélkül
Körkörös importKét modul kölcsönösen importálja egymást — kerülendő
.env.exampleA .env fájl mintája, ami mehet verziókezelőbe
← Előző 6. Middleware