36 KiB
FELADAT: CORS + Manuális DI Container + Email Notification
🎯 Cél
Fejlett backend fejlesztési technikák elsajátítása Node.js-ben Clean Architecture és CQRS mintával:
- CORS biztonságos konfiguráció whitelist alapú origin ellenőrzéssel
- Saját Dependency Injection (DI) container különböző lifecycle-okkal (singleton, transient, scoped)
- Email küldés Nodemailer + Handlebars template alapú értesítésekkel
Amit már kész kaptál:
- ✅ Clean Architecture struktúra (api, application, domain, infrastructure)
- ✅ CQRS Pattern (Commands/Handlers, Queries/Handlers)
- ✅ Prisma ORM PostgreSQL adatbáziskezeléssel
- ✅ Cookie-based JWT autentikáció (regisztráció, login, logout, védett endpointok)
- ✅ bcrypt jelszó hashelés
- ✅ Repository Pattern (Domain interfaces + Infrastructure implementations)
- ✅ JwtService (cookie-based token management)
- ✅ Jest testing framework
📐 Architektúra
Ez a projekt Clean Architecture mintát követi CQRS (Command Query Responsibility Segregation) pattern-nel:
🏛️ Layer Struktúra
src/
├── api/ # Presentation Layer
│ ├── controllers/ # HTTP request handlers
│ ├── middlewares/ # Express middlewares (auth, cors, scope)
│ ├── routers/ # Route definitions
│ └── server.js # Express app setup + DI configuration
│
├── application/ # Application Layer
│ ├── auth/commands/ # Authentication commands & handlers
│ ├── user/ # User commands & queries
│ │ ├── commands/ # Write operations
│ │ └── queries/ # Read operations
│ ├── services/ # Application services
│ │ ├── JwtService.js ✅ KÉSZ (Cookie-based JWT)
│ │ └── EmailService.js ❌ TODO (FELADAT 3)
│ └── Container.js ❌ TODO (FELADAT 1)
│
├── domain/ # Domain Layer
│ ├── models/ # Business entities
│ │ └── User.js ✅ KÉSZ (Domain model)
│ └── irepositories/ # Repository interfaces
│ └── IUserRepository.js ✅ KÉSZ
│
└── infrastructure/ # Infrastructure Layer
├── db/ # Database connection
│ └── DatabaseConnection.js ✅ KÉSZ (Prisma wrapper)
└── repositories/ # Repository implementations
└── UserRepository.js ✅ KÉSZ (Prisma-based)
Key Concepts:
- Domain: Üzleti entitások és szabályok (User model, IUserRepository interface)
- Application: Business logic (Commands, Queries, Services, DI Container)
- Infrastructure: Külső rendszerekhez kapcsolódás (Prisma, Database)
- API: HTTP layer (controllers, routes, middlewares)
🚀 Projekt Indítása
1. Függőségek telepítése
npm install
2. Környezeti változók konfigurálása
Másold le a .env.example fájlt .env néven:
cp .env.example .env
Állítsd be az .env fájlban:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/cors_di_app?schema=public"
JWT_SECRET="titkos-kulcs-jwt-hez-változtasd-meg"
JWT_EXPIRES_IN="7d"
PORT=3000
NODE_ENV=development
# Ethereal Email (test SMTP)
ETHEREAL_USER=your-ethereal-user@ethereal.email
ETHEREAL_PASS=your-ethereal-password
# CORS (comma-separated origins)
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
3. Docker indítása (PostgreSQL)
npm run docker:up
4. Prisma migrációk futtatása
npm run prisma:migrate
npm run prisma:generate
5. Szerver indítása
npm run dev
A szerver elindul: http://localhost:3000
🔐 Kész API Endpoints (Teszteléshez)
⚠️ FONTOS: Cookie-Based Authentication
Ez a projekt cookie-based JWT autentikációt használ (nem localStorage!):
- JWT token httpOnly cookie-ban tárolódik
- Authorization header nem szükséges
- Böngésző automatikusan küldi a cookie-t
Publikus endpointok (JWT nem kell)
1. Regisztráció (Cookie-t ad vissza)
POST http://localhost:3000/api/auth/register
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"password": "securePassword123"
}
Válasz:
HTTP/1.1 201 Created
Set-Cookie: auth_token=<JWT>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800
{
"message": "User registered successfully",
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"createdAt": "2026-03-04T10:00:00.000Z",
"updatedAt": "2026-03-04T10:00:00.000Z"
}
}
}
Figyelem: Token nincs a response body-ban, csak cookie-ban!
2. Bejelentkezés (Cookie-t ad vissza)
POST http://localhost:3000/api/auth/login
Content-Type: application/json
{
"email": "john@example.com",
"password": "securePassword123"
}
Válasz:
HTTP/1.1 200 OK
Set-Cookie: auth_token=<JWT>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800
{
"message": "Login successful",
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
}
}
3. Kijelentkezés (Cookie törlése)
POST http://localhost:3000/api/auth/logout
Cookie: auth_token=<JWT>
Válasz:
HTTP/1.1 200 OK
Set-Cookie: auth_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0
{
"message": "Logout successful"
}
Védett endpointok (JWT cookie szükséges!)
A böngésző automatikusan küldi a cookie-t. cURL-nél használd a -b és -c flageket.
4. Aktuális felhasználó lekérdezése
# cURL
curl -b cookies.txt http://localhost:3000/api/users/me
# JavaScript (fetch)
fetch('http://localhost:3000/api/users/me', {
credentials: 'include' // Cookie automatikusan küldődik
})
5. Aktuális felhasználó frissítése
PUT http://localhost:3000/api/users/me
Cookie: auth_token=<JWT>
Content-Type: application/json
{
"name": "John Updated"
}
6. Összes felhasználó lekérdezése
GET http://localhost:3000/api/users
Cookie: auth_token=<JWT>
7. Egy felhasználó lekérdezése ID alapján
GET http://localhost:3000/api/users/2
Cookie: auth_token=<JWT>
📋 MIT KELL IMPLEMENTÁLNI - 3 FELADAT
⚙️ FELADAT 1: DI Container (src/application/Container.js)
⚙️ FELADAT 1: DI Container (src/application/Container.js)
Cél: Dependency Injection konténer implementálása különböző lifecycle-okkal
⚠️ FONTOS: A Container.js fájl src/application/ mappában van (nem core-ban)!
A Container három lifecycle típust támogat:
- Singleton: Egyszer jön létre, osztva van minden request között
- Transient: Minden resolve()-nál új példány jön létre
- Scoped: Request szinten megosztott a példány (req.scope-on keresztül)
Implementálandó metódusok:
/**
* Dependency Injection Container
* src/application/Container.js
*/
class Container {
constructor() {
// TODO 1: Inicializáld a services Map-et (singleton instance-ok tárolása)
// TODO 2: Inicializáld a factories Map-et (factory függvények tárolása)
// TODO 3: Inicializáld a lifetimes Map-et (lifecycle típusok tárolása)
// Példa inicializálás:
// this.services = new Map();
// this.factories = new Map();
// this.lifetimes = new Map();
}
register(name, factory, lifetime = 'singleton') {
// TODO 4: Tárold el a factory függvényt (this.factories.set(name, factory))
// TODO 5: Tárold el a lifetime típust (this.lifetimes.set(name, lifetime))
// TODO 6: Ha a lifetime === 'singleton', azonnal példányosítsd:
// - Hívd meg a factory-t: const instance = factory();
// - Tárold el: this.services.set(name, instance);
}
resolve(name, scope = null) {
// TODO 7: Ha a service regisztrálva van mint 'scoped' ÉS van scope paraméter:
// - Ellenőrizd: if (scope && scope.has(name)) return scope.get(name);
// - Ha nincs még a scope-ban, példányosítsd és tárold:
// const instance = this.factories.get(name)();
// scope.set(name, instance);
// return instance;
// TODO 8: Ha singleton, add vissza a services-ből:
// if (this.lifetimes.get(name) === 'singleton') {
// return this.services.get(name);
// }
// TODO 9: Ha transient, minden alkalommal hívj egy új factory-t:
// if (this.lifetimes.get(name) === 'transient') {
// return this.factories.get(name)();
// }
// TODO 10: Ha nem regisztrált a service, dobj hibát:
// throw new Error(`Service '${name}' is not registered`);
}
createScope() {
// TODO 11: Hozz létre egy új Map-et az scoped instance-oknak
// TODO 12: Térj vissza egy objektummal ami tartalmaz egy resolve metódust:
// const scopeMap = new Map();
// return {
// resolve: (name) => this.resolve(name, scopeMap)
// };
}
}
module.exports = Container;
Tesztelés:
A src/api/server.js már használja a Container-t:
const container = new Container();
// Singleton regisztráció
container.register('PrismaClient', () => {
return databaseConnection.getClient();
}, 'singleton');
// Használat
const prisma = container.resolve('PrismaClient');
Indítsd el a szervert és nézd meg, hogy működik-e a DI:
npm run dev
# ✅ DI Container configured (CQRS pattern)
Részletes segítség: Nézd meg a SEGÍTSÉG.md fájlt teljes megoldással!
🌐 FELADAT 2: CORS Middleware (src/api/middlewares/corsMiddleware.js)
Cél: Whitelist alapú CORS konfiguráció implementálása
A CORS middleware csak bizonyos origin-ekről engedélyezi a hozzáférést.
const cors = require('cors');
// Engedélyezett origin-ek whitelist-je (környezeti változóból vagy default)
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [
'http://localhost:3000',
'http://localhost:5173'
];
const corsOptions = {
origin: function (origin, callback) {
// TODO 1: Ha nincs origin (pl. backend-to-backend, Postman, curl):
// if (!origin) {
// return callback(null, true);
// }
// TODO 2: Ha az origin benne van az allowedOrigins listában:
// if (allowedOrigins.includes(origin)) {
// return callback(null, true);
// }
// TODO 3: Egyébként tiltsd le CORS hibával:
// callback(new Error('Not allowed by CORS'));
},
credentials: true, // Cookie és Authorization header engedélyezése
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};
module.exports = cors(corsOptions);
.env beállítás:
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://myfrontend.com
Aktiválás a server.js-ben:
Uncomment this line in src/api/server.js:
// A middleware chain-ben (körülbelül 130. sor)
app.use(cookieParser());
app.use(corsMiddleware); // <-- UNCOMMENT THIS LINE!
Tesztelés cURL-lel:
# Engedélyezett origin - SIKERES
curl -H "Origin: http://localhost:3000" http://localhost:3000/api/users
# Access-Control-Allow-Origin: http://localhost:3000
# Tiltott origin - HIBA
curl -H "Origin: http://malicious-site.com" http://localhost:3000/api/users
# Error: Not allowed by CORS
Frontend tesztelés (React/Vue):
fetch('http://localhost:3000/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include', // FONTOS: Cookie küldés/fogadás engedélyezése
body: JSON.stringify({ email, password })
})
.then(res => res.json())
.then(data => console.log('Login sikeres:', data))
.catch(err => console.error('CORS hiba:', err));
Részletes segítség: Nézd meg a SEGÍTSÉG.md fájlt teljes megoldással!
📧 FELADAT 3: Email Service (src/application/services/EmailService.js)
Cél: Nodemailer + Handlebars template alapú email küldés implementálása
Az EmailService welcome emailt küld regisztráció után.
⚠️ FONTOS: Az EmailService src/application/services/ mappában van!
const nodemailer = require('nodemailer');
const handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
class EmailService {
constructor() {
// TODO 1: Hozz létre Nodemailer transportot Ethereal tesztelő SMTP-vel
// this.transporter = nodemailer.createTransport({
// host: 'smtp.ethereal.email',
// port: 587,
// secure: false, // TLS
// auth: {
// user: process.env.ETHEREAL_USER || 'your-test-email@ethereal.email',
// pass: process.env.ETHEREAL_PASS || 'your-test-password'
// }
// });
console.log('📧 EmailService initialized');
}
async sendWelcomeEmail(userEmail, userName) {
try {
// TODO 2: Olvasd be a welcome.hbs template fájlt
// const templatePath = path.join(__dirname, 'templates', 'welcome.hbs');
// const templateSource = fs.readFileSync(templatePath, 'utf-8');
// TODO 3: Compile-old a template-et Handlebars-szal
// const template = handlebars.compile(templateSource);
// TODO 4: Generáld a HTML-t a felhasználói adatokkal
// const html = template({
// name: userName,
// date: new Date().toLocaleDateString('hu-HU')
// });
// TODO 5: Küld el az emailt
// const info = await this.transporter.sendMail({
// from: '"Clean Architecture App" <noreply@cleanarch.com>',
// to: userEmail,
// subject: 'Üdvözlünk az alkalmazásban!',
// html: html
// });
// TODO 6: Logold ki az Ethereal preview URL-t
// const previewUrl = nodemailer.getTestMessageUrl(info);
// console.log('📧 Email elküldve:', previewUrl);
return true;
} catch (error) {
console.error('❌ Email küldési hiba:', error.message);
return false;
}
}
}
module.exports = EmailService;
Email Template létrehozása:
Hozd létre: src/application/services/templates/welcome.hbs
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; }
.container { background: white; padding: 30px; border-radius: 10px; max-width: 600px; margin: 0 auto; }
h1 { color: #333; }
.footer { margin-top: 30px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="container">
<h1>🎉 Üdvözlünk, {{name}}!</h1>
<p>Sikeres regisztráció az alkalmazásunkban!</p>
<p>Regisztráció dátuma: <strong>{{date}}</strong></p>
<p>Mostantól hozzáférsz az összes funkciónkhoz.</p>
<div class="footer">
<p>Ez egy automatikus üzenet, kérjük ne válaszolj rá.</p>
</div>
</div>
</body>
</html>
.env konfigurálás Ethereal SMTP-vel:
- Menj a https://ethereal.email oldalra
- Kattints a "Create Ethereal Account" gombra
- Másold ki a kapott user és password értékeket
- Állítsd be a
.envfájlban:
ETHEREAL_USER=your-generated-user@ethereal.email
ETHEREAL_PASS=your-generated-password
Tesztelés:
Regisztrálj egy új felhasználót:
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "Test User",
"email": "test@example.com",
"password": "password123"
}'
Console output:
📧 Email elküldve: https://ethereal.email/message/XXXXXX
Nyisd meg a böngészőben a linket és látni fogod az elküldött emailt!
Részletes segítség: Nézd meg a SEGÍTSÉG.md fájlt teljes megoldással és példákkal!
📁 Projekt Struktúra (Frissített)
src/
├── api/ # Presentation Layer
│ ├── controllers/
│ │ ├── AuthController.js ✅ KÉSZ (Cookie-based JWT)
│ │ └── UserController.js ✅ KÉSZ (CQRS queries/commands)
│ ├── middlewares/
│ │ ├── authMiddleware.js ✅ KÉSZ (Cookie-based auth)
│ │ ├── corsMiddleware.js ❌ TODO (FELADAT 2)
│ │ └── scopeMiddleware.js 📚 Opcionális
│ ├── routers/
│ │ ├── authRoutes.js ✅ KÉSZ (register, login, logout)
│ │ └── userRoutes.js ✅ KÉSZ (protected endpoints)
│ └── server.js ✅ KÉSZ (Express + DI setup)
│
├── application/ # Application Layer
│ ├── auth/commands/ ✅ KÉSZ (CQRS)
│ │ ├── RegisterUserCommand.js
│ │ ├── RegisterUserCommandHandler.js
│ │ ├── LoginUserCommand.js
│ │ └── LoginUserCommandHandler.js
│ ├── user/ ✅ KÉSZ (CQRS)
│ │ ├── commands/ # Write operations
│ │ └── queries/ # Read operations
│ ├── services/
│ │ ├── JwtService.js ✅ KÉSZ (Cookie-based JWT)
│ │ ├── EmailService.js ❌ TODO (FELADAT 3)
│ │ └── templates/
│ │ └── welcome.hbs ❌ TODO (Email template)
│ └── Container.js ❌ TODO (FELADAT 1)
│
├── domain/ # Domain Layer
│ ├── models/
│ │ └── User.js ✅ KÉSZ (Domain entity)
│ └── irepositories/
│ └── IUserRepository.js ✅ KÉSZ (Repository interface)
│
├── infrastructure/ # Infrastructure Layer
│ ├── db/
│ │ └── DatabaseConnection.js ✅ KÉSZ (Prisma wrapper)
│ └── repositories/
│ └── UserRepository.js ✅ KÉSZ (Prisma implementation)
│
├── prisma/
│ ├── schema.prisma ✅ KÉSZ (User model)
│ └── migrations/ ✅ KÉSZ
│
└── tests/ # Testing
└── unit/
├── commands/ ✅ KÉSZ (Command handler tests)
├── queries/ ✅ KÉSZ (Query handler tests)
├── controllers/ ✅ KÉSZ (Controller tests)
├── middlewares/ ✅ KÉSZ (Middleware tests)
└── services/ ✅ KÉSZ (JwtService tests)
🎯 Tanulási Célok
CORS (Cross-Origin Resource Sharing)
- ✅ Origin alapú hozzáférés-vezérlés implementálása
- ✅ Whitelist konfiguráció production környezetben
- ✅ Preflight request-ek kezelése (OPTIONS)
- ✅ Credential (cookie, auth header) kezelés
DI Container (Dependency Injection)
- ✅ Lifecycle management (Singleton, Transient, Scoped)
- ✅ Loose coupling az alkalmazásban
- ✅ Tesztelhetőség javítása (dependency injection)
- ✅ Service registry pattern
Email Notification
- ✅ SMTP konfiguráció (Ethereal Email)
- ✅ Template-alapú email generálás (Handlebars)
- ✅ Async email küldés (nem blokkolja a request-et)
- ✅ Test email preview Ethereal-lel
Clean Architecture + CQRS
- ✅ Layer separation (Domain, Application, Infrastructure, API)
- ✅ CQRS Pattern (Commands for writes, Queries for reads)
- ✅ Repository Pattern (interface + implementation)
- ✅ Cookie-based JWT authentication (httpOnly, secure, sameSite)
🧪 Teljes Tesztelési Flow
1. Szerver indítása
npm run dev
Expected output:
✅ Prisma connected to PostgreSQL
✅ DI Container configured (CQRS pattern)
🚀 Server running on http://localhost:3000
📧 Email Service configured (implement EmailService!)
🔐 JWT Authentication enabled (cookie-based)
🍪 Cookies: httpOnly, secure (production), sameSite=strict
📍 Endpoints:
POST /api/auth/register
POST /api/auth/login
POST /api/auth/logout
GET /api/users/me (protected)
PUT /api/users/me (protected)
GET /api/users (protected)
GET /api/users/:id (protected)
2. Regisztráció (cURL cookie példa)
curl -c cookies.txt -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "Test User",
"email": "test@example.com",
"password": "password123"
}'
Kimenet:
- ✅ JWT token cookie-ban (Set-Cookie header)
- ✅ bcrypt-tel hashelt jelszó az adatbázisban
- ✅ Email elküldve (ha implementáltad a FELADAT 3-at)
- ✅ Cookie mentve a
cookies.txtfájlba
3. Bejelentkezés
curl -c cookies.txt -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "password123"
}'
Kimenet:
- ✅ JWT token cookie-ban
- ✅ bcrypt.compare jelszó ellenőrzés
4. Védett endpoint hívása (Cookie-val)
curl -b cookies.txt http://localhost:3000/api/users/me
Kimenet:
- ✅ authMiddleware ellenőrzi a JWT token-t cookie-ból
- ✅ req.user.userId beállítva
- ✅ GetMeQueryHandler lekéri az aktuális user-t Prisma-val
5. Kijelentkezés
curl -b cookies.txt -c cookies.txt -X POST http://localhost:3000/api/auth/logout
Kimenet:
- ✅ Cookie törlése (Max-Age=0)
- ✅ Következő request-nél 401 Unauthorized
🧪 Unit Testing
Futtatás:
npm test # Összes teszt + coverage
npm run test:watch # Watch mode
npm run test:unit # Csak unit tesztek
Expected output:
PASS tests/unit/commands/RegisterUserCommandHandler.test.js
PASS tests/unit/commands/LoginUserCommandHandler.test.js
PASS tests/unit/queries/GetMeQueryHandler.test.js
PASS tests/unit/controllers/AuthController.test.js
PASS tests/unit/middlewares/authMiddleware.test.js
PASS tests/unit/services/JwtService.test.js
Test Suites: 10 passed, 10 total
Tests: 75 passed, 75 total
❌ Hibaelhárítás
Docker PostgreSQL nem indul
docker-compose down
docker-compose up -d
docker-compose logs postgres
Prisma migration hiba
npm run prisma:migrate:reset
npm run prisma:migrate
npm run prisma:generate
JWT token invalid hiba (Cookie-based)
- ✅ Ellenőrizd, hogy a
.envfájlban van-eJWT_SECRET - ✅ cURL-nél használd a
-b cookies.txtflag-et - ✅ Frontend-en használd a
credentials: 'include'opciót
Email nem megy ki
- ✅ Ellenőrizd az Ethereal SMTP credentials-t (
.env) - ✅ Nézd meg a console-t az Ethereal preview URL-ért
- ✅ Hozd létre a
templates/welcome.hbsfájlt - ✅ Ellenőrizd, hogy implementáltad-e az EmailService-t
CORS hiba frontend-ből
- ✅ Add meg a frontend origin-t az
.envfájlban:ALLOWED_ORIGINS=http://localhost:5173 - ✅ Uncomment a
app.use(corsMiddleware);sort aserver.js-ben - ✅ Implementáld a CORS middleware-t (FELADAT 2)
📊 Értékelési Szempontok
Funkcionális követelmények:
- ✅ DI Container működik (register, resolve, createScope)
- ✅ Singleton, Transient, Scoped lifecycle-ok implementálva
- ✅ CORS whitelist működik (engedélyezett és tiltott origin-ek)
- ✅ Email küldés funkcionális (Handlebars template + Ethereal SMTP)
- ✅ Template fájl létrehozva és használva
Kód minőség:
- ✅ Kód tisztaság és SOLID elvek követése
- ✅ Hibakezelés (try-catch, validation)
- ✅ Environment változók használata (.env)
- ✅ Kommentek és dokumentáció
- ✅ Clean Architecture layering betartása
Testing:
- ✅ Unit tesztek írása (Container, CORS, EmailService)
- ✅ Tesztek lefutnak és zöldek
- ✅ Megfelelő coverage (>70%)
📚 Hasznos Források
- 📖 SEGÍTSÉG.md - Részletes példakódok és megoldások
- 📖 ARCHITECTURE.md - Clean Architecture dokumentáció
- 📖 COOKIE_AUTH_MIGRATION.md - Cookie-based JWT migration guide
- 🌐 Ethereal Email - Test SMTP service
- 🌐 Handlebars - Template engine
- 🌐 Prisma Docs - ORM documentation
Sok sikert a feladatokhoz! 🚀
Ha elakadtál, nézd meg a SEGÍTSÉG.md fájlt teljes megoldásokkal és magyarázatokkal!
Cél: Dependency Injection konténer implementálása különböző lifecycle-okkal
A Container három lifecycle típust támogat:
- Singleton: Egyszer jön létre, osztva van minden request között
- Transient: Minden resolve()-nál új példány jön létre
- Scoped: Request szinten megosztott a példány (req.scope-on keresztül)
class Container {
constructor() {
// TODO 1: Inicializáld a services Map-et (singleton instance-ok tárolása)
// TODO 2: Inicializáld a factories Map-et (factory függvények tárolása)
// TODO 3: Inicializáld a lifetimes Map-et (lifecycle típusok tárolása)
// Példa inicializálás:
// this.services = new Map();
// this.factories = new Map();
// this.lifetimes = new Map();
}
register(name, factory, lifetime = 'singleton') {
// TODO 4: Tárold el a factory függvényt (this.factories.set(name, factory))
// TODO 5: Tárold el a lifetime típust (this.lifetimes.set(name, lifetime))
// TODO 6: Ha a lifetime === 'singleton', azonnal példányosítsd:
// - Hívd meg a factory-t: const instance = factory();
// - Tárold el: this.services.set(name, instance);
}
resolve(name, scope = null) {
// TODO 7: Ha a service regisztrálva van mint 'scoped' ÉS van scope paraméter:
// - Ellenőrizd: if (scope && scope.has(name)) return scope.get(name);
// - Ha nincs még a scope-ban, példányosítsd és tárold:
// const instance = this.factories.get(name)();
// scope.set(name, instance);
// return instance;
// TODO 8: Ha singleton, add vissza a services-ből:
// if (this.lifetimes.get(name) === 'singleton') {
// return this.services.get(name);
// }
// TODO 9: Ha transient, minden alkalommal hívj egy új factory-t:
// if (this.lifetimes.get(name) === 'transient') {
// return this.factories.get(name)();
// }
// TODO 10: Ha nem regisztrált a service, dobj hibát:
// throw new Error(`Service ''${name}'' is not registered`);
}
createScope() {
// TODO 11: Hozz létre egy új Map-et az scoped instance-oknak
// TODO 12: Térj vissza egy objektummal ami tartalmaz egy resolve metódust:
// const scopeMap = new Map();
// return {
// resolve: (name) => this.resolve(name, scopeMap)
// };
}
}
module.exports = Container;
Tesztelés:
A src/api/server.js már használja a Container-t. Indítsd el a szervert és nézd meg, hogy működik-e a DI.
FELADAT 2: CORS Middleware (src/api/middlewares/corsMiddleware.js)
Cél: Whitelist alapú CORS konfiguráció implementálása
A CORS middleware csak bizonyos origin-ekről engedélyezi a hozzáférést.
const cors = require(''cors'');
// Engedélyezett origin-ek whitelist-je (környezeti változóból vagy default)
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split('','') || [
''http://localhost:3000'',
''http://localhost:5173''
];
const corsOptions = {
origin: function (origin, callback) {
// TODO 1: Ha nincs origin (pl. backend-to-backend, Postman, curl):
// if (!origin) {
// return callback(null, true);
// }
// TODO 2: Ha az origin benne van az allowedOrigins listában:
// if (allowedOrigins.includes(origin)) {
// return callback(null, true);
// }
// TODO 3: Egyébként tiltsd le CORS hibával:
// callback(new Error(''Not allowed by CORS''));
},
credentials: true, // Cookie és Authorization header engedélyezése
methods: [''GET'', ''POST'', ''PUT'', ''DELETE'', ''OPTIONS''],
allowedHeaders: [''Content-Type'', ''Authorization'']
};
module.exports = cors(corsOptions);
.env beállítás:
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://myfrontend.com
Tesztelés:
curl -H "Origin: http://localhost:3000" http://localhost:3000/api/users
# Sikeres - engedélyezett origin
curl -H "Origin: http://malicious-site.com" http://localhost:3000/api/users
# Hiba - tiltott origin
Aktiválás a server.js-ben:
// src/api/server.js
const corsMiddleware = require(''./middlewares/corsMiddleware'');
app.use(corsMiddleware); // <-- Uncomment this line!
FELADAT 3: Email Service (src/core/EmailService.js)
Cél: Nodemailer + Handlebars template alapú email küldés implementálása
Az EmailService welcome emailt küld regisztráció után.
const nodemailer = require(''nodemailer'');
const handlebars = require(''handlebars'');
const fs = require(''fs'');
const path = require(''path'');
class EmailService {
constructor() {
// TODO 1: Hozz létre Nodemailer transportot Ethereal tesztelő SMTP-vel
// this.transporter = nodemailer.createTransport({
// host: ''smtp.ethereal.email'',
// port: 587,
// secure: false, // TLS
// auth: {
// user: process.env.ETHEREAL_USER || ''your-test-email@ethereal.email'',
// pass: process.env.ETHEREAL_PASS || ''your-test-password''
// }
// });
console.log('' EmailService initialized'');
}
async sendWelcomeEmail(userEmail, userName) {
try {
// TODO 2: Olvasd be a welcome.hbs template fájlt
// const templatePath = path.join(__dirname, ''templates'', ''welcome.hbs'');
// const templateSource = fs.readFileSync(templatePath, ''utf-8'');
// TODO 3: Compile-old a template-et Handlebars-szal
// const template = handlebars.compile(templateSource);
// TODO 4: Generáld a HTML-t a felhasználói adatokkal
// const html = template({
// name: userName,
// date: new Date().toLocaleDateString(''hu-HU'')
// });
// TODO 5: Küld el az emailt
// const info = await this.transporter.sendMail({
// from: ''"Clean Architecture App" <noreply@cleanarch.com>'',
// to: userEmail,
// subject: ''Üdvözlünk az alkalmazásban!'',
// html: html
// });
// TODO 6: Logold ki az Ethereal preview URL-t
// const previewUrl = nodemailer.getTestMessageUrl(info);
// console.log('' Email elküldve:'', previewUrl);
} catch (error) {
console.error('' Email küldési hiba:'', error.message);
}
}
}
module.exports = EmailService;
Email Template (src/core/templates/welcome.hbs):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; }
.container { background: white; padding: 30px; border-radius: 10px; max-width: 600px; margin: 0 auto; }
h1 { color: #333; }
.footer { margin-top: 30px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="container">
<h1>Üdvözlünk, {{name}}! </h1>
<p>Sikeres regisztráció az alkalmazásunkban!</p>
<p>Regisztráció dátuma: <strong>{{date}}</strong></p>
<p>Mostantól hozzáférsz az összes funkciónkhoz.</p>
<div class="footer">
<p>Ez egy automatikus üzenet, kérjük ne válaszolj rá.</p>
</div>
</div>
</body>
</html>
.env konfigurálás Ethereal SMTP-vel:
- Menj a https://ethereal.email oldalra
- Kattints a "Create Ethereal Account" gombra
- Másold ki a kapott user és password értékeket
- Állítsd be a
.envfájlban:
ETHEREAL_USER=your-generated-user@ethereal.email
ETHEREAL_PASS=your-generated-password
Tesztelés: Regisztrálj egy új felhasználót:
POST http://localhost:3000/api/auth/register
{
"name": "Test User",
"email": "test@example.com",
"password": "password123"
}
Nézd meg a console-ban az Ethereal preview URL-t, ami így níz ki:
Email elküldve: https://ethereal.email/message/XXXXXX
Nyísd meg a böngészőben és látni fogod az elküldött emailt!
Projekt Struktúra
src/
api/
controllers/
AuthController.js Kész (regisztráció, login)
UserController.js Kész (védett user endpointok)
middlewares/
authMiddleware.js Kész (JWT token validálás)
corsMiddleware.js TODO - FELADAT 2
scopeMiddleware.js Opcionális
routers/
authRoutes.js Kész (publikus auth endpointok)
userRoutes.js Kész (JWT védett user endpointok)
server.js Kész (Express + DI setup)
application/
auth/
AuthService.js Kész (bcrypt + JWT)
user/
UserService.js Kész (Prisma CRUD)
core/
Container.js TODO - FELADAT 1
EmailService.js TODO - FELADAT 3
JwtUtil.js Kész (JWT utility)
templates/
welcome.hbs Welcome email template
domain/
(Prisma modellekkel helyettesítve)
infrastructure/
(Prisma ORM kezeli)
prisma/
schema.prisma Kész (User model)
Tanulási Célok
CORS (Cross-Origin Resource Sharing)
- Origin alapú hozzáférés-vezérlés implementálása
- Whitelist konfiguráció production környezetben
- Preflight request-ek kezelése (OPTIONS)
- Credential (cookie, auth header) kezelés
DI Container (Dependency Injection)
- Lifecycle management (Singleton, Transient, Scoped)
- Loose coupling az alkalmazásban
- Tesztelhetőség javítása (dependency injection)
- Service registry pattern
Email Notification
- SMTP konfiguráció (Ethereal Email)
- Template-alapú email generálás (Handlebars)
- Async email küldés (nem blokkolja a request-et)
- Test email preview Ethereal-lel
Teljes Tesztelési Flow
1. Szerver indítása
npm run dev
2. Regisztráció
POST http://localhost:3000/api/auth/register
{
"name": "Test User",
"email": "test@example.com",
"password": "password123"
}
Kimenet:
- JWT token válaszban
- bcrypt-tel hashelt jelszó az adatbázisban
- Email elküldve (ha implementáltad a FELADAT 3-at)
3. Bejelentkezés
POST http://localhost:3000/api/auth/login
{
"email": "test@example.com",
"password": "password123"
}
Kimenet:
- JWT token válaszban
- bcrypt.compare jelszó ellenőrzés
4. Védett endpoint hívása
GET http://localhost:3000/api/users/me
Authorization: Bearer <token>
Kimenet:
- authMiddleware ellenőrzi a JWT token-t
- req.user.userId beállítva
- UserService lekéri az aktuális user-t Prisma-val
Hibaelhárítás
Docker PostgreSQL nem indul
docker-compose down
docker-compose up -d
docker-compose logs postgres
Prisma migration hiba
npm run prisma:migrate:reset
npm run prisma:migrate
npm run prisma:generate
JWT token invalid hiba
- Ellenőrizd, hogy a
.envfájlban van-eJWT_SECRET - Ellenőrizd, hogy a token
Authorization: Bearer <token>formátumban van-e
Email nem megy ki
- Ellenőrizd az Ethereal SMTP credentials-t (
.env) - Nézd meg a console-t az Ethereal preview URL-ért
- Ellenőrizd, hogy implementáltad-e az EmailService-t
Értékelési Szempontok
- DI Container működik (register, resolve, createScope)
- CORS whitelist működik (engedélyezett és tiltott origin-ek)
- Email külső funkcionális (Handlebars template + Ethereal SMTP)
- Kód tisztaság és SOLID elvek követése
- Hibakezelés (try-catch, validation)
- Environment változók használata (.env)
Sok sikert!