<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Olusi Jackson</title>
    <description>The latest articles on DEV Community by Olusi Jackson (@olusi_jackson_52199637ef3).</description>
    <link>https://dev.to/olusi_jackson_52199637ef3</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3965345%2F90c19e0f-c6ed-42da-b66e-4ece444f1d73.jpg</url>
      <title>DEV Community: Olusi Jackson</title>
      <link>https://dev.to/olusi_jackson_52199637ef3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/olusi_jackson_52199637ef3"/>
    <language>en</language>
    <item>
      <title>How My Team from Risevest Academy Built an End-to-End Encrypted Messaging App in 3 Weeks</title>
      <dc:creator>Olusi Jackson</dc:creator>
      <pubDate>Tue, 02 Jun 2026 22:23:06 +0000</pubDate>
      <link>https://dev.to/olusi_jackson_52199637ef3/how-my-team-from-risevest-academy-built-an-end-to-end-encrypted-messaging-app-in-3-weeks-4hma</link>
      <guid>https://dev.to/olusi_jackson_52199637ef3/how-my-team-from-risevest-academy-built-an-end-to-end-encrypted-messaging-app-in-3-weeks-4hma</guid>
      <description>&lt;p&gt;This project was Victor's brainchild, and it came a a result of our mentor asking each of us to come up with ideas to bring to reality. Victor's is the second project to be worked upon by the team (I hope I'm not the next).&lt;/p&gt;

&lt;p&gt;Three weeks are up, and Victor's idea of a real-time, end-to-end encrypted messaging platform with WebSocket messaging, media sharing, push notifications, and a functioning encryption layer within such a short time has come to fruition.&lt;/p&gt;

&lt;p&gt;Where We Started&lt;br&gt;
Before I take the reader too far, it is important to state that I was regrettably not able to contribute significantly to the codebase due to recurring health issues, and I do not want to unintentionally take credit by writing this piece. All I was involved in was the base architectural planning, research and decision making as was every other team member.&lt;br&gt;
The initial idea looked simple on paper; build a functional chat app, with privacy as the most important concern. Chats would be encrypted on the sender's device and could only be decrypted by the recipient's, such that not even us could read those messages. I personally was very insistent on how securely we handled user's data, because I was fed up of apps telling me they can't read my messages on the server but they conveniently can when there's a subpoena.&lt;/p&gt;

&lt;p&gt;I personally think that this added a layer of complexity to the project that we could not correctly anticipate at the time. Each of us went ahead to research popular chat apps and their methods of encryption to see if we would straight up copy or modify.&lt;br&gt;
This and more led to the stack we initially agreed upon;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;br&gt;
 I mean this is a no-brainer for literally any project that is written in js these days. Type safety is a superhero and it saves time and lives, and it definitely saved us from a lot of troubles in development. strict mode was non-negotiable. One very neat thing I noticed in the implementation was making sure the frontend caught the types used in the backend to ensure that the data flow ends to end was an expected set of values, reminded me of tRPC&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Express.js&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another no-brainer if you ask me. We were working with a very short timeline, so it was better to go with a widely known and acceptable framework. It comes with a lot of things out of the box - routing, middlewares etc. and it made development iterations easier and faster as we didn't need to reinvent the wheel to have a functional backend up and running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL and Knex&lt;/strong&gt; Considering our data model, postgres is a standard industry choice because of how relational our data was. Users to conversations to members to messages, are dimensionally relational in one or more ways.&lt;br&gt;
 Upon research, Knex was also very convenient for using Typescript for the project. I honestly have to doff my hat to the Knex team. If one didn't know, they'd think Knex was an ORM and not a query builder. It managed our migrations without any issues while being super fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Socket.io&lt;/strong&gt; &lt;br&gt;
I honestly feel like this is a contender for top two no-brainer decisions in our architecture. Real-time messaging has to work or the app is useless. Not only is Socket.io perfect for our situation, it goes further with room abstraction which allows for sockets to be able to join group chats, which leads me to the next;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt;&lt;br&gt;
When we have multiple server instances, a message arriving at server A needs to reach a user connected to server B. Redis pub/sub is the channel between them, and this let's us scale the process(es).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BullMQ&lt;/strong&gt;&lt;br&gt;
 Looking into this actually mirrors how Stripe payments work under the hood. When a payment is completed, events are emitted to a defined websocket, which in turn processes the events received. However, if it fails for any reason, it retries sending the events and for each failed retry, the time for the next retry increases geometrically. A very similar concept is applied here for offline message delivery. There is a queue that manages messages for recipients who are offline. The queue pushes out messages when their recipients are online. This was actually a genius suggestion from Victor himself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firebase&lt;/strong&gt;&lt;br&gt;
 I personally dislike platforms like Firebase and Supabase because of the abstractions and levels of fine-grained ownership not available to you, but our timeline couldn't let us be choosy. So we had Firebase for the following: Authentication through firebase phone verification, and Cloud Messaging for handling push notifications. The initial plan was to use Firebase Storage for media but we switched to Cloudinary midway through because of its built-in transformation pipeline (and Cloudinary is just goated fr).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Most Important Architecture Decision?&lt;/strong&gt;&lt;br&gt;
As previously stated, we wanted to take user's security and privacy very seriously, so we arrived at a common point: the server would store ciphertext and nothing else.&lt;/p&gt;

&lt;p&gt;Encryption and decryption happen entirely on the client&lt;br&gt;
If and when our database is breached, the attacker gets encrypted blobs they cannot read.&lt;br&gt;
The server cannot comply with a request to hand over users' messages because it genuinely does not have them neither does it have the capacity to decrypt what it has.&lt;/p&gt;

&lt;p&gt;This is what the expected data flow should look like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;user A sends a message to user B&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The frontend fetches the user B's public key from the backend and a temporary key pair is generated for this specific message.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ECDH key agreement derives a shared secret using the temporary private key and user B's public key, and the sent message is encrypted with an encryption protocol using that shared secret.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then, the encrypted blob is sent to the backend which stores it without reading it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User B's frontend receives the blob, derives the same shared secret from the other side, and decrypts locally.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I see that the team eventually switched to using Web Crypto API and I'm still looking into why as of the time of the writing of this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issues I faced&lt;/strong&gt;&lt;br&gt;
As previously mentioned, I couldn't contribute significantly to the codebase, but before I fell sick, I had the chance to try and work on migrating our internal auth system for both user session and socket session verification to JWT. Firebase Auth was deeply entrenched into the system and it was somewhat troublesome to implement our system alongside the original Firebase Auth system.&lt;/p&gt;

&lt;p&gt;The baseline is that I'm proud of the team for pulling off what they did within such a short period even with one person down. Special kudos to Victor, I particularly look forward to working with him.&lt;br&gt;
Of course, I can't discard the contributions of our chief security officer; Chuka, and our senior backend engineer; Purpose.&lt;br&gt;
Last but certainly not the least, this project could not have gotten here without the feedback loop of our mentor, Glory. &lt;/p&gt;

&lt;p&gt;What's Next?&lt;br&gt;
I believe that would be Voice and Video calls integration, and whatever comes next would be Victor's prerogative. &lt;/p&gt;

&lt;p&gt;I believe there is room for improvement for us all, and we're on track to becoming forces of nature.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>backend</category>
      <category>node</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
