Files
GKNB_MSTM071/Backend/elso gyakorlat/FELADAT_LEIRAS_USER_MANAGEMENT.md
T
2026-02-13 19:14:10 +01:00

44 KiB

User Management Rendszer - Feladat Leírás

Projekt Áttekintés

Egy User Management (felhasználó kezelési) rendszer elkészítése Node.js és Express.js használatával, Clean Architecture elveket követve, CQRS (Command Query Responsibility Segregation) pattern alkalmazásával.

Célkitűzés

Egy RESTful API létrehozása felhasználók kezeléséhez (CRUD műveletek), amely követi a modern szoftver architektúra elveket és jól strukturált, karbantartható kódot eredményez.


Technológiai Stack

Kötelező Technológiák

  • Node.js (v18 vagy újabb)
  • Express.js (v5.2.1 vagy újabb) - Web framework
  • ES6 Modules - import/export szintaxis használata
  • JSON File Storage - Adattárolás fájl alapú megoldással (későbbi adatbázis integrációhoz előkészítve)

Fejlesztői Eszközök

  • nodemon - Automatikus újraindítás fejlesztés közben

Architektúra Áttekintés

A projekt 4 fő réteget tartalmaz:

src/
├── API/                    # Prezentációs réteg (Web API)
├── Application/            # Üzleti logika réteg (CQRS)
├── Domain/                 # Domain modell és interfészek
└── Infrastructure/         # Adatelérési réteg és külső szolgáltatások

Rétegek Részletes Leírása

1. Domain Layer (Domain réteg)

  • Felelősség: Az alkalmazás magját képező interfészek és domain modellek
  • Tartalma: Repository interfészek (IUserRepository)
  • Függőség: Nincs más rétegre való függősége

2. Infrastructure Layer (Infrastruktúra réteg)

  • Felelősség: Technikai implementációk (adatbázis, fájlkezelés, külső API-k)
  • Tartalma: Repository implementációk, adattárolás
  • Függőség: Domain layer-től függ (implementálja az interfészeket)

3. Application Layer (Alkalmazás réteg)

  • Felelősség: Üzleti logika, use case-ek implementálása
  • Tartalma: Commands, CommandHandlers, Queries, QueryHandlers
  • Függőség: Domain layer-től függ
  • CQRS Pattern: Írás (Command) és olvasás (Query) műveletek szétválasztása

4. API Layer (API réteg)

  • Felelősség: HTTP kérések kezelése, routing, válaszok küldése
  • Tartalma: Controllers, Routers, Server konfiguráció
  • Függőség: Application és Infrastructure layer-től függ

CQRS Pattern Magyarázat

Mi az a CQRS?

Command Query Responsibility Segregation - Az írási és olvasási műveletek szétválasztása.

Command (Parancs) - Írási műveletek

  • Megváltoztatja a rendszer állapotát
  • Nem ad vissza értéket (vagy csak a létrehozott entitást)
  • Példák: CreateUser, UpdateUser, DeleteUser

Struktúra:

  1. Command osztály - Tartalmazza a parancs adatait
  2. CommandHandler osztály - Végrehajtja a parancsot, validálja az adatokat

Query (Lekérdezés) - Olvasási műveletek

  • Nem változtatja meg a rendszer állapotát
  • Adatokat ad vissza
  • Példák: GetAllUsers, GetUserById

Struktúra:

  1. Query osztály - Tartalmazza a lekérdezés paramétereit
  2. QueryHandler osztály - Végrehajtja a lekérdezést

Részletes Implementációs Útmutató

1. Projekt Inicializálás

1.1 Projekt struktúra létrehozása

Backend/
└── elso gyakorlat/
    ├── package.json
    └── src/
        ├── API/
        │   ├── server.js
        │   ├── controllers/
        │   │   └── userController.js
        │   └── routers/
        │       └── userRouter.js
        ├── Application/
        │   └── users/
        │       ├── command/
        │       │   ├── createUserCommand.js
        │       │   ├── createUserCommandHandler.js
        │       │   ├── updateUserCommand.js
        │       │   ├── updateUserCommandHandler.js
        │       │   ├── deleteUserCommand.js
        │       │   └── deleteUserCommandHandler.js
        │       └── query/
        │           ├── getAllUsersQuery.js
        │           ├── getAllUsersQueryHandler.js
        │           ├── getUserByIdQuery.js
        │           └── getUserByIdQueryHandler.js
        ├── Domain/
        │   └── IUserRepository.js
        └── Infrastructure/
            ├── user.json
            └── userRepository.js

1.2 package.json létrehozása

Cél: Node.js projekt konfigurációs fájl létrehozása ES6 modul támogatással.

Részletes leírás:

A package.json az npm projekt szíve - tartalmazza a metaadatokat, függőségeket és script-eket.

Kulcsfontosságú mezők:

1. Alapinformációk:

  • name: Projekt neve (lowercase, kötőjellel)
  • version: Verzió szám (semantic versioning: MAJOR.MINOR.PATCH)
  • description: Rövid leírás
  • main: Belépési pont (itt nem használt)

2. "type": "module" ⚠️ KRITIKUS!

  • Engedélyezi az ES6 modulok használatát (import/export)
  • Nélküle csak require()/module.exports működne (CommonJS)
  • Ez kötelező, hogy import szintaxist használhassunk!

