Kompletné Backend API pre chatting aplikáciu typu Slack. Postavené na Elysia frameworku s Prisma ORM a PostgreSQL databázou.
- Runtime: Bun
- Framework: Elysia 🦊
- Database: PostgreSQL
- ORM: Prisma
- Authentication: JWT (Access & Refresh Tokens)
- Password Hashing: bcrypt (12 rounds)
- Token Security: SHA-256 hash pre refresh tokeny
- Registrácia s emailom a heslom
- Prihlásenie s JWT access & refresh tokenmi
- Odhlásenie (invalidácia refresh tokenu)
- Refresh endpoint pre obnovenie access tokenu
- Zobrazenie profilu prihláseného používateľa
- Vyhľadávanie ostatných používateľov
- JWT tokeny s claims: sub, userId, email, name, exp
- Vytvorenie 1-na-1 konverzácie
- Vytvorenie skupinových chatov
- Automatické generovanie default názvu (meno druhého usera pre 1-na-1, zoznam mien pre skupiny)
- Možnosť zmeny názvu konverzácie
- Pridávanie/odstraňovanie účastníkov
- Zoznam všetkých konverzácií používateľa
- Posielanie textových správ
- Odpovedanie na správy (reply feature)
- Zobrazenie histórie správ v konverzácii
- Pridávanie emoji reakcií na správy
- Každý používateľ môže mať jednu reakciu na správu
- Zmena reakcie (automatický update)
- Odstránenie reakcie
- 77 dostupných emoji
- Cursor-based pagination pre správy v konverzáciách
- Optimalizované pre real-time chat aplikácie
- Konzistentné výsledky aj pri novo pridaných správach
- Konfigurovateľný limit (1-100, default: 50)
- Správy zoradené od najnovších po najstaršie
- Všetky chat endpointy vyžadujú autentifikáciu
- Kontrola prístupu ku konverzáciám (iba účastníci)
- Input validácia na všetkých endpointoch
- Hashované heslá (bcrypt)
- Token-based sessions
- DTO pattern (nevracia citlivé údaje)
bun installVytvorte .env súbor v root priečinku:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/Clasmate?schema=public"
# JWT Secrets (v produkcii použite silné, náhodné reťazce)
ACCESS_TOKEN_SECRET="your-access-token-secret-change-in-production"
REFRESH_TOKEN_SECRET="your-refresh-token-secret-change-in-production"Dôležité: V produkcii vygenerujte silné, náhodné secret keys:
# Príklad generovania náhodných secretov
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"bun run db:push
bun run db:generatebun run db:seedbun run dev
# alebo
bun src/index.tsServer beží na: http://localhost:3000 🦊
| Endpoint | Method | Auth | Popis |
|---|---|---|---|
/auth/register |
POST | No | Registrácia nového používateľa |
/auth/login |
POST | No | Prihlásenie používateľa |
/auth/refresh |
POST | No | Obnovenie access tokenu pomocou refresh tokenu |
/auth/logout |
POST | Yes | Odhlásenie (invalidácia refresh tokenu) |
/auth/profile |
GET | Yes | Profil prihláseného používateľa |
/auth/users?search=text |
GET | Yes | Vyhľadávanie používateľov |
{
"email": "alice@example.com",
"password": "password123",
"name": "Alice"
}{
"user": {
"id": 1,
"email": "alice@example.com",
"name": "Alice"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 900
}{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}{
"user": {
"id": 1,
"email": "alice@example.com",
"name": "Alice"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 900
}| Endpoint | Method | Auth | Popis |
|---|---|---|---|
/chat/conversations |
POST | Yes | Vytvorenie konverzácie |
/chat/conversations |
GET | Yes | Zoznam všetkých konverzácií |
/chat/conversations/:id |
GET | Yes | Detail konverzácie |
/chat/conversations/:id |
PATCH | Yes | Zmena názvu konverzácie |
/chat/conversations/:id/participants |
POST | Yes | Pridanie účastníka |
/chat/conversations/:id/participants/:userId |
DELETE | Yes | Odstránenie účastníka |
{
"participantIds": [2]
}{
"participantIds": [2, 3, 4]
}{
"name": "Project Discussion"
}| Endpoint | Method | Auth | Popis |
|---|---|---|---|
/chat/conversations/:id/messages |
POST | Yes | Poslanie správy |
/chat/conversations/:id/messages |
GET | Yes | Zobrazenie správ s pagination |
{
"content": "Hello everyone!"
}{
"content": "I agree!",
"replyToId": 5
}GET /chat/conversations/:id/messages?limit=20&cursor=123
Query parametry:
limit(optional): Počet správ na stránku (1-100, default: 50)cursor(optional): ID správy pre ďalšiu stránku
Response:
{
"data": [
{
"id": 125,
"content": "Latest message",
"author": { "id": 1, "name": "Alice", "email": "alice@example.com" },
"replyTo": null,
"reactions": [],
"createdAt": "2024-01-15T10:30:00Z"
},
...
],
"pagination": {
"nextCursor": "105",
"hasMore": true,
"limit": 20
}
}Poznámky:
- Správy sú zoradené od najnovších po najstaršie (DESC)
nextCursorjenullak neexistujú ďalšie správy- Pre načítanie starších správ použite
cursorz predchádzajúcej odpovede - Cursor-based pagination zaručuje konzistentné výsledky aj pri real-time správach
| Endpoint | Method | Auth | Popis |
|---|---|---|---|
/chat/messages/:id/reactions |
POST | Yes | Pridanie/zmena reakcie |
/chat/reactions/:id |
DELETE | Yes | Odstránenie konkrétnej reakcie (len vlastnej) |
{
"emojiId": 1
}Poznámka: Každý používateľ môže mať max 1 reakciu na správu. Ak už reakciu má, automaticky sa zmení (upsert).
DELETE /chat/reactions/:reactionId
Bezpečnosť:
- Používateľ môže vymazať len svoju vlastnú reakciu
- Vymaže sa vždy len jedna konkrétna reakcia (podľa ID)
- Pokus o vymazanie cudzej reakcie vráti chybu: "You can only delete your own reactions"
| Endpoint | Method | Auth | Popis |
|---|---|---|---|
/chat/emojis |
GET | Yes | Zoznam všetkých dostupných emojis |
API používa JWT (JSON Web Tokens) s dvojitým token systémom pre maximálnu bezpečnosť:
- Typ: JWT (JSON Web Token)
- Expirácia: 15 minút
- Použitie: Autentifikácia API requestov
- Claims:
sub: User ID (string)userId: User ID (number)email: User emailname: User nameiat: Issued at (timestamp)exp: Expiration (timestamp)
- Typ: JWT (JSON Web Token)
- Expirácia: 7 dní
- Použitie: Obnovenie access tokenu
- Uloženie: Hash (SHA-256) v databáze
- Rotácia: Pri každom refresh sa generuje nový pár tokenov
1. Registrácia/Login:
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"pass123","name":"User"}'Response obsahuje oba tokeny:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 900
}2. Použitie Access Tokenu:
curl -H "Authorization: Bearer <accessToken>" \
http://localhost:3000/auth/profile3. Obnovenie Access Tokenu: Keď access token expiruje (po 15 min), použite refresh token:
curl -X POST http://localhost:3000/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken":"<refreshToken>"}'4. Logout:
curl -X POST http://localhost:3000/auth/logout \
-H "Authorization: Bearer <accessToken>"✅ Krátka expirácia access tokenu (15 min) - minimalizuje riziko pri úniku
✅ Dlhá expirácia refresh tokenu (7 dní) - pohodlné pre používateľa
✅ Refresh token hash v DB - aj pri úniku DB nie je priamo použiteľný
✅ Token rotation - každý refresh generuje nový pár tokenov
✅ Logout invaliduje refresh token - okamžité odpojenie
✅ JWT signed tokens - nemožno zmieniť bez secret key
curl -H "Authorization: Bearer 08umLUtivoaJzhif31fgNnb5FtnNVg" \
http://localhost:3000/auth/profilefetch('http://localhost:3000/chat/conversations', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})- Autentifikácia používateľov
- Email (unique), name, password (bcrypt hashed)
- RefreshTokenHash (nullable, unique) - SHA-256 hash JWT refresh tokenu
- Access token v JWT formáte (nie v DB, iba signed)
- Refresh token hash pre sessions (7 dní)
- Konverzácie medzi používateľmi
- Voliteľný vlastný názov
- Default názov generovaný automaticky
- Prepojenie User ↔ Conversation
- Jeden user môže byť v mnohých konverzáciách
- Jedna konverzácia môže mať mnohých účastníkov
- Unique constraint (userId, conversationId)
- Správy v konverzáciách
- Self-referential relation pre replies (replyToId)
- Cascade delete pri vymazaní konverzácie
- Reakcie používateľov na správy
- Každý user môže mať max 1 reakciu na správu
- Unique constraint (messageId, userId)
- Upsert logika (update or create)
- 77 predefinovaných emojis
- Symbol (👍) a name (thumbs_up)
src/
├── index.ts # Main app s global error handler
├── database.ts # Prisma Client instance
├── modules/
│ ├── auth/
│ │ ├── index.ts # Auth endpoints
│ │ ├── model.ts # Validation schemas
│ │ ├── dto/
│ │ │ └── user.ts # User DTO
│ │ └── middlewares/
│ │ └── authenticate.ts # Auth middleware
│ └── chat/
│ ├── index.ts # Chat endpoints
│ ├── model.ts # Validation schemas
│ ├── dto/
│ │ ├── conversation.ts # Conversation DTO
│ │ ├── message.ts # Message DTO
│ │ └── reaction.ts # Reaction DTO
│ └── middlewares/
│ └── conversationAccess.ts
└── utils/
├── generateToken.ts # Token generation (30 chars)
└── conversationName.ts # Default name logic
prisma/
├── schema.prisma # Database schema
└── seed.ts # Emoji seed script
API automaticky generuje názvy konverzácií:
Každý používateľ vidí meno druhého používateľa:
- Alice vidí: "Bob"
- Bob vidí: "Alice"
Každý používateľ vidí mená ostatných účastníkov (max 3):
- Alice vidí: "Bob, Charlie, David"
- Bob vidí: "Alice, Charlie, David"
Názov je možné zmeniť cez PATCH /chat/conversations/:id.
- Email: must be valid email format
- Password: min 6 znakov
- Name: min 2 znaky
- participantIds: min 1, max 50 účastníkov
- content: min 1 znak, max 5000 znakov
{
"status": "error",
"type": "validation",
"errors": [
{
"property": "Expected string length greater or equal to 6",
"message": "Expected string length greater or equal to 6"
}
]
}- Neprihlásený používateľ nemôže pristúpiť k chat endpointom
- Používateľ môže pristupovať iba ku konverzáciám, v ktorých je účastník
- Reakcie možné iba na správy v dostupných konverzáciách
- Používateľ môže vymazať len svoje vlastné reakcie (nie cudzie)
- Vymazanie reakcie je vždy po jednej (nie hromadné)
- Reply možný iba na správy v tej istej konverzácii
- Heslá hashované s bcrypt (12 rounds)
- JWT Access Token: 15 min expirácia, signed, nie v DB
- JWT Refresh Token: 7 dní expirácia, hash v DB (SHA-256)
- Access token obsahuje: sub, userId, email, name, iat, exp
- Refresh token rotation: nový pár pri každom refresh
- DTO pattern - nevracia password ani refreshTokenHash v listoch
- RefreshTokenHash nullable - po logout je null
- Input validation na všetkých endpointoch
- Type checking (TypeScript + Elysia)
- Unique constraints v databáze
- Email format validation
bun run dev # Dev server s auto-reload
bun run start # Production server
bun run db:generate # Generuje Prisma Client
bun run db:push # Synchronizuje schema s DB
bun run db:migrate # Vytvorí migrácie
bun run db:studio # Otvorí Prisma Studio GUI
bun run db:seed # Seed emojis do DB- Register: POST
/auth/register - Login: POST
/auth/login→ získaj token - Search Users: GET
/auth/userss Authorization headerom - Create Conversation: POST
/chat/conversationss participantIds - Send Message: POST
/chat/conversations/:id/messages - Add Reaction: POST
/chat/messages/:id/reactions - List Conversations: GET
/chat/conversations - Get Messages: GET
/chat/conversations/:id/messages
# 1. Register Alice
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"alice@test.com","password":"pass123","name":"Alice"}'
# 2. Register Bob
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"bob@test.com","password":"pass123","name":"Bob"}'
# 3. Alice creates conversation with Bob (userId=2)
curl -X POST http://localhost:3000/chat/conversations \
-H "Authorization: Bearer <alice_token>" \
-H "Content-Type: application/json" \
-d '{"participantIds":[2]}'
# 4. Alice sends message
curl -X POST http://localhost:3000/chat/conversations/1/messages \
-H "Authorization: Bearer <alice_token>" \
-H "Content-Type: application/json" \
-d '{"content":"Hello Bob!"}'
# 5. Bob replies
curl -X POST http://localhost:3000/chat/conversations/1/messages \
-H "Authorization: Bearer <bob_token>" \
-H "Content-Type: application/json" \
-d '{"content":"Hi Alice!","replyToId":1}'
# 6. Bob adds reaction (response vracia reactionId v message objektu)
curl -X POST http://localhost:3000/chat/messages/1/reactions \
-H "Authorization: Bearer <bob_token>" \
-H "Content-Type: application/json" \
-d '{"emojiId":1}'
# 6b. Bob removes his reaction (musí vedieť reactionId)
curl -X DELETE http://localhost:3000/chat/reactions/1 \
-H "Authorization: Bearer <bob_token>"
# 7. Get messages with pagination
curl "http://localhost:3000/chat/conversations/1/messages?limit=20" \
-H "Authorization: Bearer <alice_token>"
# 8. Get next page (use nextCursor from previous response)
curl "http://localhost:3000/chat/conversations/1/messages?limit=20&cursor=15" \
-H "Authorization: Bearer <alice_token>"User (1) ←→ (*) ConversationParticipant (*) ←→ (1) Conversation
User (1) ←→ (*) Message (*) ←→ (1) Conversation
User (1) ←→ (*) Reaction (*) ←→ (1) Message
Message (1) ←→ (*) Message (self-referential reply)
Emoji (1) ←→ (*) Reaction
{
"status": "error",
"type": "validation",
"errors": [...]
}{
"status": "error",
"message": "User already exists"
}Unauthorized
{
"status": "error",
"message": "Access denied"
}- Úprava modelov v
prisma/schema.prisma bun run db:pushpre syncbun run db:generatepre nové Prisma typy- Reštart servera (
bun run dev)