9.7 KiB
User Management Backend - Gyakorlat
Projekt Célja
User kezelő backend implementálása CQRS pattern és layered architecture használatával.
Technológiák
- Node.js + Express
- Prisma ORM + SQLite
- JavaScript (nem TypeScript)
- Layered Architecture
- CQRS Pattern
Projekt Struktúra
├── prisma/
│ └── schema.prisma # Adatbázis séma (TODO)
├── src/
│ ├── api/ # API réteg
│ │ ├── controllers/
│ │ │ └── UserController.js # Kész
│ │ └── routes/
│ │ └── user.routes.js # Kész
│ ├── application/ # Alkalmazási logika
│ │ └── user/
│ │ ├── command/
│ │ │ ├── CreateUserCommand.js # Kész
│ │ │ ├── CreateUserHandler.js # TODO
│ │ │ ├── UpdateUserCommand.js # Kész
│ │ │ ├── UpdateUserHandler.js # TODO
│ │ │ ├── DeleteUserCommand.js # Kész
│ │ │ └── DeleteUserHandler.js # TODO
│ │ └── query/
│ │ ├── GetUserQuery.js # Kész
│ │ ├── GetUserHandler.js # TODO
│ │ ├── GetAllUsersQuery.js # Kész
│ │ └── GetAllUsersHandler.js # TODO
│ ├── domain/ # Domain réteg
│ │ ├── interfaces/
│ │ │ └── IUserRepository.js # Kész (interfész definíció)
│ │ └── models/
│ │ └── User.js # TODO
│ └── infrastructure/ # Infrastruktúra réteg
│ ├── database/
│ │ └── prisma.js # Kész
│ └── repositories/
│ └── UserRepository.js # TODO (extends IUserRepository)
├── .env # Kész
├── package.json # Kész
└── server.js # Kész
Indítás
npm install
npx prisma generate
npx prisma migrate dev --name init
npm run dev
Szerver: http://localhost:3000
Feladatok
1. Prisma Schema (10 perc)
Fájl: prisma/schema.prisma
Definiáld a User modelt a TODO komment helyén:
model User {
id Int @id @default(autoincrement())
email String @unique
name String
password String
role String @default("user")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Migráció után ellenőrizd: npx prisma studio
2. UserRepository Implementáció (20 perc)
Fájl: src/infrastructure/repositories/UserRepository.js
A UserRepository már extends IUserRepository-t. Implementáld a metódusokat:
create(userData)
return await prisma.user.create({ data: userData });
findById(id)
return await prisma.user.findUnique({
where: { id: parseInt(id) }
});
findByEmail(email)
return await prisma.user.findUnique({
where: { email }
});
findAll()
return await prisma.user.findMany({
orderBy: { createdAt: 'desc' }
});
update(id, userData)
return await prisma.user.update({
where: { id: parseInt(id) },
data: userData
});
delete(id)
return await prisma.user.delete({
where: { id: parseInt(id) }
});
3. Domain Model (10 perc)
Fájl: src/domain/models/User.js
class User {
constructor(id, email, name, password, role, createdAt, updatedAt) {
this.id = id;
this.email = email;
this.name = name;
this.password = password;
this.role = role;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
isAdmin() {
return this.role === 'admin';
}
canEdit(targetUser) {
return this.isAdmin() || this.id === targetUser.id;
}
validate() {
if (!this.email || !this.name || !this.password) {
throw new Error('Email, name és password kötelező');
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(this.email)) {
throw new Error('Hibás email formátum');
}
if (this.password.length < 6) {
throw new Error('Jelszó min. 6 karakter');
}
}
}
4. Command Handlers (25 perc)
CreateUserHandler (src/application/user/command/CreateUserHandler.js)
const UserRepository = require('../../../infrastructure/repositories/UserRepository');
class CreateUserHandler {
constructor() {
this.userRepository = new UserRepository();
}
async handle(command) {
// 1. Validáció
if (!command.email || !command.name || !command.password) {
throw new Error('Email, name és password kötelező');
}
// 2. Email formátum
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(command.email)) {
throw new Error('Hibás email formátum');
}
// 3. Duplikáció ellenőrzés
const existing = await this.userRepository.findByEmail(command.email);
if (existing) {
throw new Error('Email már használatban');
}
// 4. Létrehozás
return await this.userRepository.create({
email: command.email,
name: command.name,
password: command.password,
role: command.role || 'user'
});
}
}
module.exports = CreateUserHandler;
UpdateUserHandler (src/application/user/command/UpdateUserHandler.js)
const UserRepository = require('../../../infrastructure/repositories/UserRepository');
class UpdateUserHandler {
constructor() {
this.userRepository = new UserRepository();
}
async handle(command) {
// 1. Létezés ellenőrzés
const user = await this.userRepository.findById(command.id);
if (!user) {
throw new Error('User nem található');
}
// 2. Email egyediség (ha változott)
if (command.email && command.email !== user.email) {
const existing = await this.userRepository.findByEmail(command.email);
if (existing) {
throw new Error('Email már használatban');
}
}
// 3. Frissítendő adatok
const updateData = {};
if (command.email) updateData.email = command.email;
if (command.name) updateData.name = command.name;
if (command.role) updateData.role = command.role;
// 4. Frissítés
return await this.userRepository.update(command.id, updateData);
}
}
module.exports = UpdateUserHandler;
DeleteUserHandler (src/application/user/command/DeleteUserHandler.js)
const UserRepository = require('../../../infrastructure/repositories/UserRepository');
class DeleteUserHandler {
constructor() {
this.userRepository = new UserRepository();
}
async handle(command) {
const user = await this.userRepository.findById(command.id);
if (!user) {
throw new Error('User nem található');
}
await this.userRepository.delete(command.id);
return { message: 'User törölve', id: command.id };
}
}
module.exports = DeleteUserHandler;
5. Query Handlers (10 perc)
GetUserHandler (src/application/user/query/GetUserHandler.js)
const UserRepository = require('../../../infrastructure/repositories/UserRepository');
class GetUserHandler {
constructor() {
this.userRepository = new UserRepository();
}
async handle(query) {
const user = await this.userRepository.findById(query.id);
if (!user) {
throw new Error('User nem található');
}
return user;
}
}
module.exports = GetUserHandler;
GetAllUsersHandler (src/application/user/query/GetAllUsersHandler.js)
const UserRepository = require('../../../infrastructure/repositories/UserRepository');
class GetAllUsersHandler {
constructor() {
this.userRepository = new UserRepository();
}
async handle(query) {
return await this.userRepository.findAll();
}
}
module.exports = GetAllUsersHandler;
Tesztelés
Endpoints
Create User
POST http://localhost:3000/api/users
Content-Type: application/json
{
"email": "test@test.com",
"name": "Test User",
"password": "password123"
}
Get All Users
GET http://localhost:3000/api/users
Get User by ID
GET http://localhost:3000/api/users/1
Update User
PUT http://localhost:3000/api/users/1
Content-Type: application/json
{
"name": "Updated Name"
}
Delete User
DELETE http://localhost:3000/api/users/1
Hibakezelés Tesztelése
- Hiányzó email/name/password
- Duplikált email
- Nem létező user ID
- Hibás email formátum
Architektúra
Layered Architecture
API Layer (routes, controllers)
↓
Application Layer (commands, queries, handlers)
↓
Domain Layer (models, interfaces)
↓
Infrastructure Layer (database, repositories)
CQRS Pattern
Command - Írás (Create, Update, Delete) Query - Olvasás (Get, GetAll)
Előnyök:
- Szeparált felelősségek
- Könnyebb skálázhatóság
- Tiszta intent
Parancsok
npm run dev # fejlesztői szerver
npm start # produkciós szerver
npx prisma generate # Prisma client generálás
npx prisma migrate dev # migráció
npx prisma studio # adatbázis GUI
Gyakori Hibák
Cannot find module '@prisma/client'
→ npx prisma generate
Port already in use
→ .env-ben módosítsd a PORT értékét
Not implemented → Implementáld a TODO-kat
Bónusz
- bcrypt jelszó hash-elés
- JWT authentikáció
- Request logging middleware
- Joi validáció
- Error handling middleware