3. Scripts:

  • start: A szerver indítása nodemon-nal
    • nodemon src/API/server.js - automatikus újraindítás fájl módosításkor
    • Így nem kell minden változtatás után manuálisan újraindítani
  • test: Placeholder teszt script (később Jest/Mocha tesztek jöhetnek ide)

4. Dependencies (Futásidejű függőségek):

  • express: ^5.2.1 - Web framework
    • ^ (caret): Automatikus minor/patch update-ek engedélyezése
    • 5.x.x verzió tartományon belül
  • nodemon: ^3.1.11 - Fejlesztői szerverAutomatikus újraindítás
    • Érdemes devDependencies-be tenni (csak fejlesztéshez kell)

Létrehozási módok:

Manuális: Hozd létre a fájlt és másold be a tartalmat.

npm init:

npm init -y  # Gyors, alapértelmezett értékekkel

Utána add hozzá: "type": "module"

Későbbi bővítések:

"devDependencies": {
  "nodemon": "^3.1.11",  // Ide kellene
  "jest": "^29.0.0"      // Teszteléshez
},
"engines": {
  "node": ">=18.0.0"     // Node verzió megkötés
}
📄 Teljes package.json tartalom
{
  "name": "elso-gyakorlat",
  "version": "1.0.0",
  "description": "User Management System",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "start": "nodemon src/API/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^5.2.1",
    "nodemon": "^3.1.11"
  }
}

FONTOS: Ne felejtsed el hozzáadni a "type": "module" sort!

1.3 Függőségek telepítése

npm install

2. Domain Layer Implementálás

2.1 IUserRepository.js - Repository Interface

Fájl: src/Domain/IUserRepository.js

Cél: Egy interfész-szerű osztály létrehozása, amely definiálja a user repository alapvető műveleteit.

Részletes leírás:

A Domain Layer középpontjában az interfészek állnak. JavaScript-ben nincs natív interfész támogatás (mint pl. TypeScript-ben vagy Java-ban), de osztályokkal szimulálhatjuk ezt a viselkedést.

Mit kell létrehozni:

  1. IUserRepository osztály - Ez lesz a "szerződés", amit minden repository implementációnak követnie kell

  2. 5 metódus, mind aszinkron (async):

    • getAll() - Összes felhasználó lekérése (paraméter nélkül)
    • getById(id) - Egy felhasználó lekérése ID alapján
    • create(user) - Új felhasználó létrehozása (user objektumot vár paraméterként)
    • update(id, userData) - Felhasználó módosítása (ID és új adatok)
    • delete(id) - Felhasználó törlése (ID alapján)
  3. Error dobás - Minden metódus dobjon hibát alapértelmezetten (throw new Error('Method not implemented'))

    • Miért? Mert ez biztosítja, hogy ha valaki implementálja ezt az osztályt, de elfelejt egy metódust felülírni, egyből hibát kapjon

Fontos elvek:

  • Ez az osztály NEM tartalmaz implementációt, csak az "interfészt" definiálja
  • Minden metódus async, mert később fájlkezelés/adatbázis műveleteket fogunk végezni
  • Ez a Dependency Inversion Principle része - a magasabb szintű kód ettől az interface-től függ, nem a konkrét implementációtól
📄 Teljes kód megtekintése
export class IUserRepository {
  async getAll() {
    throw new Error('Method not implemented');
  }

  async getById(id) {
    throw new Error('Method not implemented');
  }

  async create(user) {
    throw new Error('Method not implemented');
  }

  async update(id, userData) {
    throw new Error('Method not implemented');
  }

  async delete(id) {
    throw new Error('Method not implemented');
  }
}

3. Infrastructure Layer Implementálás

3.1 user.json - Adat fájl

Fájl: src/Infrastructure/user.json

Cél: Egyszerű JSON fájl alapú adattárolás létrehozása.

Részletes leírás:

Ez lesz az "adatbázisunk" - egy egyszerű JSON fájl, amely a felhasználókat tárolja. Később ezt könnyedén lecserélhetjük valódi adatbázisra (MongoDB, PostgreSQL, stb.) anélkül, hogy az alkalmazás többi részét módosítanánk.

Mit tartalmaz:

  • Kezdetben egy üres tömb ([])
  • A repository fog írni/olvasni ebből a fájlból
  • Minden user objektumként lesz tárolva a tömbben

Miért JSON fájl?

  • Egyszerű és gyors kezdéshez
  • Nem kell adatbázist telepíteni
  • Könnyen olvasható és debuggolható
  • Demonstrálja a repository pattern használatát
📄 Fájl tartalma megtekintése
[]

3.2 userRepository.js - Repository Implementáció

Fájl: src/Infrastructure/userRepository.js

Cél: Az IUserRepository interfész konkrét implementációja JSON fájl alapú tárolással.

Részletes leírás:

Ez az osztály valósítja meg a tényleges adatelérési logikát. Kiterjeszti (extends) az IUserRepository interfészt, tehát felül kell írnia minden metódust.

Importok és inicializálás:

  1. fs/promises - Node.js fájlrendszer modul aszinkron verzió (promise-based)
  2. path - Fájl útvonalak kezeléséhez
  3. fileURLToPath - ES6 modulokban szükséges a __dirname eléréséhez
  4. IUserRepository - A szülő interfész

Fontos ES6 Modules trükk:

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

