MyRelay is a ticket-based support platform with real-time chat per ticket.
The product has one app, one backend, and one database, but different users see different areas and actions depending on their role.
Implementation of backend architecture using:
- Node.js
- Express or NestJS later
- PostgreSQL
- Socket.IO
- Next.js
- Auth from scratch at app level
- Role-based permissions
- Real-time ticket chat
A user can create a support ticket with a description of their issue.
That ticket becomes a persistent record in the database and moves through a lifecycle:
openassignedresolvedclosed
Each ticket can also have a live chat room attached to it.
The ticket itself is created and managed through normal HTTP APIs.
The chat attached to the ticket uses WebSockets through Socket.IO.
This means the app is a combination of:
- ticket management system
- real-time support communication tool
- role-based internal dashboard
The public landing page introduces MyRelay and gives users a clear path into the product.
- simple marketing entry
- explain what the product is
- redirect users into the app
/→ landing page/login/register/app→ authenticated app area
Keep it simple:
- product name
- one-line description
- CTA button to sign in or open app
Example direction:
MyRelay
A real-time ticket and support workspace for teams.
Authentication is part of the app and should be implemented directly in the backend, but kept intentionally simple.
Learn practical app auth, not build a full auth platform.
- register
- login
- logout
- fetch current authenticated user
- protect routes
- protect socket connections
- support role-based access
There are 3 main roles:
useragentadmin
Can:
- register and log in
- create tickets
- view only their own tickets
- open their own ticket details
- join chat for their own ticket
- send messages on their own tickets
Can:
- log in
- access inbox
- view tickets they are allowed to handle
- join ticket chat rooms
- send replies
- assign tickets
- update ticket status
Can:
- access dashboard
- view all tickets
- view system-level stats
- view users and agents
- perform full management actions later
For v1, auth should stay small and boring:
- email + password
- password hashing with bcrypt
- JWT or cookie-based auth
- middleware for route protection
- middleware for role checks
Do not start with:
- forgot password
- social auth
- email verification
- 2FA
- advanced session management
The app is one product with different screens depending on role.
//login/register
/app/tickets/app/tickets/new/app/tickets/:ticketId
/app/inbox/app/inbox/:ticketId
/app/admin/app/admin/tickets/app/admin/users/app/admin/agents
Same app, same backend, same database.
The difference is:
- what each role can access
- what each role can do
- which screens each role sees
The ticket is the main business object.
A ticket is not just a message thread.
A ticket contains its own persistent details and can also have many chat messages attached to it.
A user creates a ticket using HTTP.
subject→ optionaldescription→ required
The backend stores the ticket with metadata such as:
- creator user id
- current status
- assigned agent id if any
- timestamps
- maybe priority later
- maybe source later
A new ticket starts as:
open
Main status flow:
openassignedresolvedclosed
For v1:
- ticket starts as
open - agent manually assigns ticket to self
- once assigned, ticket becomes
assigned - later agent can mark as
resolved - later ticket can be marked
closed
Do not auto-assign just because an agent opened the room.
Viewing is not the same as ownership.
Manual assignment is cleaner.
When opening a ticket detail page, the app should load the ticket and its chat data separately.
- ticket id
- subject
- description
- creator
- status
- assigned agent
- created time
- updated time
- message history
- message input
- live incoming messages
- system updates later if needed
The ticket description belongs to the ticket.
It is not required to be stored as a normal chat message.
In the UI, it can be shown as:
- a ticket context section at the top
- a pinned case summary
- an intro block before the messages
This keeps the domain model clean.
Each ticket can have a live chat room.
The room exists so that the user and the assigned or viewing agent can communicate instantly.
- HTTP creates and manages the ticket
- Socket.IO powers live communication inside that ticket
Each room should be tied to a ticket id.
Example:
ticket:{ticketId}
Possible future room patterns:
user:{userId}agent:{agentId}
Can join only if the ticket belongs to them.
Can join if they have permission to handle that ticket.
Can join if full admin access is allowed.
- user joins ticket room
- agent joins ticket room
- user sends message
- agent sends message
- both sides receive new messages instantly
- room receives status updates instantly
- room receives assignment updates instantly
ticket:jointicket:leaveticket:message:send
ticket:message:newticket:status:updatedticket:assignedticket:user_joinedticket:user_left
Optional later:
ticket:typing:startticket:typing:stop
Do not make sockets the main source of truth.
- create ticket
- list tickets
- fetch ticket detail
- fetch chat history
- assign ticket
- update ticket status
- live room connection
- realtime message delivery
- live status broadcasts
- live assignment broadcasts
- typing or presence later
That split is important.
The inbox is mainly for agents.
This is the operational area where they manage tickets.
- see available tickets
- see assigned tickets
- filter by status
- open a specific ticket
- assign ticket to self
- respond in chat
- update ticket status
Each ticket preview can show:
- ticket id
- subject
- creator name or email
- current status
- assigned agent
- created time
- updated time
- last message preview later
- unread count later
- open ticket
- assign to me
- mark resolved
- mark closed
The admin dashboard is for system-level visibility.
This should not become a fake analytics platform in v1.
Keep it practical.
- total tickets
- open tickets
- assigned tickets
- resolved tickets
- closed tickets
- total users
- total agents
- currently connected clients
- maybe active ticket rooms
Be precise.
Do not show vague labels like:
connections
Instead use clear labels like:
connected_clientsconnected_agentsactive_ticket_rooms
PostgreSQL is the source of truth.
The database should model tickets and messages as separate but related things.
Stores all authenticated app users.
Suggested columns:
idemailpassword_hashdisplay_namerolecreated_atupdated_at
Possible later:
last_seen_atis_active
Stores the support case itself.
Suggested columns:
idsubjectdescriptionstatuscreated_by_user_idassigned_agent_idcreated_atupdated_atresolved_atclosed_at
Possible later:
prioritysourcecategory
Stores live chat messages linked to a ticket.
Suggested columns:
idticket_idsender_user_idsender_rolecontentcreated_atupdated_at
Possible later:
message_typeis_internalattachment_url
- one user can create many tickets
- one agent can be assigned many tickets
- one ticket can have many messages
- each message belongs to one ticket
- each message is sent by one user
A ticket is the case.
Messages are communication within that case.
Do not collapse both concepts into one thing.
This is a suggested v1 HTTP API structure.
POST /auth/registerPOST /auth/loginPOST /auth/logoutGET /auth/me
POST /ticketsGET /ticketsGET /tickets/:ticketIdPATCH /tickets/:ticketId/statusPATCH /tickets/:ticketId/assign
GET /tickets/:ticketId/messages
Main message sending can happen through sockets.
If needed later, an HTTP fallback route can exist too.
GET /admin/statsGET /admin/ticketsGET /admin/users
- landing page
- login page
- register page
- my tickets list
- create ticket form
- ticket detail with chat
- inbox ticket list
- ticket detail with controls + chat
- dashboard overview
- tickets summary
- users summary
This is the recommended first build scope.
- register
- login
- me
- logout
- role guards
- create ticket
- list own tickets
- view own ticket
- chat on ticket
- list tickets in inbox
- open ticket
- assign to self
- send messages
- update ticket status
- basic dashboard stats
- live chat per ticket
- live status update
- live assignment update
That is enough for a strong v1.
Only after v1 is stable.
- typing indicators
- unread counts
- presence / online status
- internal agent notes
- search
- filters
- pagination improvements
- audit logs
- attachments
For the backend, prefer feature-based structure instead of splitting everything only by transport type.
Example direction:
src/
modules/
auth/
tickets/
messages/
users/
admin/
socket/
middleware/
lib/
app.ts
server.ts