{"id":25216321,"url":"https://github.com/serbanuntu/messenger","last_synced_at":"2026-04-10T06:33:00.742Z","repository":{"id":275198464,"uuid":"925341997","full_name":"SerbanUntu/messenger","owner":"SerbanUntu","description":"A lightweight instant messaging application built with Express.js","archived":false,"fork":false,"pushed_at":"2025-02-09T18:42:22.000Z","size":2669,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-05T09:15:11.083Z","etag":null,"topics":["express","jwt","postgresql","react","socket-io"],"latest_commit_sha":null,"homepage":"https://messenger-su.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SerbanUntu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-31T17:38:35.000Z","updated_at":"2025-02-09T09:12:44.000Z","dependencies_parsed_at":"2025-01-31T20:34:33.981Z","dependency_job_id":null,"html_url":"https://github.com/SerbanUntu/messenger","commit_stats":null,"previous_names":["serbanuntu/messenger"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/SerbanUntu/messenger","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SerbanUntu%2Fmessenger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SerbanUntu%2Fmessenger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SerbanUntu%2Fmessenger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SerbanUntu%2Fmessenger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SerbanUntu","download_url":"https://codeload.github.com/SerbanUntu/messenger/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SerbanUntu%2Fmessenger/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262969886,"owners_count":23392530,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["express","jwt","postgresql","react","socket-io"],"created_at":"2025-02-10T19:17:42.734Z","updated_at":"2025-12-30T19:57:16.078Z","avatar_url":"https://github.com/SerbanUntu.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Messenger\n\nA lightweight instant messaging application built with Express.js\n\n![Banner](/public/banner.jpg)\n\n## About\n\nThis chat application allows users to create conversations and send messages to each other in real time.\n\n**Features**:\n- One-to-one conversations\n- Group conversations (3 or more people)\n- Real-time updates when receiving messages or being added to conversations\n- Optimistic UI updates to minimize waiting for server operations\n- Website rendered as a SPA allowing navigation to different pages without a full browser refresh\n- Fully responsive UI (works on both mobile and desktop)\n- Account system\n\n## Technologies\n\n![Logos of used technologies](/public/stack.jpg)\n\n### Frontend\n\n- **React**: A framework for manipulating the DOM declaratively.\n- **v0 by Vercel**: An AI tool used to build user interfaces from prompts. It outputs Next.js code by default. I used it to build the UI very quickly.\n- **shadcn/ui**: A registry of unstyled reusable components. Used for more complex components such as toast notifications. Also used by v0.\n- **TailwindCSS**: A CSS library that includes utility classes to allow styling of components direclty in the HTML.\n- **Webpack**: A build tool used to bundle all the React code into a single minified, optimized and compressed plain Javascript file ready for production.\n\n### Backend\n\n- **Node.js**: A runtime for Javascript on the server (note that `ts-node` is used to run the Typescript code which is not natively supported by Node).\n- **Express.js**: A framework for handling server-side routing, browser requests, middleware and the responses sent back to the browser.\n- **Socket.io**: A library for handling Websocket connections for realtime updates to the messages and conversations.\n- **JSON Web Tokens**: A stateless way of managing user authentication with symmetric encryption.\n- **node-postgres**: A library for sending SQL queries to the Postgres database from the Node server.\n\n### Deployment\n\n- **Neon**: A Postgres serverless runtime built with Rust. I highly recommend their [Postgres tutorial](https://neon.tech/postgresql/tutorial).\n- **Google Cloud Run**: A cloud service that manages and runs the server instance. \n- **Docker**: A platform used to define a reproducible configuration for the server build, so it can be hosted anywhere.\n\n## What I Learned\n\n### Authentication with JWT\n\nFor this project I rolled out my own authentication to avoid importing a heavy library and to learn more about how to keep sensitive information secure.\n\nWhen the user logs in, the server will generate a new token using the `JWT_SECRET` environment variable, which acts as the encryption / decryption key.\n\nThe token stores the user id in its payload, so it can be used to fetch the user that made the request if needed.\n\n```ts\n// auth.ts\nexport const getCurrentUser = async (req: Request, res: Response) =\u003e {\n\tconst token = req.cookies.messenger_jwt\n\ttry {\n\t\tconst decoded = jwt.verify(token, process.env.JWT_SECRET!) as { user_id: number };\n\t\tconst user = await getUserById(decoded.user_id)\n\t\tres.status(200).json(user)\n\t} catch (err) {\n\t\tres.status(401).json({ error: (err as Error).message })\n\t}\n}\n```\n\n### Compression\n\nI fell into a bit of a compression rabbit hole while trying to get that 100 Performance Lighthouse score.\n\nI learned about the 4 main types of compression used by browsers (gzip, deflate, brotli and zstandard) and decided to also serve a Brotli-encoded variant of my bundled JS.\n\n```ts\n// webpack.config.ts\nnew CompressionPlugin({\n\tfilename: '[path][base].br',\n\talgorithm: 'brotliCompress',\n\ttest: /\\.(js|css|html|svg)$/,\n\tcompressionOptions: {\n\t\tlevel: 11\n\t},\n\tthreshold: 10240,\n\tminRatio: 0.8,\n\tdeleteOriginalAssets: false,\n}),\n```\n\nThe compression middleware in the `compression.ts` file will look into the `Accept-Encoding` header from the request to see if the browser supports Brotli.\n\nThis simple change reduces the size of the JS file sent over the network from 510 KB to **120 KB**.\n\n### Real-time Communication\n\nOn the server, I store the `Socket` objects for each connected user in a *hashmap*, so I can only target the necessary users that should be notified.\n\n```ts\n// websockets.ts\nconst sockets: Map\u003cnumber, Socket\u003e = new Map()\n\nexport const emitMessage = async (message: Message) =\u003e {\n\tconst targets = await getUsersInConversation(message.conversation_id, message.author_id) as { user_id: number }[]\n\ttargets.forEach(u =\u003e {\n\t\tconst socket = sockets.get(u.user_id)\n\t\tif (socket) {\n\t\t\tsocket.emit('message', message)\n\t\t\tconsole.log(`Message from user ${message.author_id} received by ${u.user_id}`)\n\t\t}\n\t})\n}\n```\n\n*Note: It might be possible to use channels and subscriptions to achieve a cleaner implementation*.\n\n### Deployment\n\nSince this application requires long-lived connections between the server and client for realtime updates, serverless providers such as Vercel weren't an option.\n\nI initially tried to upload the Github repo directly to Google Cloud Run but I didn't succeed. \n\nTo simplify the configuration, I got my server up and running using Docker before uploading it to Cloud Run again.\n\n## Further development\n\n- Light theme\n- Image sharing\n- Message reactions\n- Enhanced onboarding and usage instructions\n- Message encryption\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserbanuntu%2Fmessenger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserbanuntu%2Fmessenger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserbanuntu%2Fmessenger/lists"}