ES6 modulokban nem elérhető a __dirname, így ezt a workaround-ot kell használni.

Metódusok részletes működése:

1. getAll() - Összes user lekérése

  • fs.readFile() - Beolvassa a JSON fájlt (async)
  • JSON.parse() - String-ből objektummá alakítja
  • Try-catch: ha a fájl nem létezik/üres, üres tömböt ad vissza
  • Fontos: Ez a metódus lesz a többi metódus alapja is!

2. getById(id) - Egy user keresése

  • Meghívja a getAll()-t
  • find() - Tömb metódus, megkeresi az első egyező elemet
  • Ha nincs találat, undefined-et ad vissza

3. create(user) - Új user létrehozása

  • Lekéri az összes user-t
  • Új objektum létrehozása:
    • id: Date.now().toString() - Timestamp alapú egyedi ID
    • ...user - Spread operator, bemásol minden property-t
    • createdAt - ISO formátumú timestamp
  • users.push() - Hozzáadja a tömb végéhez
  • fs.writeFile() - Visszamenti a fájlba
    • JSON.stringify(users, null, 2) - Pretty print, 2 szóköz indentálás
  • Visszaadja az új user objektumot

4. update(id, userData) - User módosítása

  • findIndex() - Megkeresi az user indexét a tömbben
  • Ha index === -1, nem létezik → null visszatérés
  • Merge művelet spread operator-ral:
    • ...users[index] - Régi adatok
    • ...userData - Új adatok (felülírják a régieket)
    • id: users[index].id - ID megtartása (biztonsági okokból)
    • updatedAt - Új timestamp
  • Fájl frissítése és módosított user visszaadása

5. delete(id) - User törlése

  • filter() - Új tömb létrehozása az adott user nélkül
  • Hossz összehasonlítás: ha egyenlő, nem volt mit törölni → false
  • Fájl frissítése és true visszaadása

Best practices:

  • Minden metódus async - konzisztens API
  • Try-catch csak ahol szükséges (getAll)
  • Spread operator használata immutábilis műveleteknél
  • Pretty print a JSON fájlban - könnyebb debuggolni
📄 Teljes kód megtekintése
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { IUserRepository } from '../Domain/IUserRepository.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const USER_FILE = path.join(__dirname, 'user.json');

export class UserRepository extends IUserRepository {
  async getAll() {
    try {
      const data = await fs.readFile(USER_FILE, 'utf-8');
      return JSON.parse(data);
    } catch (error) {
      return [];
    }
  }

  async getById(id) {
    const users = await this.getAll();
    return users.find(user => user.id === id);
  }

  async create(user) {
    const users = await this.getAll();
    const newUser = {
      id: Date.now().toString(),
      ...user,
      createdAt: new Date().toISOString()
    };
    users.push(newUser);
    await fs.writeFile(USER_FILE, JSON.stringify(users, null, 2));
    return newUser;
  }

  async update(id, userData) {
    const users = await this.getAll();
    const index = users.findIndex(user => user.id === id);
    if (index === -1) return null;
    
    users[index] = {
      ...users[index],
      ...userData,
      id: users[index].id,
      updatedAt: new Date().toISOString()
    };
    await fs.writeFile(USER_FILE, JSON.stringify(users, null, 2));
    return users[index];
  }

  async delete(id) {
    const users = await this.getAll();
    const filteredUsers = users.filter(user => user.id !== id);
    if (users.length === filteredUsers.length) return false;
    
    await fs.writeFile(USER_FILE, JSON.stringify(filteredUsers, null, 2));
    return true;
  }
}

4. Application Layer - Query Implementálás

4.1 GetAllUsersQuery

Fájl: src/Application/users/query/getAllUsersQuery.js

Cél: Query objektum létrehozása az összes felhasználó lekérdezéséhez.

Részletes leírás:

A CQRS pattern-ben minden lekérdezésnek (Query) van egy saját Query osztálya, még akkor is, ha az üres. Ez a konzisztencia és a kód későbbi bővíthetősége szempontjából fontos.

Miért üres ez az osztály?

  • Az összes user lekérdezéséhez nincs szükség paraméterekre (nincs szűrés, nincs ID, stb.)
  • A konstruktor üres marad
  • Később könnyedén bővíthető (pl. pagination: page, limit paraméterek)

CQRS elv: Minden use case-nek (ebben az esetben: "add meg az összes user-t") saját Query objektuma van, még ha paraméter nélküli is.

📄 Teljes kód megtekintése
export class GetAllUsersQuery {
  constructor() {}
}

4.2 GetAllUsersQueryHandler

Fájl: src/Application/users/query/getAllUsersQueryHandler.js

Cél: A GetAllUsersQuery feldolgozása és végrehajtása.

Részletes leírás:

A Handler az, amely ténylegesen végrehajtja a műveletet. Ez a "use case" implementáció.

Struktúra:

  1. Konstruktor:

    • Fogad egy userRepository paramétert (Dependency Injection)
    • Eltárolja azt instance változóban (this.userRepository)
    • Miért DI? Később könnyű tesztelni (mock repository-val), és nem függ konkrét implementációtól
  2. handle(query) metódus:

    • Fogadja a Query objektumot (jelen esetben üres)
    • Meghívja a repository getAll() metódusát
    • Visszaadja az eredményt
    • async/await használata az aszinkron művelethez

