Files
GKNB_MSTM071/Backend/második gyakorlat/README.md
T
2026-02-18 21:02:39 +01:00

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

  1. bcrypt jelszó hash-elés
  2. JWT authentikáció
  3. Request logging middleware
  4. Joi validáció
  5. Error handling middleware