# 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:**
```bash
npm init -y # Gyors, alapértelmezett értékekkel
```
Utána add hozzá: `"type": "module"`
**Későbbi bővítések:**
```json
"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
```json
{
"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
```bash
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
```javascript
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
```json
[]
```
#### 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:**
```javascript
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
```javascript
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
```javascript
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
```javascript
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:**
```javascript
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
```javascript
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:**
```javascript
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ó:**
```javascript
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
```javascript
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:**
```javascript
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
```javascript
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):**
```javascript
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:**
```javascript
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:**
```javascript
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
```javascript
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:**
```javascript
const command = new UpdateUserCommand(
'1707825600000',
'John Updated',
'john.new@example.com',
31
);
```
📄 Teljes kód megtekintése
```javascript
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:**
```javascript
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:**
```javascript
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):**
```javascript
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
```javascript
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:**
```javascript
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
```javascript
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:**
```javascript
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ó:**
```javascript
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
```javascript
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:**
```javascript
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
```javascript
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:**
```javascript
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:**
```javascript
import express from 'express';
import userRouter from './routers/userRouter.js';
```
- Express framework
- User router (korábban definiált route-ok)
**2. Express app instance:**
```javascript
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:**
```javascript
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:**
```javascript
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ó:**
```javascript
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:**
```javascript
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:**
```javascript
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
```javascript
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>
```javascript
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`
```javascript
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
```bash
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:**
```http
GET http://localhost:3000/users
```
**Válasz:**
```json
[]
```
#### 2. Új User Létrehozása
**Request:**
```http
POST http://localhost:3000/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
```
**Válasz:**
```json
{
"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:**
```http
GET http://localhost:3000/users/1707825600000
```
**Válasz:**
```json
{
"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:**
```http
PUT http://localhost:3000/users/1707825600000
Content-Type: application/json
{
"name": "John Updated",
"email": "john.updated@example.com",
"age": 31
}
```
**Válasz:**
```json
{
"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:**
```http
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
```javascript
{
"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
- `UserController` → `IUserRepository` 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**:
```javascript
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 /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ó
---
**Dátum**: 2026. február 13.
**Verzió**: 1.0