Ez a Handler felelőssége:

  • Query feldolgozása
  • Repository meghívása
  • Eredmény visszaadása
  • (Itt lehetne üzleti logika, validáció, stb. - de ennél az egyszerű lekérdezésnél nincs rá szükség)

Nem a Handler felelőssége:

  • HTTP kérések kezelése (ezt a Controller végzi)
  • Adatbázis műveletek (ezt a Repository végzi)
📄 Teljes kód megtekintése
export class GetAllUsersQueryHandler {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async handle(query) {
    return await this.userRepository.getAll();
  }
}

4.3 GetUserByIdQuery

Fájl: src/Application/users/query/getUserByIdQuery.js

Cél: Query objektum egyedi felhasználó lekérdezéséhez ID alapján.

Részletes leírás:

Ez a Query már tartalmaz paramétert - a keresendő user ID-t.

Konstruktor paraméter:

  • id - A keresendő felhasználó egyedi azonosítója (string formátumban)
  • Eltároljuk this.id-ben, hogy a Handler hozzáférjen

Példa használat:

const query = new GetUserByIdQuery('1707825600000');

CQRS elv: A Query objektum tiszta adatstruktúra (Data Transfer Object - DTO), csak az adatokat hordozza, logika nélkül.

📄 Teljes kód megtekintése
export class GetUserByIdQuery {
  constructor(id) {
    this.id = id;
  }
}

4.4 GetUserByIdQueryHandler

Fájl: src/Application/users/query/getUserByIdQueryHandler.js

Cél: Egyedi felhasználó lekérdezésének feldolgozása validációval.

Részletes leírás:

Konstruktor:

  • Ugyanaz a DI pattern, mint a GetAllUsersQueryHandler-nél

handle(query) metódus lépései:

  1. Repository meghívása:

    const user = await this.userRepository.getById(query.id);
    
    • Lekéri a user-t a query-ben kapott ID alapján
    • A repository undefined-et ad vissza, ha nincs találat
  2. Validáció:

    if (!user) {
      throw new Error('User not found');
    }
    
    • Ha nincs találat, kivételt dob
    • Ez az üzleti logika része: egy használhatatlan Query-t hibának tekintünk
  3. Eredmény visszaadása:

    • Ha megvan a user, visszaadjuk

Error Handling:

  • A dobott hiba később a Controller-ben lesz elkapva
  • A Controller alakítja át HTTP 404 válasszá
  • Ez szépen szétválasztja a rétegeket: Application Layer nem tud HTTP-ről, csak üzleti hibákat dob
📄 Teljes kód megtekintése
export class GetUserByIdQueryHandler {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async handle(query) {
    const user = await this.userRepository.getById(query.id);
    if (!user) {
      throw new Error('User not found');
    }
    return user;
  }
}

5. Application Layer - Command Implementálás

5.1 CreateUserCommand

Fájl: src/Application/users/command/createUserCommand.js

Cél: Command objektum új felhasználó létrehozásához.

Részletes leírás:

CQRS: Command vs Query

  • A Query adatokat kér le (olvasás) - nem változtatja a rendszer állapotát
  • A Command műveletet hajt végre (írás) - megváltoztatja a rendszer állapotát

Konstruktor paraméterek:

  • name (string) - A felhasználó neve (kötelező)
  • email (string) - A felhasználó email címe (kötelező)
  • age (number) - A felhasználó életkora (opcionális)

Fontos:

  • Az ID-t NEM adjuk meg - azt a Repository generálja automatikusan
  • A createdAt timestamp-et sem - azt is a Repository adja hozzá
  • A Command csak az "üzleti adatokat" tartalmazza

Példa használat:

const command = new CreateUserCommand('John Doe', 'john@example.com', 30);

Clean Architecture elv: A Command egy egyszerű DTO (Data Transfer Object), tisztán adatok, logika nélkül.

📄 Teljes kód megtekintése
export class CreateUserCommand {
  constructor(name, email, age) {
    this.name = name;
    this.email = email;
    this.age = age;
  }
}

5.2 CreateUserCommandHandler

Fájl: src/Application/users/command/createUserCommandHandler.js

Cél: Új felhasználó létrehozási parancs végrehajtása validációval.

Részletes leírás:

Konstruktor:

  • DI pattern: userRepository injektálás

handle(command) metódus lépései:

1. Validáció (Üzleti szabályok):

if (!command.name || !command.email) {
  throw new Error('Name and email are required');
}
  • Name és email kötelező mezők
  • Ez üzleti logika - az Application Layer felelőssége
  • Ha valamelyik hiányzik, kivételt dobunk
  • Later: Bővíthető email formátum ellenőrzéssel, egyediség ellenőrzéssel, stb.

2. User objektum összeállítása:

const userData = {
  name: command.name,
  email: command.email,
  age: command.age
};
  • A Command adataiból létrehozunk egy plain objektumot
  • Csak a "tiszta" adatok mennek tovább a Repository-nak
  • Az age lehet undefined ha nem adták meg - ez rendben van

3. Repository meghívása:

return await this.userRepository.create(userData);
  • A Repository hozzáadja az ID-t és a createdAt-et
  • Visszaadja a teljes user objektumot (ID-val és timestamp-el együtt)

