DyChat is a real-time one-to-one and group chat application built with a separate backend and frontend. This document tracks the project structure, major implementation phases, architecture decisions, feature flows, and run/deployment commands.
The project is split into two main applications:
dychat/
api/ -> Express, MongoDB, Redis, Socket.IO backend
web/ -> Vite React frontendThis separation keeps backend and frontend dependencies isolated, makes development easier, and allows either side to be deployed independently or merged for production serving.
The root .gitignore covers common generated and local files for both apps:
node_modules/: installed dependencies.dist/andbuild/: generated frontend build output..env: local environment secrets.- log files and editor/OS-specific files.
The root package.json is a convenience layer for running common scripts from the repository root:
npm run dev:api
npm run dev:web
npm run start:api
npm run build:webThe actual backend package is in api/package.json, and the frontend package is in web/package.json.
The backend uses Node.js, Express 5, MongoDB with Mongoose, Redis, Socket.IO, JWT authentication, and ImageKit.
Main backend folders:
api/src/
app.js
server.js
config/
controllers/
lib/
middlewares/
models/
routes/
services/
utils/
validations/express: HTTP API server.cors: cookie-authenticated frontend requests.dotenv: environment loading.mongoose: MongoDB models and queries.redis: token blacklist storage.socket.io: realtime events.bcryptjs: password hashing.jsonwebtoken: JWT signing and verification.cookie-parser: HTTP-only auth cookie parsing.express-validator: request validation.multer: multipart image upload handling.imagekit: profile and group image storage.
This file creates and configures the Express app:
- CORS with credentials.
- JSON and URL-encoded body parsing.
- Cookie parsing.
- API routes under
/api. - Static React build serving from
api/views. - Non-API route fallback to
api/views/index.html. - Not-found and global error middleware.
This is the backend runtime entry point. It:
- Connects MongoDB.
- Connects Redis.
- Creates the HTTP server.
- Attaches Socket.IO.
- Starts listening on the configured port.
The frontend uses React, Vite, React Router, Redux Toolkit, RTK Query, React Hook Form, Socket.IO client, and Lucide icons.
Main frontend folders:
web/src/
app/
error/
hooks/
initializers/
layouts/
pages/
routes/
store/
features/
auth/
chat/
profile/
users/
shared/
api/
services/reactandreact-dom: UI rendering.vite: development and production build tool.react-router-dom: routing.@reduxjs/toolkitandreact-redux: app state and RTK Query.react-hook-form: form management.async-mutex: single-flight auth refresh handling.socket.io-client: realtime client connection.lucide-react: UI icons.
Authentication is cookie-based. The frontend never stores access or refresh tokens in localStorage, sessionStorage, or Redux.
Backend sets HTTP-only cookies:
- access token cookie
- refresh token cookie
Frontend requests use credentials: "include", so cookies are sent automatically.
- User registers or logs in.
- Backend creates a refresh session document in MongoDB.
- Backend signs RS256 access and refresh JWTs.
- Backend sets HTTP-only cookies.
- Frontend stores only the safe
userobject. - On app start,
AuthInitializercallsGET /auth/me. - If access token is expired, RTK Query refresh guard calls
POST /auth/refresh. - If refresh succeeds, the original request is retried.
- If refresh fails, user state and RTK Query cache are cleared.
POST /api/auth/register
POST /api/auth/login
POST /api/auth/refresh
GET /api/auth/me
POST /api/auth/logout
POST /api/auth/logout-all
PATCH /api/auth/profile
PATCH /api/auth/password
PATCH /api/auth/avatar
DELETE /api/auth/avatarJWTs use RS256:
- private key signs tokens.
- public key verifies tokens.
- keys are loaded from base64 environment variables.
Expected environment keys:
JWT_PRIVATE_KEY_BASE64=
JWT_PUBLIC_KEY_BASE64=Authenticated users can manage their profile from the private app sidebar.
Features:
- View profile modal.
- Update display name.
- Read-only email.
- Update password using current password.
- Upload or replace profile picture.
- Remove profile picture.
- Logout current session.
- Logout all sessions.
Profile images use Multer memory storage and ImageKit. Allowed formats are JPG, PNG, and WEBP, with a 5MB upload limit.
The protected user search API lets users find other users before starting a conversation.
Backend route:
GET /api/users/search?q=<query>Behavior:
- Requires authentication.
- Searches by name or email.
- Excludes the current user.
- Returns safe serialized user objects.
- Limits results to 12 users.
Frontend behavior:
- Sidebar search icon opens the search modal.
- Search input is debounced.
- Results show user avatar, name, email, and
Start chatbutton. Start chatcreates or reveals a direct conversation but does not auto-open the chat window.
The private app renders a chat workspace with:
- Thin left app sidebar.
- Conversation list panel.
- Active chat window.
- Conversation search.
- Header with avatar, name, and status.
- Message bubbles.
- Typing indicator.
- Composer with attachment button, text input, and send button.
The top navbar was removed in favor of the permanent left icon sidebar.
Direct chat is implemented end to end.
Backend routes:
GET /api/conversations
POST /api/conversations/direct
GET /api/conversations/:conversationId/messages
POST /api/conversations/:conversationId/seen
POST /api/messages
DELETE /api/messages/:messageIdConversation.participantsstores both users.Conversation.visibleTocontrols who sees the conversation in the sidebar.- When user A starts a chat with user B, only user A sees the conversation initially.
- When the first message is sent, the conversation becomes visible to both users.
The send-message flow was optimized for realtime speed:
- Backend verifies the sender belongs to the conversation.
- Backend immediately emits a pending realtime message with the same
clientTempId. - Backend saves the message in MongoDB.
- Backend updates conversation metadata.
- Backend emits the final DB-backed message.
- Frontend replaces the pending/optimistic message using
clientTempId.
This reduces the perceived delay on production networks while keeping the database as the source of truth.
Direct and group chats support private media/file messages end to end.
Supported attachment kinds:
- image
- video
- audio
- generic file
Privacy model:
- Frontend sends the selected file as multipart form data under the
attachmentfield. - Backend verifies the sender belongs to the conversation.
- Backend emits a pending message quickly so realtime UI feels responsive.
- Backend uploads the file to ImageKit with
isPrivateFile: true. - Backend saves only attachment metadata in MongoDB:
- ImageKit file id
- ImageKit private file path
- original name
- MIME type
- size
- attachment kind
- Backend does not send a permanent public file URL in message payloads.
- When the frontend needs to render or download the attachment, it requests a short-lived signed URL from the backend.
- Backend checks that the requesting user is still a participant in the conversation before generating the signed URL.
Signed URL route:
GET /api/messages/:messageId/attachments/:attachmentId/urlThe signed URL expires quickly, so if a link is leaked it only works for a short time. This is the selected balance between chat privacy and CDN performance.
- Unread count is calculated from messages not sent by the current user and not read by the current user.
- Conversation list shows unread count.
- Opening a conversation marks incoming unread messages as seen.
- Sidebar chat icon shows how many conversations have unread messages.
- Sender can right-click their own message and choose
Unsend message. - Backend marks the message as deleted instead of removing the document.
- The message remains in the timeline as
This message was deleted.
Socket.IO is authenticated with the same HTTP-only access cookie.
Connection flow:
- Socket handshake reads cookies.
- Access token is verified.
- Token blacklist and refresh session are checked.
- User is attached to the socket.
- Socket joins
user:<id>room. - First active socket marks user online.
- Last disconnected socket marks user offline and updates
lastSeen.
Server emits:
conversation:created
conversation:updated
conversation:removed
message:new
message:deleted
messages:seen
typing:started
typing:stopped
user:presenceClient emits:
typing:start
typing:stopGroup chat is implemented on top of the direct chat system.
Features:
- Sidebar actions menu with
New ChatandNew Group. - Group creation modal.
- Group name.
- Optional group display picture.
- User search and multi-select members.
- Creator becomes group admin.
- Group appears immediately for all selected members.
- Offline members see the group after login.
- Group messages show sender name.
- Group typing indicator shows the typing user's name.
- Group header hides call button and shows leave button.
- Leaving a group creates a centered system message.
Backend group routes:
POST /api/conversations/groups
POST /api/conversations/:conversationId/leaveGroup conversation fields:
type: "group"nameavatar.urlavatar.publicIdadminsparticipantsvisibleTo
System messages use type: "system".
Clicking the group name/avatar in the chat header opens the group details modal.
Admin users can:
- Update group name.
- Update group display picture.
- Add members.
- Remove members.
- Delete the group.
Normal members can:
- View group details.
- View member list.
- Leave the group.
Normal members cannot see edit controls.
Backend group management routes:
PATCH /api/conversations/:conversationId/group
POST /api/conversations/:conversationId/members
DELETE /api/conversations/:conversationId/members/:memberId
DELETE /api/conversations/:conversationId/groupAll group management APIs require the current user to be a group admin.
The frontend can be served by the backend server.
Production frontend API URL:
VITE_API_URL=/apiReason:
- API calls use the same backend origin.
- Auth cookies work cleanly as same-origin cookies.
- Socket.IO also uses the same origin.
Build flow:
$env:VITE_API_URL="/api"
npm --prefix web run buildThen copy web/dist contents into:
api/views/
index.html
assets/Express serves:
/api/*as backend APIs./healthas health check.- static frontend assets from
api/views. - all non-API routes through
api/views/index.html.
Production start command:
npm --prefix api startIf Render's root directory is set to api, use:
npm startBackend dev server:
npm --prefix api run devFrontend dev server:
npm --prefix web run devFrontend production build:
npm --prefix web run buildBackend production server:
npm --prefix api startRoot convenience scripts:
npm run dev:api
npm run dev:web
npm run build:web
npm run start:apiCompleted:
- Backend Express server.
- MongoDB connection.
- Redis token blacklist.
- RS256 JWT auth.
- Cookie-based access and refresh flow.
- Auth initializer and route guards.
- Profile management.
- ImageKit profile avatar upload/remove.
- User search.
- Direct conversation creation.
- Realtime message delivery.
- Fast pending-message emit before DB completion.
- Typing indicators.
- Online/offline presence.
- Seen/unseen read receipts.
- Sidebar unread conversation count.
- Message unsend/delete state.
- Private ImageKit media/file messages.
- Participant-checked signed URLs for attachments.
- Group creation.
- Group messaging.
- Group leave flow.
- Group management modal.
- Admin-only group edit/member/delete APIs.
- Production frontend build served from
api/views.
Not added yet:
- Rate limiting.
- Email verification.