CommandHandler felelősségei:

  • Validáció (üzleti szabályok)
  • Adatok előkészítése
  • Repository meghívása
  • NEM HTTP kezelés (azt a Controller végzi)
  • NEM adatbázis műveletek (azt a Repository végzi)
📄 Teljes kód megtekintése
export class CreateUserCommandHandler {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async handle(command) {
    if (!command.name || !command.email) {
      throw new Error('Name and email are required');
    }
    
    const userData = {
      name: command.name,
      email: command.email,
      age: command.age
    };
    
    return await this.userRepository.create(userData);
  }
}

5.3 UpdateUserCommand

Fájl: src/Application/users/command/updateUserCommand.js

Cél: Command objektum felhasználó módosításához.

Részletes leírás:

Konstruktor paraméterek:

  • id (string) - A módosítandó user egyedi azonosítója (kötelező!)
  • name (string) - Az új/frissített név
  • email (string) - Az új/frissített email
  • age (number) - Az új/frissített életkor

Különbség a CreateUserCommand-hoz képest:

  • Ez tartalmaz ID-t is - tudnunk kell, melyik user-t módosítjuk
  • Az ID a URL-ből jön majd (pl. PUT /users/123456)

Példa használat:

const command = new UpdateUserCommand(
  '1707825600000',
  'John Updated',
  'john.new@example.com',
  31
);
📄 Teljes kód megtekintése
export class UpdateUserCommand {
  constructor(id, name, email, age) {
    this.id = id;
    this.name = name;
    this.email = email;
    this.age = age;
  }
}

5.4 UpdateUserCommandHandler

Fájl: src/Application/users/command/updateUserCommandHandler.js

Cél: Felhasználó módosítási parancs végrehajtása.

Részletes leírás:

handle(command) metódus lépései:

1. Adatok előkészítése:

const userData = {
  name: command.name,
  email: command.email,
  age: command.age
};
  • Összegyjük a módosítandó adatokat
  • Fontos: Az ID-t NEM küldjük tovább a userData-ban!
  • Az ID külön paraméterként megy a repository-nak (biztonság és tisztaság)

2. Repository meghívása:

const updatedUser = await this.userRepository.update(command.id, userData);
  • A repository update() metódusa várja az ID-t és az új adatokat
  • Visszaad null-t ha nem található a user
  • Visszaadja a frissített user objektumot (updatedAt timestamp-el) ha sikeres

3. Validáció (létezés ellenőrzés):

if (!updatedUser) {
  throw new Error('User not found');
}
  • Ha null jött vissza, nem létezett ilyen ID-jű user
  • Kivételt dobunk, amit a Controller HTTP 404-re alakít

4. Eredmény visszaadása:

  • A frissített user objektum megy vissza a Controller-nek

Későbbi bővítési lehetőségek:

  • Validáció csak a megváltozott mezőkre (partial update)
  • Verzió kontroll (optimistic locking)
  • Jogosultság ellenőrzés (csak saját profil módosítható)
📄 Teljes kód megtekintése
export class UpdateUserCommandHandler {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async handle(command) {
    const userData = {
      name: command.name,
      email: command.email,
      age: command.age
    };
    
    const updatedUser = await this.userRepository.update(command.id, userData);
    if (!updatedUser) {
      throw new Error('User not found');
    }
    
    return updatedUser;
  }
}

5.5 DeleteUserCommand

Fájl: src/Application/users/command/deleteUserCommand.js

Cél: Command objektum felhasználó törléséhez.

Részletes leírás:

Konstruktor paraméterek:

  • id (string) - A törlendő user egyedi azonosítója

Egyszerű struktúra:

  • Csak egy ID-t tartalmaz - ez az egyetlen információ, ami kell a törléshez
  • Hasonló a GetUserByIdQuery-hez, de szándékban különbözik (írás vs olvasás)

Példa használat:

const command = new DeleteUserCommand('1707825600000');

CQRS szétválasztás: Bár hasonló a Query-hez, külön osztály kell, mert ez Command (írási művelet, állapot változtatás).

📄 Teljes kód megtekintése
export class DeleteUserCommand {
  constructor(id) {
    this.id = id;
  }
}

5.6 DeleteUserCommandHandler

Fájl: src/Application/users/command/deleteUserCommandHandler.js

Cél: Felhasználó törlési parancs végrehajtása.

Részletes leírás:

handle(command) metódus lépései:

1. Repository meghívása:

const deleted = await this.userRepository.delete(command.id);
  • A repository delete() metódusa boolean-t ad vissza
  • true - sikeres törlés
  • false - nem létezett ilyen ID-jű user

2. Validáció:

if (!deleted) {
  throw new Error('User not found');
}
  • Ha törlés sikertelen (false), kivételt dobunk
  • Ez konzisztens a többi handler validációjával

3. Visszatérési érték:

  • Nincs explicit return!
  • DELETE művelet nem ad vissza adatot (ún. "void" műv elet)
  • A Controller HTTP 204 No Content választ küld

Fontos különbség:

  • Ez az egyetlen Handler, ami nem ad vissza user objektumot
  • REST convention: DELETE művelet után nincs response body

Későbbi bővítési lehetőségek:

  • Soft delete (csak megjelölés töröltként, fizikai törlés helyett)
  • Cascade delete (kapcsolódó adatok törlése is)
  • Audit log (ki, mikor törölte)
📄 Teljes kód megtekintése
export class DeleteUserCommandHandler {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async handle(command) {
    const deleted = await this.userRepository.delete(command.id);
    if (!deleted) {
      throw new Error('User not found');
    }
  }
}

6. API Layer Implementálás

6.1 userController.js - Controller

Fájl: src/API/controllers/userController.js

Cél: HTTP kérések kezelése és koordináció az Application layer-rel.

Részletes leírás:

A Controller a Presentation Layer része - ő az, aki tudja, mi az a HTTP, mi az a request és response. Az Application Layer-t (Handler-ek) használja az üzleti logika végrehajtására.

Fájl szerkezete:

1. Importok:

  • Repository és összes Query/Command osztály
  • Összes QueryHandler és CommandHandler osztály
  • Összesen 11 import (1 Repository + 5 Query/Command pár)

2. Dependency Injection beállítása:

const userRepository = new UserRepository();
const getAllUsersQueryHandler = new GetAllUsersQueryHandler(userRepository);
// ... stb
  • Egy közös repository példány minden handler-nek
  • Handler példányok modul szinten (singleton pattern)
  • Ez egyszerűsíti a kódot - nem kell minden metódusban létrehozni

3. Controller metódusok (5 db):

getAll(req, res) - Összes user lekérése

  • Query létrehozása (paraméter nélkül)
  • Handler meghívása
  • Eredmény visszaküldése JSON formátumban
  • Error handling: 500 (szerver hiba)

getById(req, res) - Egy user lekérése

  • Query létrehozása req.params.id-val (URL paraméter)
  • Handler meghívása
  • Eredmény visszaküldése
  • Error handling: 404 (nem található)

create(req, res) - Új user létrehozása

  • Command létrehozása req.body adatokból (POST body)
  • Handler meghívása
  • Eredmény visszaküldése 201 Created státusszal
  • Error handling: 400 (validációs hiba)

update(req, res) - User módosítása

  • Command létrehozása ID-val (URL) + adatok (POST body)
  • Handler meghívása
  • Eredmény visszaküldése
  • Error handling: 404 (nem található)

delete(req, res) - User törlése

  • Command létrehozása csak ID-val
  • Handler meghívása
  • 204 No Content státusz, üres body
  • Error handling: 404 (nem található)

HTTP Státusz kódok:

  • 200 OK - Sikeres GET, PUT (default Express-ben)
  • 201 Created - Sikeres POST (új resource)
  • 204 No Content - Sikeres DELETE (nincs response body)
  • 400 Bad Request - Validációs hiba (hiányzó/helytelen adatok)
  • 404 Not Found - Resource nem található
  • 500 Internal Server Error - Váratlan szerver hiba

Felelősségek:

  • HTTP kérés/válasz kezelése
  • URL paraméterek és body adatok kinyerése
  • Command/Query objektumok létrehozása
  • Handler-ek hívása
  • HTTP státusz kódok beállítása
  • Error handling és átkonvertálása HTTP válaszokra
  • NINCS üzleti logika (azt a Handler végzi)
  • NINCS adatbázis művelet (azt a Repository végzi)
📄 Teljes kód megtekintése
import { UserRepository } from '../../Infrastructure/userRepository.js';
import { GetAllUsersQuery } from '../../Application/users/query/getAllUsersQuery.js';
import { GetAllUsersQueryHandler } from '../../Application/users/query/getAllUsersQueryHandler.js';
import { GetUserByIdQuery } from '../../Application/users/query/getUserByIdQuery.js';
import { GetUserByIdQueryHandler } from '../../Application/users/query/getUserByIdQueryHandler.js';
import { CreateUserCommand } from '../../Application/users/command/createUserCommand.js';
import { CreateUserCommandHandler } from '../../Application/users/command/createUserCommandHandler.js';
import { UpdateUserCommand } from '../../Application/users/command/updateUserCommand.js';
import { UpdateUserCommandHandler } from '../../Application/users/command/updateUserCommandHandler.js';
import { DeleteUserCommand } from '../../Application/users/command/deleteUserCommand.js';
import { DeleteUserCommandHandler } from '../../Application/users/command/deleteUserCommandHandler.js';

const userRepository = new UserRepository();
const getAllUsersQueryHandler = new GetAllUsersQueryHandler(userRepository);
const getUserByIdQueryHandler = new GetUserByIdQueryHandler(userRepository);
const createUserCommandHandler = new CreateUserCommandHandler(userRepository);
const updateUserCommandHandler = new UpdateUserCommandHandler(userRepository);
const deleteUserCommandHandler = new DeleteUserCommandHandler(userRepository);

export class UserController {
  async getAll(req, res) {
    try {
      const query = new GetAllUsersQuery();
      const users = await getAllUsersQueryHandler.handle(query);
      res.json(users);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async getById(req, res) {
    try {
      const query = new GetUserByIdQuery(req.params.id);
      const user = await getUserByIdQueryHandler.handle(query);
      res.json(user);
    } catch (error) {
      res.status(404).json({ error: error.message });
    }
  }

  async create(req, res) {
    try {
      const command = new CreateUserCommand(
        req.body.name,
        req.body.email,
        req.body.age
      );
      const user = await createUserCommandHandler.handle(command);
      res.status(201).json(user);
    } catch (error) {
      res.status(400).json({ error: error.message });
    }
  }

  async update(req, res) {
    try {
      const command = new UpdateUserCommand(
        req.params.id,
        req.body.name,
        req.body.email,
        req.body.age
      );
      const user = await updateUserCommandHandler.handle(command);
      res.json(user);
    } catch (error) {
      res.status(404).json({ error: error.message });
    }
  }

  async delete(req, res) {
    try {
      const command = new DeleteUserCommand(req.params.id);
      await deleteUserCommandHandler.handle(command);
      res.status(204).send();
    } catch (error) {
      res.status(404).json({ error: error.message });
    }
  }
}

6.2 userRouter.js - Router

Fájl: src/API/routers/userRouter.js

Cél: RESTful route-ok definiálása és Controller metódusokhoz kötése.

Részletes leírás:

A Router az Express moduláris routing rendszere - külön fájlban definiálhatjuk a route-okat, majd a főalkalmazásba importálhatjuk.

Fájl szerkezete:

1. Importok:

  • express - Express framework
  • UserController - A korábban létrehozott controller

2. Router és Controller példányosítás:

const router = express.Router();
const userController = new UserController();

3. Route definíciók (5 db):

REST API konvenciók:

HTTP Metódus URL Pattern Controller Metódus Leírás
GET / getAll() Összes user listázása
GET /:id getById() Egyedi user lekérése
POST / create() Új user létrehozása
PUT /:id update() User teljes módosítása
DELETE /:id delete() User törlése

Cél: Express alkalmazás konfigurálása, middleware-ek és route-ok beállítása, szerver indítása.

Részletes leírás:

Ez az alkalmazás belépési pontja (entry point) - ez a fájl indul el, amikor npm start-ot futtatunk.

Fájl szerkezete:

1. Importok:

import express from 'express';
import userRouter from './routers/userRouter.js';
  • Express framework
  • User router (korábban definiált route-ok)

2. Express app instance:

const app = express();
  • Létrehozza az Express alkalmazás objektumot
  • Ezen fogunk middleware-eket és route-okat regisztrálni

3. Konfigurációs változók:

const PORT = 3000;
  • Port szám, ahol a szerver hallgatózni fog
  • Később környezeti változóból is jöhet: process.env.PORT || 3000

4. Middleware-ek:

app.use(express.json());
  • Kritikus fontosságú! Ez parse-olja a JSON body-t a POST/PUT kérésekben
  • Nélküle a req.body undefined lenne
  • Az Express beépített middleware-je (korábban külön body-parser package volt)
  • Automatikusan parse-olja ha Content-Type: application/json

5. Route regisztráció:

app.use('/users', userRouter);
  • Base path: /users
  • A userRouter-ben definiált relatív útvonalak eléje kerül ez a prefix
  • Így a router.get('/') valójában GET /users lesz
  • Moduláris: könnyen hozzáadhatunk más router-eket (pl. /posts, /comments)

Teljes útvonalak:

  • GET /users → összes user
  • GET /users/:id → egy user
  • POST /users → új user
  • PUT /users/:id → user módosítás
  • DELETE /users/:id → user törlés

6. Szerver indítása:

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});
  • app.listen() - Express metódus a szerver indításához
  • Callback function - lefut, amikor a szerver sikeresen elindult
  • Console log - visszajelzés, hogy minden OK

Fontos koncepciók:

Middleware sorrend:

  • A middleware-ek sorrendben futnak!
  • express.json() a route-ok ELŐTT kell legyen
  • Így a route handler-ek már parse-olt body-t kapnak

További bővítési lehetőségek:

app.use(cors());  // CORS engedélyezése
app.use(morgan('dev'));  // HTTP logging
app.use(helmet());  // Biztonsági headers
app.use('/posts', postRouter);  // További entitások
📄 Teljes kód megtekintése
import express from 'express';
import userRouter from './routers/userRouter.js';

const app = express();
const PORT = 3000;

// Middleware
app.use(express.json());

// Routes
app.use('/users', userRouter);

// Server
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});
trong>
import express from 'express';
import { UserController } from '../controllers/userController.js';

const router = express.Router();
const userController = new UserController();

router.get('/', (req, res) => userController.getAll(req, res));
router.get('/:id', (req, res) => userController.getById(req, res));
router.post('/', (req, res) => userController.create(req, res));
router.put('/:id', (req, res) => userController.update(req, res));
router.delete('/:id', (req, res) => userController.delete(req, res));

export default router;

6.3 server.js - Szerver konfiguráció

Fájl: src/API/server.js

import express from 'express';
import userRouter from './routers/userRouter.js';

const app = express();
const PORT = 3000;

// Middleware
app.use(express.json());

// Routes
app.use('/users', userRouter);

// Server
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

Magyarázat:

  • express.json(): JSON body parser middleware (kötelező a POST/PUT kérésekhez)
  • Base path: /users prefix minden user route-hoz
  • Port: 3000 (módosítható szükség szerint)

Futtatás és Tesztelés

Szerver Indítása

npm start

Ez elindítja a nodemon-t, ami automatikusan újraindítja a szervert fájl módosítások esetén.

API Endpoint-ok Tesztelése

1. Összes User Lekérése

Request:

GET http://localhost:3000/users

Válasz:

[]

2. Új User Létrehozása

Request:

POST http://localhost:3000/users
Content-Type: application/json

{
  "name": "John Doe",
  "email": "john@example.com",
  "age": 30
}

Válasz:

{
  "id": "1707825600000",
  "name": "John Doe",
  "email": "john@example.com",
  "age": 30,
  "createdAt": "2026-02-13T10:00:00.000Z"
}

3. User Lekérése ID alapján

Request:

GET http://localhost:3000/users/1707825600000

Válasz:

{
  "id": "1707825600000",
  "name": "John Doe",
  "email": "john@example.com",
  "age": 30,
  "createdAt": "2026-02-13T10:00:00.000Z"
}

4. User Módosítása

Request:

PUT http://localhost:3000/users/1707825600000
Content-Type: application/json

{
  "name": "John Updated",
  "email": "john.updated@example.com",
  "age": 31
}

Válasz:

{
  "id": "1707825600000",
  "name": "John Updated",
  "email": "john.updated@example.com",
  "age": 31,
  "createdAt": "2026-02-13T10:00:00.000Z",
  "updatedAt": "2026-02-13T10:05:00.000Z"
}

5. User Törlése

Request:

DELETE http://localhost:3000/users/1707825600000

Válasz:

204 No Content

Tesztelési Eszközök

  • Postman: GUI alapú API tesztelő
  • Thunder Client: VS Code extension
  • cURL: Parancssori eszköz
  • REST Client: VS Code extension (.http fájlokkal)

User Adatmodell

{
  "id": "string",           // Automatikusan generált (timestamp)
  "name": "string",         // Kötelező
  "email": "string",        // Kötelező
  "age": number,            // Opcionális
  "createdAt": "ISO8601",   // Automatikusan generált létrehozáskor
  "updatedAt": "ISO8601"    // Automatikusan generált módosításkor
}

Hibakezelés

Validációs Hibák

  • 400 Bad Request: Hiányzó kötelező mezők (name, email)
  • Üzenet: "Name and email are required"

Not Found Hibák

  • 404 Not Found: User nem található (GET, PUT, DELETE)
  • Üzenet: "User not found"

Szerver Hibák

  • 500 Internal Server Error: Váratlan hiba (pl. fájl írási probléma)
  • Üzenet: Részletes hibaüzenet

Best Practice-ek és Elvek

1. Separation of Concerns (SoC)

  • Minden réteg csak a saját felelősségével foglalkozik
  • Controller nem tartalmaz üzleti logikát
  • Repository nem validál

2. Dependency Inversion Principle (DIP)

  • A magasabb szintű modulok nem függenek az alacsonyabb szintűektől
  • Mindketten az absztrakciótól (interface) függenek
  • UserControllerIUserRepository interface-től függ, nem a konkrét implementációtól

3. Single Responsibility Principle (SRP)

  • Minden osztálynak egy felelőssége van
  • Command/Query külön handler-ben
  • Repository csak adatelérésért felelős

4. CQRS Benefits

  • Egyszerűbb kód, könnyebb megérteni
  • Optimalizálható külön az olvasás és írás
  • Könnyebb tesztelni

5. Async/Await használata

  • Minden I/O művelet (fájl olvasás/írás) aszinkron
  • Try-catch az error handling-hez

Gyakori Problémák és Megoldások

1. ES6 Module hibák

Hiba: SyntaxError: Cannot use import statement outside a module

Megoldás: package.json-ben add hozzá: "type": "module"

2. __dirname not defined

Hiba: ReferenceError: __dirname is not defined in ES module scope

Megoldás:

import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));

3. Port already in use

Hiba: Error: listen EADDRINUSE: address already in use :::3000

Megoldás:

  • Másik port használata
  • Folyamat leállítása: Windows-on netstat -ano | findstr :3000 majd taskkill /PID <PID> /F

4. JSON parse error

Hiba: JSON fájl sérült vagy üres

Megoldás: Try-catch blokk a repository-ban, üres tömb visszaadása hiba esetén


Ellenőrző Lista (Checklist)

  • Projekt struktúra létrehozva
  • package.json konfigurálva ("type": "module")
  • Függőségek telepítve
  • Domain layer: IUserRepository létrehozva
  • Infrastructure layer: UserRepository és user.json létrehozva
  • Application layer: Összes Query és Command létrehozva
  • Application layer: Összes Handler létrehozva
  • API layer: UserController létrehozva
  • API layer: userRouter létrehozva
  • API layer: server.js létrehozva és konfigurálva
  • Szerver elindul hiba nélkül
  • GET /users működik
  • POST /users működik és ment a JSON fájlba
  • GET /users/:id működik
  • PUT /users/:id működik
  • DELETE /users/:id működik
  • Hibakezelés működik mindenhol
  • Kód tiszta, kommentek megfelelőek

Összegzés

Ez a User Management rendszer egy jól strukturált, modern Node.js alkalmazás, amely követi a Clean Architecture és CQRS elveket. A kód könnyen karbantartható, skálázható, és előkészített későbbi bővítésekre (pl. adatbázis integráció).

Főbb tanulságok:

  • Rétegelt architektúra előnyei
  • CQRS pattern alkalmazása
  • Dependency Injection használata
  • REST API best practice-ek
  • Async/Await és hibakezelés
  • ES6 modulok használata Node.js-ben

Fejlesztési idő becslés: 3-5 óra (tapasztalattól függően)

Nehézségi szint: Közép-haladó


Készítette: GitHub Copilot
Dátum: 2026. február 13.
Verzió: 1.0