<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Engineering Out Loud]]></title><description><![CDATA[I build things. I struggle. I learn. I ship.]]></description><link>https://blog.nabilridhwan.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1670169260049/xkLkeu8M7.png</url><title>Engineering Out Loud</title><link>https://blog.nabilridhwan.com</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 18 May 2026 03:17:17 GMT</lastBuildDate><atom:link href="https://blog.nabilridhwan.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Spotify vs Apple Music: The intersection of digital music and social features.]]></title><description><![CDATA[My stint with Apple Music has come to an unfortunate end. I’m not trying to make it sound like a huge breakup note reveal. I’m actually writing this to reflect on my time with both services.
It was an]]></description><link>https://blog.nabilridhwan.com/spotify-vs-apple-music-the-intersection-of-digital-music-and-social-features</link><guid isPermaLink="true">https://blog.nabilridhwan.com/spotify-vs-apple-music-the-intersection-of-digital-music-and-social-features</guid><category><![CDATA[Spotify]]></category><category><![CDATA[AppleMusic]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Mon, 06 Apr 2026 10:36:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/622a81e2e552e99d82e59796/6e78f0dd-593d-4173-b5ae-64e92667bc49.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My stint with Apple Music has come to an unfortunate end. I’m not trying to make it sound like a huge breakup note reveal. I’m actually writing this to reflect on my time with both services.</p>
<p>It was an impromptu decision. I was watching Juxtaposed’s video about “redesigning Apple Music’s UI” and that got me wondering about how much Apple Music lacks that Spotify already has.</p>
<p>The reason I initially switched was due to two things: The tight integration of Apple Music and the Apple ecosystem in general. Like the fullscreen background on the lockscreen – those used to look beautiful! and it’s the sound quality. Apple Music just provides lossless. Even though I still use bluetooth to this day. And also other features such as the karaoke feature and beautiful lyrics screen.</p>
<p>However, the novelty died off. The ‘tight’ integration just doesn’t impress me anymore. Every single new feature added to Apple Music was just considered nice to have. Not revolutionary.</p>
<p>I kept reading online about how one service is better than the other due to some codecs and streaming compression and Apple’s chokehold of the whole audio pipeline in Apple Music but that could just be confirmation bias (even though it might be factual)</p>
<p>So... For the first few hours of switching back, I had to make sure to squash my confirmation bias by A/B testing between the two services. I’m not so much worried about the low ends because these DO come through on the AirPods Max. I’m more worried about the high end, especially in a busy track. I’ve determined that both of them is indistinguishable to my ears. So yup, audio quality is about the same (on a bluetooth connection anyways)</p>
<p>So what did I miss from Apple Music? The karaoke feature, aptly named ‘Sing’, the iCloud syncing feature which allowed me to sync and listen to my bought albums on my other devices (i.e. Breach: Digital Remains by Twenty One Pilots) and most probably the official sets playlist and videos.</p>
<p>But the bigger question is: what did I miss from Spotify when I was on Apple Music? Music recommendation. I don’t mean it like playlist recommendations from the home page. I mean the autoplay music that comes after a song. Apple Music SUCKED at it. It kept playing music that I would only name ‘safe choices.’ All the artists that play after are your top 100 charts artists. I don’t even think genre was even considered in Apple Music’s autoplay. Secondly is Spotify Connect/Jam – this one’s huge. Being able to control music playing on my computer through my phone must be magical. And Spotify Jam? Makes it easier for me and my friends to hang around and listen to music together. I finally don’t feel like the lone-Apple-Music guy.</p>
<p>Spotify has always been playlist and social-centric while Apple Music is put out there to do one thing: For you to listen to music. Recommendations are not their priority (and screw you people who keep saying to give Apple Music some time to learn about your taste. I have done EXACTLY that – 4 months to be precise). And that’s fine. Maybe that works for some people. I, am however, a gen z, with gen z friends who’s backbone is Spotify and I shall be with my kind.</p>
<p>And that is my take on Spotify vs Apple Music. The intersection of digital music and social features.</p>
]]></content:encoded></item><item><title><![CDATA[Building a RAG System with ChromaDB: Why My Embeddings Failed (And How I Fixed It)]]></title><description><![CDATA[Author’s Note
I’ve been gone for long.
I’ve been serving my nation (National Service) and have been using the time to relax, learn different skills and upgrading myself in different ways.
Unexpectedly, I also went full-on into my other hobby… Playing...]]></description><link>https://blog.nabilridhwan.com/building-a-rag-system-with-chromadb-why-my-embeddings-failed-and-how-i-fixed-it</link><guid isPermaLink="true">https://blog.nabilridhwan.com/building-a-rag-system-with-chromadb-why-my-embeddings-failed-and-how-i-fixed-it</guid><category><![CDATA[RAG ]]></category><category><![CDATA[AI]]></category><category><![CDATA[LLM-Retrieval ]]></category><category><![CDATA[llm]]></category><category><![CDATA[chromadb]]></category><category><![CDATA[singapore]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Sun, 15 Feb 2026 10:59:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/zFy6fOPZEu0/upload/2937ee8197d5a4ccf054e449f6b8e976.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h3 id="heading-authors-note">Author’s Note</h3>
<p>I’ve been gone for long.</p>
<p>I’ve been serving my nation (National Service) and have been using the time to relax, learn different skills and upgrading myself in different ways.</p>
<p>Unexpectedly, I also went full-on into my other hobby… Playing the guitar and the bass.</p>
<p>Hence, catch me playing bass and guitar on:</p>
<ul>
<li><p><a target="_blank" href="https://www.tiktok.com/@billy.onthebass">TikTok</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/@billyonthebass">YouTube</a></p>
</li>
</ul>
<hr />
<h1 id="heading-ai-is-so-cool">“AI IS SO COOL!”</h1>
<p>Not long ago, I was hellbent on learning AI.</p>
<p>Well, to be fair, I caved in with the whole marketing jigamajig.</p>
<p>Other than the whole “AI bubble” thing, I’ve always wanted to learn AI. Intelligence in computers has always sparked something in me.</p>
<p>Moreover, I was convinced (at the time I graduated Poly) that to succeed in the IT sector space, one has to either choose between Cybersecurity or AI. Being a Software Developer in the big ‘23 was outdated. Like everybody can build a node.js server and set up some SaaS and just piece it together…</p>
<p>After I graduated, I took this unofficial course on YouTube that kick-started my ML learning journey.</p>
<p>It was a “Learn PyTorch in 24 hours” where I only could make it halfway, 12 hours before giving up because of how technical it was for me.</p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbzhpa3U0dTJvNTNpaDlpbXozNmIyM3dhZzJmeW1pYmxkM3psNmlodiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/4I87mfQncdPGdSQhXc/giphy.gif" alt class="image--center mx-auto" /></p>
<p>Time skip to 2026, I decided to jump on it again.</p>
<p>I’ve always been inspired by how people use AI to do insane things but I never understood how.</p>
<p>So I did some research and came across a new term: “AI Engineer”.</p>
<p>My initial thoughts was “<em>What is that?”</em> and I thought an AI Engineer is the same as ML Engineers except AI Engineers build LLMs.</p>
<p>But that was when I discovered that the meaning of “AI Engineer” keeps changing and the current one is just a Software Engineer who uses the capabilities of LLMs and other technologies to streamline mundane tasks and processes.</p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExOHVoNzYwdndtZmszcnNhanZnaHYyNjBhcndmb3V5Njk5OWJtYjU0cyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/qAtZM2gvjWhPjmclZE/giphy.gif" alt class="image--center mx-auto" /></p>
<p>During my research about AI Engineers, I come across this course on Datacamp titled “Associate AI Engineers for Developers” and thought <em>“Great! I’m a Developer, and this course teaches me about being an associate AI engineer! At least the courses treats me like a developer so I don’t need to learn everything from scratch”</em></p>
<p>So I took the course (well, it was a free 3 month trial)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771152494235/18a20e9d-554a-4185-9341-880790bbf523.png" alt class="image--center mx-auto" /></p>
<p>Time jump to 3 weeks later, after learning majority of the except for the LangChain stuff, I decided to use my skills.</p>
<p>So far, here's the summarised version of what I've learned that I think is important:</p>
<ul>
<li><p>Prompt engineering</p>
</li>
<li><p>RAG</p>
</li>
<li><p>Vector databases</p>
</li>
<li><p>Embeddings</p>
</li>
<li><p>LLMOps (testing, model selection, RAG vs fine-tuning, model costs and etc.)</p>
</li>
</ul>
<h1 id="heading-applying-my-skills">Applying my skills</h1>
<p><strong>Let me introduce to you the problem</strong>: I'm serving NS and I take care of the Ops Room.</p>
<p>That's my vocation: Ops Room.</p>
<p>Our job, among many things, is to take care of the keys to the whole of the training center. <strong>We issue alot of keys daily.</strong></p>
<p>The problem lies in the loads of important key information that is somehow left out and hard to keep track.</p>
<p>Here’s what I mean by that: throughout the months that I've been serving, keys and information tend to change. Sometimes they're no longer stored in the ops room. Sometimes keys go by different names. Sometimes you need to ask permission to issue out a certain key. And these information is relayed once and sometimes gets lost in the sauce.</p>
<p>Sure, a seasoned experienced person like me would know all these information and can blurt out information at lightning speed but what about the newcomers? (I mean, to be fair, they can just store these knowledge in their notes)</p>
<p><strong>Hence the solution, building an RAG system using the information of keys and other important contextual information.</strong></p>
<p>To be fair, the problem isn’t even a real issue to begin with but you know me… I like to hone my skills by solving a non-existent problem.</p>
<p>It’s like the saying goes, <em>“I take 10 hours to build a solution that takes me 2 minutes by hand every day.”</em></p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExdWhuZGNld3ZpaXV6ajk1dzd3M2U1a3kyZDdndG12ZXN3bXgzam1nZyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/NcrhM3USM6TABpus85/giphy.gif" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-disclaimer-privacy">Disclaimer: Privacy</h1>
<p>Before working on this project, I understand the importance of privacy. Especially the information that I’ve been working with. Which is why I made an effort to:</p>
<ul>
<li><p>Scramble data when interacting with online services</p>
</li>
<li><p>Censor sensitive information in this blog post (i.e. make it as vague as possible)</p>
</li>
<li><p>I made sure all of this is ran locally on my machine!</p>
</li>
</ul>
<hr />
<h1 id="heading-data-processing">Data processing</h1>
<p>I sat down in my chair. Back arched into my desk. Furiously typing and looking for relevant files to feed the vector database while thinking about processing the data for the embeddings.</p>
<p>Yes, there's the problem of <em>what MEANINGFUL data to embed</em> but before that I was already stuck about converting the master-list excel file into a structured data</p>
<p>FYI: All record of keys and what rooms they're for is kept in an excel master-list file that has multiple sheets, one for each section of the training center. It is not as simple as your typical CSV file – some pages have a subheading that merged the cells and whatnot. It's not a simple "read excel file from python" problem.</p>
<p>Introducing... ChatGPT! Yep – I used the skills I learned in Prompt Engineering to format my excel file into a JSON format I wanted.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771151152610/4dfa6287-9d26-403a-b99a-444f8711408b.png" alt /></p>
<p>In the end, I wrote a quick <code>process_keys.py</code> file that helped me take the multiple json files and combined them into one json filed called <code>merged_keys</code> and looks somewhat like this:</p>
<pre><code class="lang-json">[
    ...,
    {
        <span class="hljs-attr">"bunch_no"</span>: <span class="hljs-string">"930D"</span>,
        <span class="hljs-attr">"no_of_keys"</span>: <span class="hljs-number">8</span>,
        <span class="hljs-attr">"location"</span>: <span class="hljs-string">"DORM 93"</span>,
        <span class="hljs-attr">"remarks"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"key_section"</span>: <span class="hljs-string">"'D' - DORMS"</span>,
        <span class="hljs-attr">"building"</span>: <span class="hljs-string">"DORM BLOCK"</span>,
        <span class="hljs-attr">"id"</span>: <span class="hljs-string">"D_93"</span>
    },
    ...
]
</code></pre>
<blockquote>
<p>Note that the information on top has been scrambled with false information. The structure remains true.</p>
</blockquote>
<h1 id="heading-embedding-data">Embedding data</h1>
<blockquote>
<p>With the help of the lectures from the Datacamp course, I used <strong>ChromaDB</strong> as my Vector Database, ChatGPT-ed and asked for the best embedding model. It's my choice because there is a persistent store and useful for quick prototypes. Don’t need to run a docker container and whatnot.</p>
</blockquote>
<p>At first, when embedding data, I embedded all the information I got from the keys.</p>
<p>The original JSON structure looked like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"bunch_no"</span>: <span class="hljs-string">"930D"</span>,
    <span class="hljs-attr">"no_of_keys"</span>: <span class="hljs-number">8</span>,
    <span class="hljs-attr">"location"</span>: <span class="hljs-string">"DORM 93"</span>,
    <span class="hljs-attr">"remarks"</span>: <span class="hljs-string">""</span>,
    <span class="hljs-attr">"key_section"</span>: <span class="hljs-string">"'D' - DORMS"</span>,
    <span class="hljs-attr">"building"</span>: <span class="hljs-string">"DORM BLOCK"</span>,
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"D_93"</span>
}
</code></pre>
<p>Each key was down to a string that looked like this:</p>
<pre><code class="lang-plaintext">Bunch No: 930D
Number of keys: 8 key(s)
Location: DORM 93
Remarks: 
Key Section: 'D' - DORMS
Building: DORM BLOCK
...
</code></pre>
<p>I was ready to test out my big masterpiece next-gen AI app that will crown me the best tech adopter in the whole of Singapore!!!</p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExYWcxOWh4bzJldjhreGUwYmJzNzlseG1iMHlpaWJkaHZ6bW03cjdpcCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/okLCopqw6ElCDnIhuS/giphy.gif" alt class="image--center mx-auto" /></p>
<h1 id="heading-when-life-give-you-lemons">When life give you lemons...</h1>
<p>You squeeze it into your eyes! Just kidding.</p>
<p>Did you really thought my first test would pass?</p>
<p>It failed miserably. Queries were returning unexpected documents.</p>
<p>For example, when asked "What is the keys to bunk 930D"</p>
<p>It would return god-knows-what key that has nothing to do with bunk 930D</p>
<p>I was “debugging” furiously.</p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExcnk3bWs3OWM0NnY5OHBwM2xudDhiMnF6aTVzNzBxYTdnOHl0ZGh4ZSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/C2GtNxNdOfprXhUJGR/giphy.gif" alt class="image--center mx-auto" /></p>
<p>To me, this whole “embedding model” thing is a black box. You can't really debug the semantics that the embedding model captured.</p>
<p>Sure, you can visualise the embeddings on a graph but whatfor? (correct me if I'm mistaken)</p>
<p>I went back to ChatGPT and asked <strong>"What is the best embedding model that can be ran locally?"</strong></p>
<p>I was so sure that it was the embedding model that didn't capture my search query semantics – maybe it was dumb enough to not understand "bunk 930D" as "dormitory 930D" but well, I'm not any smarter either.</p>
<p>I did a quick switch of embedding models and ran it again, and…</p>
<h1 id="heading-do-you-not-understand">Do you not understand?</h1>
<p>It worked! I’m kidding!</p>
<p>Nope, it didn’t.</p>
<p>Yeah… I was baffled.</p>
<p>I thought a better embedding model would understand the nuances of the word “dorm” being interchangeable with "dormitory" or "bunk".</p>
<p>I went back to the drawing board.</p>
<p>I thought hard about how do I make these models understand these nuances.</p>
<p>Do I have to train my own model to do so? Will that take so much of my time?</p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZ3BjOXhlN3FxbThuOGw2bG85MW9nNXJ0N3g0aDFjcDYyMmVuOHZuYyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/Oc4KnIJ3E7ziqN3l6T/giphy.gif" alt class="image--center mx-auto" /></p>
<p>I was convinced it was somehow the embedding model <strong>UNTIL I asked ChatGPT during my research process.</strong></p>
<p>I asked whether my semantics were captured, why it did not return what I expected and if the issue is with the dimensionality of the embeddings.</p>
<p>ChatGPT, being the supportive friend it is (/s), told me that there wasn't ever an issue about embeddings but could be a myriad of other issues:</p>
<ul>
<li><p>Metric for search (dot product vs cosine similarity)</p>
</li>
<li><p>Vector normalisation</p>
</li>
<li><p>Embedded text data</p>
</li>
<li><p>... and many more</p>
</li>
</ul>
<h1 id="heading-embed-or-not-to-embed">Embed or not to embed?</h1>
<p>Okay, now I’m convinced it’s the data that I’m embedding</p>
<p>I went back to my code and went back to the <code>embed_keys.py</code> file.</p>
<p>I looked at the initial data that was sent for embedding – ChatGPT told me to embed information that’s concise but store all the other important information in the metadata of the document.</p>
<p>So I did just that.</p>
<p>From each key being given a embedding data of:</p>
<pre><code class="lang-plaintext">Bunch No: 930D
Number of keys: 8 key(s)
Location: DORM 93
Remarks: 
Key Section: 'D' - DORMS
Building: DORM BLOCK
...
</code></pre>
<p>I reduced it to:</p>
<pre><code class="lang-plaintext">DORM 93. Key number/bunch number: 930D. 8 key(s). DORM BLOCK Building. Remarks:  'D' - DORMS.
</code></pre>
<p>✅ Concise and understandable.</p>
<p>Maybe that's what the embedding model wants: A concise sentence that captures the information!</p>
<p>I excitedly re-embedded all the documents and tested it out to query for keys for bunk 930D...</p>
<p>It didn't work again.</p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExODFobTBwNDkwbzM3MzM2eDgxNzdyZDd6dmdzN25jcmRxYWt1djJqcyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/lnBuZsAZ1wN3i/giphy.gif" alt class="image--center mx-auto" /></p>
<p>I went through all that nail-biting, toe-curling research using ChatGPT and Google just to find out that the changes I made didn't work.</p>
<p>Don't even get me started about how I hard I had to research on how to delete a ChromaDB collection.</p>
<p>You might think what in the world is going on...</p>
<p>Here's the rundown</p>
<ul>
<li><p>First, I wanted to create an RAG to practice the skills I've learned</p>
</li>
<li><p>So I decided to solve a (non-existent) issue – the nuances and context with keys in my building. How easy it would be to just ask the Chatbot "What keys are bunk 930?" and it'll return the relevant information.</p>
</li>
<li><p>Hence, I built RAG system using ChromaDB and a local embedding model</p>
</li>
<li><p>After embedding, I tried asking the vector DB "What keys are bunk 930" but it didn't work</p>
</li>
<li><p>I was baffled because I thought embedding models are supposed to understand the nuances of ‘bunk, dorm, dormitory’ but it didn't</p>
</li>
<li><p>I went through a nail-biting research session using ChatGPT and Google because embedding models are a black box and these type of issues are not easily debuggable.</p>
</li>
<li><p>ChatGPT suggested (among other potential issues) that maybe the data I'm embedding for each key might be too verbose.</p>
</li>
<li><p>I went back to reduce the embedding data for each key making it concise. Concise one sentence like "DORM 930. KEY/BUNCH NUMBER 930D. DORM BLOCK"</p>
</li>
<li><p>I was excited to test again by asking "What keys are bunk 930?"</p>
</li>
<li><p>Did NOT work</p>
</li>
</ul>
<p>Yeah, that sums it up.</p>
<p>I was close to giving up. Maybe it was built to answer YouTube questions (it’s a joke pointing to how one of the lectures required us to code out a RAG system querying from a YouTube video, description, title and links dataset) and not answer questions about the keys.</p>
<p>So, I finally took a break. I went about my day. Every single thing I do, the problem haunts me, nudging me every second.</p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWF6YnM5YnF5YnBuMXZvd3pnaGRjOHR3Mm04MnhsZWw1YXBrcDNsZSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/yqehUC7MbTcx4a7WCZ/giphy.gif" alt class="image--center mx-auto" /></p>
<p>I caved in to the hauntings.</p>
<p>I thought about it...</p>
<p>How do I make them understand that when I ask "What keys are bunk 930?", I actually want the program to understand that I'm asking for Dorm 930 keys.</p>
<p>And something clicked!</p>
<blockquote>
<p>“If RAG was a system to retrieve relevant documents (all it returns are just documents and context for the LLM to understand), then this means I can add my own context to be embedded!”</p>
</blockquote>
<p>I opened up my laptop.</p>
<p>I created another file called <code>info.json</code> that contains all important context for keys.</p>
<pre><code class="lang-json">[
    ...,
    {
        <span class="hljs-attr">"remarks"</span>: <span class="hljs-string">"dorm 930 is also known as dormitory 930 or bunk 930 which is bunch 930d at dorm block"</span>,
        <span class="hljs-attr">"id"</span>: <span class="hljs-string">"dorm_930_info"</span>
    },
    ...
]
</code></pre>
<p>and finally...</p>
<h1 id="heading-the-win">The Win</h1>
<p>I cleared all the database items and re-embedded all the information and asked the vector database "What keys are bunk 930?" and LO AND BEHOLD! It returned the information correctly!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771152015534/be2018cb-30dd-4b50-a218-4ca82993137f.png" alt /></p>
<h1 id="heading-so-whats-next">So, what's next?</h1>
<p>Well, now that I got the prototype RAG built.</p>
<p>I will</p>
<ul>
<li><p>build a more robust system using other vector DBs like Pinecone.</p>
</li>
<li><p>using LangChain to use LLMs so answers are nicely structured when asked (obviously, there's more to make out of it and it can expand to other domains – not just asking about keys in the place)</p>
</li>
</ul>
<p>Yeah, that’s all I could think about as of writing.</p>
<p>Well. That's about it for now. Catch y'all soon!</p>
]]></content:encoded></item><item><title><![CDATA[I Helped Build a Roblox Cybersecurity Game for an Open House — Here’s What Broke]]></title><description><![CDATA[Special Thanks
I want to extend my special thanks to the team members that I’ve met, Russell, Bryan, Dinie and other team members who helped out too! Along with the wonderful and friendly Republic Poly lecturers that I’ve met (especially Nona, Firman...]]></description><link>https://blog.nabilridhwan.com/using-roblox-to-teach-students-cybersecurity-concepts</link><guid isPermaLink="true">https://blog.nabilridhwan.com/using-roblox-to-teach-students-cybersecurity-concepts</guid><category><![CDATA[roblox]]></category><category><![CDATA[ #roblox studio]]></category><category><![CDATA[DevBlogging]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Tue, 25 Jun 2024 10:56:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443991167/ae44f1cd-e840-4b41-94b1-6c3458620e20.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-special-thanks">Special Thanks</h3>
<p>I want to extend my special thanks to the team members that I’ve met, Russell, Bryan, Dinie and other team members who helped out too! Along with the wonderful and friendly Republic Poly lecturers that I’ve met (especially Nona, Firman and my favorite photographer – RJ).</p>
<p>Dinie: <a target="_blank" href="https://www.linkedin.com/in/muhammad-dinie-bin-baharudin-7a1680213/">https://www.linkedin.com/in/muhammad-dinie-bin-baharudin-7a1680213/</a></p>
<p>Russell: <a target="_blank" href="https://www.linkedin.com/in/russell-low-b94b44210/">https://www.linkedin.com/in/russell-low-b94b44210/</a></p>
<p>Bryan: <a target="_blank" href="https://www.linkedin.com/in/bryan-chan-146a1820b/">https://www.linkedin.com/in/bryan-chan-146a1820b/</a></p>
<h2 id="heading-preface">Preface</h2>
<p>We have reached the end of the life of our game. Our game life has extended beyond just the open house in January 24'.</p>
<p>Recently, on May 20th, 2024, we concluded the final session for secondary school students to introduce them to the Diploma in Cybersecurity and Digital Forensics (DCDF), formerly known as the Diploma in Information Security (DISM).</p>
<p>This blog post recalls the timeline from when I started entering the project until the end. <strong>I am not from Republic Polytechnic, and my involvement in this project is not on record (i.e., I don’t receive CCA points for this; I’m doing it of my own will).</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716444181793/59fa40f7-2105-4e73-8f7c-92a79e1b0003.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-introduction">Introduction</h1>
<p>Back then, I was in the middle of my internship, towards the final stages where we were supposed to finalize the last features of our mobile app, get it built, shipped, packaged, and sent to the app stores. I knew I had some free time, so I said yes.</p>
<p>I said yes for two reasons.</p>
<p>First, Dinie was admirably convincing, and the idea he pitched was awesome. Second, I had the opportunity to learn about Lua, Roblox programming, the Roblox game infrastructure, and game development design practices. Game development had scared me away for the longest time, but I figured that it's just Roblox—what harm could it bring?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443282880/8354036f-da83-44f4-bbe2-881c07a4d04e.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-before-that-a-quote-from-the-ogs">Before that, a quote from the OGs</h2>
<p>I joined the project at a later stage (in November), let's hear it from Dinie, Russell and Bryan who has been there since day one (and are in the same school)</p>
<blockquote>
<p>Back in the first half of year two, Dinie, Bryan, and I toyed with the idea of creating a Roblox game for our final year project (FYP). While the concept was exciting, I secretly doubted that our school would ever allow us to make a Roblox game, thinking it was too far-fetched.</p>
<p>As time passed without much discussion between us, I joined another FYP group, believing that the Roblox game idea was just a pipe dream.</p>
<p>Soon after, at the end of the half of year three, and after our FYP projects were done for, Dinie invited me to work on his (separate) game for the RP Open House 2024. This time, I didn't hesitate. I gratefully accepted the offer to be part of this exciting project.</p>
<p>– Russell Low, Team Member</p>
<p>Back in April 2023 when this project began, some people doubted our ability to create an interactive 3D cybersecurity capture the flag game, claiming it had never been done before. Despite these doubts, we not only made it happen but also demonstrated our capability to bring this CTF game to life, proving them that nothing is impossible.</p>
<p>– Bryan Chan, Team Member</p>
</blockquote>
<h1 id="heading-so-what-can-i-bring-to-the-team">So, what can I bring to the team?</h1>
<p>I asked Dinie, "What I could bring to the team?"</p>
<p>He answered, stating that he was the sole developer for the game and wanted me on board to create a really cool functionality for the game.</p>
<p>The functionality is the ability to 'trip' the room of another player, causing their room to lose power. This would halt any progress they were making in attacking other players. The affected players would then have to either shoot the person who tripped their power or solve a puzzle on the laptop in their hotel room.</p>
<p>As he explained each detail, my brain couldn't help but create a mental diagram of how this would work. I knew I would need several things: a 'lever' for the player to pull and a state system for the rooms to keep track of which rooms are 'tripped.'</p>
<p>But then I stopped myself. I had obviously forgotten that this was my first experience in game development and that I needed to learn.</p>
<h1 id="heading-learning-lua-game-development">Learning <s>Lua</s> Game development</h1>
<p>Yes, I would say that my fundamentals in programming are strong – not just concepts such as conditionals and loops, but also design patterns. Learning Lua was not that hard. Sure, the syntax is much different than what I am used to – but isn't that what Google is for? However, the one thing that caught me was the Roblox ecosystem.</p>
<h3 id="heading-what-do-you-mean-by-roblox-ecosystem">What do you mean by Roblox ecosystem?</h3>
<p>It's the idea of data storage and persistence via DataStores, server scripts that run on the server, local scripts that run on the client, properties and attributes of items, modifying the properties and attributes programmatically, and many more. These ideas are hard to grasp, so I took it slowly, asking Dinie for a little leniency as I explored and learned about Roblox.</p>
<p>The first thing that came to mind as I searched for a solution was the fact that I wanted to establish data persistence. That's when I asked the group chat for assistance. I drew what I wanted in Excalidraw and messaged the group chat asking for the best way to store this data.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443321564/f26fc8fe-6707-46d9-8890-6bd314e9ee52.png" alt class="image--center mx-auto" /></p>
<p>Upon further research, I found that using a DataStore is the best method for my use case. Between starting the feature and finishing it, I hopped on calls with Dinie to clarify and gain his thoughts, because I've got to respect him as a Roblox game developer. These back-and-forth ‘meetings’ are what I call productive meetings because each one has a goal. We lay down our thoughts, mix both of our thoughts, and the cycle continues.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443358850/4a32847a-24df-4919-9aed-70c0a499a356.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-showcasing-to-the-school-of-infocomm-republic-polytechnic">Showcasing to the School of Infocomm, Republic Polytechnic</h3>
<p>At this point in time, we have reached a milestone; most of the features are done, and the game is at its most playable state. We need to attend a showcase for the Operations/Business side of the school. Dinie asked if I could come along, and I agreed. I wanted to meet the lecturers who put in their time outside of work to mentor the team. But uniquely, I have to play a guise. I can’t let them know that I am a student from Singapore Polytechnic.</p>
<p>That day, I met the cutest lecturer, Nona. She reminds me of my grandma. The way she was enthusiastic about her ‘escape room’ and showing where she hid the ‘scroll’ was just way too cute. Her enthusiasm, chuckle, and her pause, waiting for our reactions to her genius-ness, was cute!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443693722/64cf1fe1-5be2-4715-adf8-8e45515ec1a4.png" alt class="image--center mx-auto" /></p>
<p>I went with Dinie to the showcase, and along with us were the projects from other diplomas, such as a VR simulator for a server room and a blockchain exchange website by the Diploma in Financial Technology. These projects really opened up my eyes to the people with all their eagerness to showcase their projects!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443435203/57400df8-bf0b-4119-96d8-49e7a9d7e310.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443455812/3f9ebd10-bd29-43e6-b83d-bf21b846db1e.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443494907/f64915da-1a06-4875-ac79-05833ef6c9b6.png" alt class="image--center mx-auto" /></p>
<p>At the showcase, we were given some feedback regarding the playability of the game and how players might find it confusing – especially when we need to stick with 20-minute sessions on the day of the Open House itself. We took this feedback personally but constructively too. Dinie and I brainstormed ideas and went back to the drawing board.</p>
<p>We relayed the feedback and the ideas to the team, where the team gladly gave their input. <em>The power of teamwork, am I right?</em></p>
<p>In a future iteration, Dinie added an introduction cutscene to the game, which is mind-blowing because it has cool camera animations (that even measly Nabil couldn’t do).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443522926/6bb4e9bb-c90c-4a8c-81a7-46fa5bcaa976.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-refactoring-leading-to-bugs-leading-to-refactoring-and-leading-to-bugs">Refactoring, leading to bugs, leading to refactoring and leading to bugs.</h3>
<p>Have you ever encountered a scenario where you have to refactor code, but after refactoring, new bugs are introduced, creating a vicious cycle? It sure as hell happened in this project.</p>
<p>For instance, we had a play-test between the 4 team members over Discord, where we actually put the actual gameplay to use. A short session of 1 hour harvested so many gameplay issues and bugs that I needed to fix (because it was my fault).</p>
<p>Mind you, this was way past the time of me coding the project, so I had to open up the project and look at the mess of the code I had written. This is where the refactoring part comes in. I refactored most of the code to use RemoteEvents – remote events are self-explanatory; they are events that can be called from server to server and vice versa. There is also another concept called BindableEvents where it can only be called client to server and vice versa.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443740215/49058986-0a42-4dec-8dfb-f6a0c6f9947f.png" alt class="image--center mx-auto" /></p>
<p>This cycle continued until I got it to a stable state. Other than that, we just have to lay low and wait for the day of the open house</p>
<h1 id="heading-the-day-of-the-open-house">The day of the open house</h1>
<p>The day has come; all the hard work our team put in has led to this moment. For three full days, we showcase our project at our course booth, promoting the DISM (now known as DCDF) booth.</p>
<p>At first, I found it strange. I am not a student from Republic Polytechnic. Heck, I’m not even from the same course. I am from the Diploma in Information Technology. I build apps and websites for a living – sure, I do have some experience in cybersecurity, but not much. I was nervous.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443827451/90dc511f-be5d-40bf-a40f-8ec1563991ee.png" alt class="image--center mx-auto" /></p>
<p>But my nervousness quickly died as I reached our booth, just in time for the briefing. Everybody had one job: to keep the booth alive. Our Roblox game was held in sessions in the lab room, but meanwhile, we took care of our mini booth dedicated to showcasing the game.</p>
<p>I took on a different role in the open house; I helped out in the escape room booth that Nona made.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443778657/621933f6-23ee-4605-8dd5-88583e0efccf.png" alt class="image--center mx-auto" /></p>
<p>It was fun. I learned about steganography and discovered that you can hide text in images. Having to put on a facade to the prospective students and parents of the students, saying that there is an ongoing attack in RP and that we need to find the date and time of the attack, was both tiring and really fun. I engaged with a lot of prospective students, so much so that I lost myself in the sauce; I literally told them to join RP DISM. What was I doing? Is this the purest form of betrayal? Shouldn’t I be promoting my school instead?</p>
<p>This goes on for all three days of the open house.</p>
<h3 id="heading-the-sessions-in-the-lab">The sessions in the lab</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443808191/ef63f706-eec8-4e2a-b6a1-0aa311cb8ca5.png" alt class="image--center mx-auto" /></p>
<p>As I said, we held game sessions in the lab. Like the saying goes, 'anything that can go wrong, will go wrong' – Murphy's Law. That is exactly what happened. For three days, six (or more) sessions were filled with the game breaking. Dinie and I fixed the game in 30 minutes during our lunch break. It was intense, more intense than the time we spent building the game. This time we were constrained by time: 30 minutes to fix the game before the session started for the day. I am, however, pleasantly surprised by Dinie’s quick thinking ability. He converted what was already a broken game into an engaging one by changing the rules of the game to be based on the maximum number of points instead of attacking/defending.</p>
<h3 id="heading-my-friends-russell-and-bryan">My friends, Russell and Bryan.</h3>
<p>These people are the friendliest I've met in RP (not that I have many friends from RP anyways). We slacked when nothing was going on and talked about the most random stuff. They were an absolute delight to be around.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443880052/15f60b7f-dfeb-48cc-aac2-fb7629769531.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-debrief">Debrief</h3>
<p>After three tiresome days, we ended with a debrief led by Dinie, where we discussed the future of the game. I also extended my thanks to the team members for forming a connection, a friendship that I’ll remember despite meeting only for these three short days. The immense amount of pressure, time, and all the fun things we did in these three days!</p>
<h1 id="heading-the-future-of-the-game">The future of the game</h1>
<p>The open house ended on January 6th, 2024. Between then and around April 2024, we didn’t touch the game at all. We led our lives until recently. We somewhat went back to the drawing board and fixed what needed to be fixed. To my surprise, the game is being used outside of the open house. Our game, which shamelessly was broken, is being used to promote the diploma. I never got that opportunity in my own school. Being able to do that in another school felt both wrong and empowering.</p>
<p>In a recent Discord call, we gathered all four team members: me, Dinie, Bryan, and Russell, and we discussed and talked for a bit. The main point of the call was to discuss what needed to be done in preparation for May 20th, when a group of secondary school students will come to RP for a tour.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716443905326/59dad50f-7d72-463b-84fb-9ce208b04c2d.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This is too much to conclude. Six months, to be precise. But it opened my eyes. I learned a lot from kids on YouTube, teaching me Roblox Studio to the team members who are determined, to learning about soft skills from Dinie and the other team members – and lastly, to forming a friendship between me, Bryan, and Russell. This is one of the projects that I will never forget. From being a total noob in Roblox and game development to coming out confident about the ins and outs of Roblox game development.</p>
<p>Let's hear it from the OGs again for their conclusion of the project</p>
<blockquote>
<p>I am grateful to be given this opportunity to develop an interactive 3D cybersecurity capture the flag game together with my teammates Dinie, Joharie, Nabil and Russell.</p>
<p>Developing a game from scratch presented numerous challenges, including defining game concepts, gameplay mechanics, target audience, development timelines, and marketing strategies.</p>
<p>Throughout the development process, challenges were encountered where things didn't always go as planned. Despite these obstacles, we persevered, ultimately bringing the game to life.</p>
<p>Our effort, dedication and hard work paid off when our game was selected to showcase at GovWare 2023, Republic Polytechnic open house and secondary school students.</p>
<p>This game wouldn’t be possible without the collective effort from the team. I would like to extend my heartfelt thanks to everyone for their contribution in making this game project possible.</p>
<p>– Bryan Chan, Team Member</p>
<p>Working on the game has been a wonderful experience. I had the privilege of collaborating with amazing people, including Nabil from Singapore Polytechnic, an extremely skilled programmer who significantly contributed to the game's success and was just in general an incredibly fun dude to be around.</p>
<p>As the person spearheading the project, I am extremely impressed by Dinie's ability to lead the team in achieving his vision for the project, think on the fly, and keep a level head in fixing issues despite the immense time constraints and pressure.</p>
<p>Bryan, who designed the 3D assets for the game, made the game look unbelievably stunning with his meticulous attention to detail and artistic flair. The vibrant and immersive lighting in the environments he created added a level of polish that truly brought our game to a new level.</p>
<p>Outside of our team, the open house also afforded me the opportunity to connect with many other people outside of my cybersecurity discipline who shared a common interest in application development. It was inspiring to see the diverse projects and innovations from students across different fields, and it gave me a broader perspective on the possibilities within tech development.</p>
<p>Looking back, I'm glad I seized this second chance. Being part of this Roblox Capture the Flag game has not only been fulfilling but also a testament to following one's passion and approaching new opportunities with an open mind.</p>
<p>– Russell Low, Team Member</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Is Swift Worth Learning in 2024? A React Native Developer’s Honest Take]]></title><description><![CDATA[Preface
I have been a React developer for the majority of my years.
I've witnessed React evolve from Class Components to Stateless Functional Components, and then to what we have now: Functional Components with Hooks.
While this article might seem bi...]]></description><link>https://blog.nabilridhwan.com/is-learning-swift-worth-it-my-experience-with-swift-as-a-prior-react-native-developer</link><guid isPermaLink="true">https://blog.nabilridhwan.com/is-learning-swift-worth-it-my-experience-with-swift-as-a-prior-react-native-developer</guid><category><![CDATA[iOS]]></category><category><![CDATA[Swift]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[React Native]]></category><category><![CDATA[Expo]]></category><category><![CDATA[Mobile apps]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Tue, 18 Jun 2024 04:38:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718684039206/d12a877a-624a-4051-b8ab-3409360e1fa9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h2 id="heading-preface">Preface</h2>
<p>I have been a React developer for the majority of my years.</p>
<p>I've witnessed React evolve from Class Components to Stateless Functional Components, and then to what we have now: Functional Components with Hooks.</p>
<p>While this article might seem biased, I assure you that I strive to be reasonable, logical, and objective in any judgments and conclusions I make. I pour my heart and mind into this endeavor.</p>
<p>What you are about to read is just my two cents and should not be taken to heart.</p>
<p>You can view the repository for Thoughtful <a target="_blank" href="https://github.com/nabilridhwan/thoughtful-ios">here</a>.</p>
<p>With that, the story begins prior to WWDC 2024.</p>
<hr />
<h1 id="heading-about-a-week-before-wwdc24">About a week before WWDC24...</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718623842584/f8b63ed6-8538-4112-86d9-2ffa0bffd93a.png" alt class="image--center mx-auto" /></p>
<p>It's about a week away from WWDC 2024. News outlets and YouTube channels are scrambling to be the first to upload reported leaks about the new iOS 18. Features such as customization of the control center, which surfaced last year, are making a comeback.</p>
<p>Amidst all this chaos, Apple Developer's YouTube channel started bulk uploading WWDC 2023 sessions. At that time, it was my first time ever watching a WWDC session, so I clicked on the most interesting video and gave it a watch.</p>
<p>I was pleasantly surprised. Even though I don't really understand Swift, I could infer the language syntax. What caught my eye was how seamlessly Apple's libraries and frameworks work together. It's like butter. Everything just works! Need a map? Import MapKit. Need tips to appear in your app? TipKit!</p>
<p>While watching the session video, I remembered that I had bookmarked an official tutorial guide by Apple, "Develop in Swift," which I had been longing to follow.</p>
<p>Deep down, I had bookmarked the tutorial just in case I got bored and needed something to do. Additionally, I had been eager to learn Swift and iOS app development after following a Udemy course but gave up halfway through.</p>
<p>Since WWDC 2024 is about a week away, I decided to take my chances and follow the tutorial. Maybe I could keep track of WWDC 2024's new Platform State of the Union changes?</p>
<h1 id="heading-is-develop-in-swift-any-good">Is "Develop in Swift" any good?</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718622710990/5e481dbb-a4b5-405a-814a-2f2fa48f0365.png" alt class="image--center mx-auto" /></p>
<p>I don't know who is the target audience for the guide but all I knew was that it wasn't targeted towards individuals who have never programmed in their life.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718622734617/28900f09-3e81-4301-b5cc-7683a458d1ac.png" alt class="image--center mx-auto" /></p>
<p>Even I had trouble wrapping my head around some of Swift's concepts, such as closures and trailing closures. It's really a syntax issue that I need to get used to. Other than that, it is strongly-typed and comes with neat features like computed properties.</p>
<p>The tutorial guide covers the basics of Swift, building an app in SwiftUI, integrating data persistence using SwiftData, and more. <strong>One thing worth highlighting is that the analogies used in the tutorial guide are easy to grasp and really good. They make difficult concepts digestible!</strong></p>
<p>I, shamelessly, haven't completed the tutorial guide in full and stopped after the part on SwiftData as I tried to integrate these concepts into a project I started long ago, but this time, on iOS.</p>
<h1 id="heading-learning-swift-through-building-an-old-project-i-started">Learning Swift through building an old project I started</h1>
<p>For those that didn't know, in February/March this year, I decided to impart some of the skills and knowledge I learned throughout my Mobile App Dev internship by building an app called "Thoughtful." Thoughtful is a daily gratitude journal app that prompts the user to journal about their life.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718622775922/1501a152-5db2-4246-b9f7-96abf6f83035.png" alt class="image--center mx-auto" /></p>
<p>After a few days of designing in Figma and perfecting every user interaction, I built the app in Flutter. I stored the data in SQLite, on-device, and used Flutter BloC for state management. While it was a huge learning curve to understand BloC (I still detest BloC to this day), my states are kept in sync, and that is what is important.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718622931070/c3b39a2a-1509-4cad-bb60-a9675a9aa223.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718622811869/cde566da-e465-40ed-a910-f4c7eabf7ec1.png" alt class="image--center mx-auto" /></p>
<p>Now that you know about Thoughtful in Flutter, I tried to build Thoughtful for iOS. It was a complete redesign (though sticking to some elements) and adhered to Apple's Design Guidelines.</p>
<blockquote>
<p>Yes, I know that Flutter is cross-platform. I built Thoughtful for iOS as a way to cement my knowledge from the tutorial guide.</p>
</blockquote>
<h1 id="heading-what-i-loved-about-swift-swiftui-and-swiftdata-as-compared-to-react">What I loved about Swift, SwiftUI and SwiftData (as compared to React)</h1>
<h2 id="heading-declarative-approach-to-building-user-interfaces">Declarative approach to building User Interfaces</h2>
<p>As I was developing using Swift and SwiftUI, I couldn't help but notice how similar it is to Flutter. Both SwiftUI and Flutter have a declarative approach to laying out a UI.</p>
<blockquote>
<p>Declarative, in this context, means laying out a UI involves me <strong>"telling"</strong> SwiftUI what I want instead of <strong>"making"</strong> the element in SwiftUI into what I want.</p>
<p>In this case, declarative would be <code>Button().background{RoundedRectangle(cornerRadius: 24)}</code>.</p>
<p>Imperative would be React-like where we have to declare a Component ("Button") and use styling to style the Button – i.e. I make the Button in React into what I want</p>
<p><code>&lt;Button style={styles.buttonStyles} /&gt;</code></p>
</blockquote>
<p>This declarative approach makes it easy to prototype UI but at the same time can make the codebase messy as styles might be everywhere (instead of having one source of truth, like a stylesheet file).</p>
<p><strong>However, it is nice to know that colors (such as background or foreground) are propagated throughout the children when applied at a top-level view.</strong></p>
<h2 id="heading-packages-tend-to-work-or-at-least-the-ones-i-used-do">Packages tend to work, or at least the ones I used do</h2>
<p>A big gripe I will forever have with React Native (and Expo) is that packages would break after upgrading either React Native's or Expo's version.</p>
<p>With every Expo SDK upgrade, I would spend more time modifying components, installing compatible packages, or replacing packages.</p>
<p>There was a time when I had to debug for 1.5 hours because the <code>react-svg-loader</code> package broke, and the error message didn't have anything to do with the package.</p>
<p>On the other hand, almost everything is built into SwiftUI. Tooltip? Tab Navigation? Stack Navigation? It's built-in. No need to install packages that might break one's workflow.</p>
<h2 id="heading-swiftdata-is-a-lifesaver">SwiftData is a Lifesaver</h2>
<p>SwiftData is a way for developers to persist data in their apps. The underlying technology is SQLite (the same as in Thoughtful Flutter), but this "wrapper" allows us to read, insert, update, and delete as if we're interacting with objects and classes rather than doing what we're used to as developers (ID lookups and whatnot). It's really easy!</p>
<pre><code class="lang-swift">
<span class="hljs-comment">// Read "Item" from storage and sort it by the date_created</span>
@<span class="hljs-type">Query</span>(<span class="hljs-built_in">sort</span>: \<span class="hljs-type">Item</span>.date_created) <span class="hljs-keyword">var</span> items: [<span class="hljs-type">Item</span>] = []

<span class="hljs-comment">// Insert</span>
modelContext.insert(<span class="hljs-type">Item</span>(...))

<span class="hljs-comment">// Update – Notice how I didn't even have to touch the modelContext as the Item struct has all the bindings with SwiftData. Editing the object directly would update the Item in the database</span>
<span class="hljs-type">Item</span>.date_created = <span class="hljs-type">Date</span>.now

<span class="hljs-comment">// Delete</span>
modelContext.delete(instanceOfItem)
</code></pre>
<h2 id="heading-ios-previews-and-view-previews">iOS Previews and View Previews</h2>
<p>The iOS Previews and View Previews allow us to quickly preview views in a side panel (called "Canvas"). I found it intuitive and fast. It hot-reloads too!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718622594824/937cb160-0506-4a4b-8fcf-e7df2358e649.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-quality-and-quantity-of-documentation-and-support">Quality (and quantity) of documentation and support</h2>
<p>The quality and quantity of support available for Swift is mind-blowing. Apple's official documentation includes comprehensive guides that have been invaluable.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718685066372/3b209f0e-75a8-40a6-86bf-555f0b094e29.png" alt class="image--center mx-auto" /></p>
<p>YouTube channels like Sean Allen's have provided me with more than enough knowledge to build this project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718685110995/a5ffce84-e2ed-400b-9529-cc67335c0c8f.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-where-react-shines">Where React shines</h1>
<h2 id="heading-cross-platform">Cross Platform</h2>
<p>That's the biggest driving factor behind basing an entire project on React Native. While React Native may not be as performant (due to having a layer of JavaScript engine running in the application), for small teams, it makes sense to sacrifice some performance for a better developer experience (DX). Moreover, we can build for iOS and Android using a single codebase.</p>
<h2 id="heading-maturity">Maturity</h2>
<p>Yep, I said it. React Native is more mature than SwiftUI and Swift. This maturity also means better packages (if needed) and better documentation for those packages (for instance, Spotify's iOS API documentation is in Objective-C).</p>
<h2 id="heading-its-easy-for-developers-whos-familiar-with-react">It's easy for developers who's familiar with React</h2>
<p>Yeah, it's true. React developers would likely find the switch to React Native much smoother compared to switching to Swift. Even switching to Flutter and Dart would be smoother than switching to Swift.</p>
<h1 id="heading-my-biggest-gripe-with-ios-app-development-the-app-id-limit">My biggest gripe with iOS App Development – The App ID Limit</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718684408628/25baf9e0-106b-4498-a4df-a3a6e73396cc.png" alt class="image--center mx-auto" /></p>
<p>The signing limit really frustrates me, especially as a developer who's not enrolled in the Apple Developer Program (it costs $99 USD/year, but I'm just a student with no budget).</p>
<p>This issue arises when I try to run the build on my own device to test out the widgets I made. Unfortunately, I can't, and I have to wait 7 days.</p>
<p>You might ask, "How did you use up all 10 App IDs already?" My answer would be that I followed the "Develop in Swift" tutorial, which guides you through building multiple apps.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Apple's primary message and agenda in recent years have been to make their tools more accessible so that anybody can code. However, Swift and SwiftUI introduce many abstractions that novice developers might initially overlook.</p>
<p>I want to clarify that I'm not gatekeeping. I would be proud to see a novice developer build an app from scratch, but these abstractions could pose challenges in later stages of development for beginners. For instance, they might wonder, "What if I want to build a Kotlin Composable version of this app?"</p>
<p>This is my opinion, but I'm confident that determined novice developers would find ways to overcome these challenges.</p>
<p>Swift offers numerous language features that simplify development, and Xcode's deep integration with Simulator and macOS (enabling apps to run directly on your Mac) makes it compelling for developers to create apps across multiple Apple platforms (iOS, iPadOS, macOS, watchOS, tvOS, and VisionOS).</p>
<p>SwiftUI has emerged as a leader with its declarative approach to UI development and simplified state management. Gone are the days of setState() and React.createContext(). States and contexts are now more intuitive, not to mention the straightforward approach to data persistence with SwiftData.</p>
<p>In conclusion, Swift is easy to learn and encourages developers to create better mobile apps through tightly integrated APIs like TipKit, SwiftData, WalletKit, WidgetKit, and more. These APIs enable developers to think less and accomplish more – which should be at the heart of every app.</p>
<p>For React Native developers, adopting Swift can enhance app personalization for their Android counterparts. While React offers native APIs that interface with the mentioned APIs, having Swift in your skillset offers unique advantages.</p>
<p>Flutter developers transitioning to Swift will find the switch relatively straightforward. Concepts like states transfer smoothly from Flutter to Swift (and vice versa with React to Flutter), and mastering Swift within a day or two is achievable.</p>
]]></content:encoded></item><item><title><![CDATA[I Built a Private Graduation Website for My Friends (For $0)]]></title><description><![CDATA[As graduation approaches, I wanted to create a site to share some of my fondest memories for a select group of friends. The idea is that each friend would receive an access code in the goodie bag I prepared. Scanning it will redirect them to the site...]]></description><link>https://blog.nabilridhwan.com/i-built-a-private-graduation-website-for-my-friends-for-0</link><guid isPermaLink="true">https://blog.nabilridhwan.com/i-built-a-private-graduation-website-for-my-friends-for-0</guid><category><![CDATA[Next.js]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Sat, 11 May 2024 11:40:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715427079092/f6c9d724-53fd-47da-919f-15f12200ab32.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As graduation approaches, I wanted to create a site to share some of my fondest memories for a select group of friends. The idea is that each friend would receive an access code in the goodie bag I prepared. Scanning it will redirect them to the site, containing memorable images/moments and videos of me and the said person.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715388581635/c0338056-0839-4356-ae29-d92b0b6b1af6.png" alt class="image--center mx-auto" /></p>
<p><strong>My main mission is to not spend a single dime and rely on ways to make this application free. The last thing I want to do is spend money for graduation.</strong></p>
<p><a target="_blank" href="https://graduation.nabilridhwan.com">The Site (though you can't do much because you don't have an access code)</a></p>
<p>In this blog post, I'll run you through my thought process, splitting into ideas and then bringing everything together with conclusions and what I've actually learned.</p>
<p>~ Starting off with how I would like to 'design' the access code for each individual.</p>
<h2 id="heading-ideas-for-an-access-code">Ideas for an access code</h2>
<p>The access code plays an important part. Each person will receive a unique access code. Initially, I came up with the idea to use pass-phrases such as <code>shuffle-playing-encircle-thickness</code>. But then, I want the access codes to be personalized for each person.</p>
<p>And that's where the idea came in! I can just use their name followed by adjectives. Such as <code>jane-the-broccoli</code> or <code>jack-the-OG</code>.</p>
<p>After all, personalization is really my thing ✨</p>
<h2 id="heading-okay-where-do-i-store-these-images-and-videos">Okay, where do I store these images and videos?</h2>
<p>I sat down and thought of ways to host images and videos for free. Each person averages 3 pictures, showing my fondest memories with them throughout my 4 years in poly.</p>
<p>First, I thought of using AWS S3 Free-Tier. It was <em>generous</em> and also I have used it during my internship. But then, I don't want to spend time learning it again – I totally forgot how to upload assets to S3 programatically. Moreover, I want the site to be up and running. Graduation is coming in two weeks!</p>
<p>I sat down and realized I can use Imgur and unlistied YouTube videos!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715426914178/2fe65c5f-e413-4b8d-8f4a-1519c221d155.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715427167809/4bbc8925-bdf6-4c3a-bcc6-064f894ef82f.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-i-got-the-assets-sorted-what-framework-should-i-use-for-the-site">I got the assets sorted... What framework should I use for the site?</h2>
<p>Okay, now that assets will be handled by Imgur and YouTube. I need a framework that can help me get the site running in no time. Something that, perhaps, have simple hosting solutions (that are also free!) Sounds familiar? – was it Next.js?</p>
<p>Apart from Next.js being the fastest framework I can use to get the site up and running, other considerations such as easiness of spinning up an API route and Server-Side Rendered (SSR) pages are why I chose Next.js</p>
<p>Anyways, in a later section, SSR will play a huge role. Keep this in mind!</p>
<h2 id="heading-designing-the-data-structure">Designing the data structure</h2>
<p>Again, I want it to be fast, not in terms of speed of the site but the development time. <strong>By now, graduation is about 10 days away</strong>. The first thing that came to my mind is JSON! Why not, they're easy to access anyways. That is when I designed the following data structure:</p>
<p>Let's say John is a really great friend of mine, here's how his data will look like:</p>
<pre><code class="lang-json"><span class="hljs-string">"john-the-og"</span>: {
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"John"</span>,
  <span class="hljs-attr">"content"</span>: <span class="hljs-string">"Thanks John!"</span>,
  <span class="hljs-attr">"images"</span>: [
    {
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://image.com/img.jpg"</span>,
          <span class="hljs-attr">"desc"</span>: <span class="hljs-string">"Us in class"</span>
    }
  ],
  <span class="hljs-attr">"videos"</span>: [
    {
        <span class="hljs-attr">"yt_embed"</span>: <span class="hljs-string">"9D0Sjd99J"</span>,
          <span class="hljs-attr">"desc"</span>: <span class="hljs-string">"A song that reminds me of you"</span>
    }
  ],
}
</code></pre>
<p>Ah, that makes it simple. Each person will have it's access code as the key and the data as the value of the object.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715388505146/3292b3a2-1ed5-4f50-9d5b-56adead53d0a.png" alt class="image--center mx-auto" /></p>
<p>Now... where do I store all these?</p>
<h2 id="heading-storing-the-data">Storing the data</h2>
<p>Okay, call me wild, but for the sake of development speed, I initially wanted to just have a <code>data.json</code> file containing all the data and access code in the structure shown above. But after some thinking, I didn't want the codebase to have any traces of anyone's data – not that it contains personal data. It's just that I want to make it as 'modular' as possible.</p>
<p><em>I guess deadlines do make one think of crazy things.</em></p>
<p>If you're thinking "Why not just spin up an AWS RDS instance or a Supabase project?", you'd be half-right. While they're fast to spin up, I'll have to bloat the site (which should be simple) with Supabase client library or even worse... ORMs.</p>
<p>That's when I remembered in an earlier project, plsgrade.me, I used Vercel KV to store some user-generated data. Moreover, it checked a lot of boxes. First being that it is fast to spin up, second is seamless integration with Next.js.</p>
<p>So that is exactly what I did. I spun up a Vercel KV storage (which then spins up an upstash instance – Redis) for me.</p>
<p>The only downside of this approach is that every time I need to write data, I'll have to store it in Redis in String and when fetched from the storage, I'll have to parse the JSON. Moreover, due to the nature of how I chose to create data entries, I would need to omit any apostrophes from the stringified JSON (i.e. "you're" -&gt; "youre")</p>
<p>I wrote some test data, manually, to test if the application really shows up.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715388421757/61c5943b-b523-486e-9b62-52d8e677724d.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-the-first-test">The first test</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715388530957/44a1673d-5078-43e0-b81b-62d3253ff2c8.png" alt="Wow" class="image--center mx-auto" /></p>
<blockquote>
<p>Image of the home page</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715388581635/c0338056-0839-4356-ae29-d92b0b6b1af6.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>What it looks like when accessing the page using the correct access code</p>
</blockquote>
<h2 id="heading-time-to-generate-data-for-the-rest-of-the-people">Time to generate data for the rest of the people...</h2>
<p><strong>By now, graduation is about 1 week away</strong>, I was thinking of ways to improve the application. The first test data works... It shows up, I'm ecstatic.</p>
<p>Now, I'm writing each person's data into Vercel KV CLI.</p>
<p>But this task is proven to be a chore... For every person, I need to type out the data structure, change the name, content, url and then copy the key for the access code and paste in the value in the CLI format: <code>SET &lt;key&gt; &lt;value&gt;</code>.</p>
<p>That's when I made my own script to help me generate data in a type-safe way and pretty-print, so that I can just copy and paste into the CLI: <code>generateData.ts</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715388902495/5941151a-28b5-47dd-8193-b7f0107c6acc.png" alt class="image--center mx-auto" /></p>
<p>And running the file would output these into the console, making it easier for me to bulk insert data into the CLI:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715389046390/39378799-8563-4890-8d9e-cfd8d1647279.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>A – A ready-to-copy-and-paste text to paste into Vercel CLI</p>
<p>B – The URL for the QR Code</p>
</blockquote>
<h2 id="heading-the-essence-of-anticipation">The essence of anticipation!</h2>
<p>With about less than 7 days, I prepared everything – the goodie bags and the site – cleaning up messes, smoothing out rough edges.</p>
<p>One thing that was missing is the essence of anticipation – taking cues from other sites, some sites incite anticipation via countdowns. And that is what I did!</p>
<p>I created an if-check to check if the user's current date is before 6th of May, 6 p.m. (Graduation date)</p>
<h3 id="heading-big-issue">Big Issue!</h3>
<p>After deploying the test version, I realized that users are able to change their device's date and time to skip the countdown and display the hidden content.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715425802153/8559aa70-5752-4a05-82bc-7c6326ce0238.png" alt class="image--center mx-auto" /></p>
<p>So, how did I fix this? I knew that the problem lies in that the check for the time checks it against the <strong>user's date and time</strong>. I researched on ways to solve this and came down to the solution – <strong>instead of relying on the user's date and time, rely on the server's date and time</strong>.</p>
<p>I created an api at <code>/api/time</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715426028413/b5996bce-10a1-4432-871e-725a5bb92017.png" alt class="image--center mx-auto" /></p>
<p>Remember when I said SSR played a part? It does because when the component is rendered and sent to the client, no other code is being sent to the client. If you view the page source, you'll only see the code for the countdown but not the hidden content. <em>SSR – 1, Hackers – 0.</em></p>
<h2 id="heading-final-touches">Final touches</h2>
<p>Okay, everything is done. From the access code, to the note, videos, images and the anticipation element. One final thing, which was REALLY not needed but somehow fun to do is the idea of access logging.</p>
<p>I would love to know when someone entered their access code and viewed their note.</p>
<p>I leveraged Redis, again, to store the data. Instead of using a simple GET and SET, I used LPUSH (List Push) which allows me to store items as part of an array. Retrieving it is as simple as using LRANGE.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715426217452/8727587f-befa-4430-89ae-d5334c84dd00.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715426735607/1e9f39c7-c496-4bcd-8bb6-0e2f8d13cd33.png" alt class="image--center mx-auto" /></p>
<p>Tada, it works!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715426257305/29a6bec8-729a-40f1-8cb4-9f62fff4cafe.png" alt class="image--center mx-auto" /></p>
<p>Now, all I got to do is send to my friends the website and wait for the day to come.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The day came, and I think the site is well received:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715427308949/a1f57765-10bb-4eb8-87af-6cc5752cc595.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715427374897/8e001d52-7cc0-4478-898c-3a43f8a75239.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715427435192/240dc60f-87a3-4576-b70e-355356857ab7.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715427486235/6c5c7b8b-a2c4-4b2b-88bf-4244738aca7a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715427546912/9d5e83ff-73ba-477d-a06a-399aba1d9886.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-what-did-i-exactly-learn">What did I exactly learn?</h3>
<p>Yeah, this seems like a simple job. There is no out-of-the-world technologies being implemented. But hey, every project has a lesson.</p>
<h4 id="heading-countdown-and-its-nuances">Countdown and it's nuances</h4>
<p>Since it is my first time working with time, the issue of relying on user's date and time versus relying on the server's date and time is new to me. And I'm pretty sure I'll keep this in mind for future projects.</p>
<h4 id="heading-using-redis">Using Redis</h4>
<p>While learning how to use Redis, I also learned that Redis can be used for plethora of other things such as rate limiting. Isn't that interesting? This is something new to me, and I'll explore using Redis for my other projects. I know that you can use Redis as a complete data store but not recommended to. Maybe I'll explore data caching using Redis?</p>
<h3 id="heading-what-was-done-right">What was done right?</h3>
<p>I feel that I am proud of how fast I got the project up-and-running. Considering some security measures implemented, which is overkill, would benefit other projects BUT there is no harm to me using what I've learned thus far into this small site.</p>
<h3 id="heading-what-couldve-been-done-better">What could've been done better?</h3>
<p>Let's take a step back. If there was something that can be done better, it is to create a dedicated page for data creation rather than having to manually create data form the Vercel KV CLI.</p>
<p>Maybe this new page can interact with Imgur's and YouTube's API to handle uploading of assets rather than me having to manually upload the assets and copy the URL and ID.</p>
]]></content:encoded></item><item><title><![CDATA[I Migrated a 2-Year-Old Next.js App to App Router - Here’s What Happened]]></title><description><![CDATA[I made an app called “Musicn”. It’s an app that allows users to discover music and share your music taste with others. Beta version of Musicn
It was a pretty old project, with any major change being about 2 years ago. The most significant thing after...]]></description><link>https://blog.nabilridhwan.com/i-migrated-a-2-year-old-nextjs-app-to-app-router-heres-what-happened</link><guid isPermaLink="true">https://blog.nabilridhwan.com/i-migrated-a-2-year-old-nextjs-app-to-app-router-heres-what-happened</guid><category><![CDATA[Next.js]]></category><category><![CDATA[Lucia]]></category><category><![CDATA[OAuth2]]></category><category><![CDATA[prisma]]></category><category><![CDATA[react server components]]></category><category><![CDATA[rsc]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Mon, 19 Feb 2024 07:43:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708328504696/626f641d-327f-4967-95be-1ec0c863f393.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I made an app called “Musicn”. It’s an app that allows users to discover music and share your music taste with others. <a target="_blank" href="https://beta.musicnapp.com">Beta version of Musicn</a></p>
<p>It was a pretty old project, with any major change being about 2 years ago. The most significant thing after was moving to Chakra UI as the UI library.</p>
<p>The Next.js version of the project was 12, still using the page router. While it was sufficient for that time, I came back to work on the project to work on a nifty new feature that I have been planning for days...</p>
<p>So here I go, into the codebase...</p>
<h2 id="heading-the-messy-code">The messy code</h2>
<p>Coming back, I wasn’t really given a nice greeting with how messy the code is. Messy, as in, redundant functions, unneeded abstractions and the lack of code comments.</p>
<p>The lack of code comments made it hard for me to navigate and change the code.</p>
<p>Recently, I’ve watched advancements in Next.js. Introducing App Router and all the cool features but it was a bit of a jump for me, which is why I didn’t bother upgrading the Next.js of the project to version 13 at that point in time… Until recently…</p>
<h2 id="heading-preparing-myself-for-migration">Preparing myself for migration</h2>
<p>I read the migration guide from Vercel themselves</p>
<p><a target="_blank" href="https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration">https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration</a></p>
<p>And then I realised, why not make the switch and along the way, clean up the mess I’ve wrote?</p>
<p>I prepared myself for the hell to come, knowing that a lot of functionality will break. I prepared myself by creating a new branch (<code>feat/migrate-nextjs</code>) and got my hands dirty.</p>
<h2 id="heading-initial-migration-steps">Initial migration steps</h2>
<p>According to the guide, we just have to install the latest Next.js, React, React DOM and the ESLint configuration. And then, we can incrementally adopt pages from the <code>pages</code> directory to the <code>app</code> directory.</p>
<p>Remember how I mentioned that I’ve watched advancements in Next.js and didn’t bother to upgrade? One of the main reasons is the seemingly different file structure. For example, why is there <code>page.tsx</code> and why can’t we name the routes as is such as <code>signup.tsx</code> anymore.</p>
<p>But after doing some reading and trying it out for myself, I actually thought the new way is genius! The <code>page.tsx</code> serves as the main page, the content of the page while other <code>tsx</code> files such as <code>loading.tsx</code> serves as the component to show when the page is being loaded from the server (if it is an RSC) and <code>error.tsx</code> serves as the component to show when there is an error.</p>
<p>The migration process was made simpler as the old pages from the <code>pages</code> directory are still working, thank you Vercel for allowing me to incrementally adopt the app router.</p>
<h2 id="heading-a-taste-of-react-server-components">A taste of React Server Components</h2>
<p>At that point in time, the biggest feature that came with future version of Next.js was the use of React Server Components. Watching their Next.js 13 video, I am quickly amazed by how you can just call database functions from your React Components. It’s like magic! In the past, I’d have to expose an API route and make a HTTP request from the client. But now, I can just call database functions directly? You can imagine how mind-blown I was!</p>
<p>This, in result, cuts down the codebase by a lot. In the past, Musicn had a lot of API routes exposed, each of them has a lot of boilerplate code because I needed to make sure it was as typesafe as possible. But with this new addition, types are automatically inferred from our database calls (<em>types inferred</em> here refers to types from the Prisma client).</p>
<p>Moreover, React Suspense allows me to show a loading indicator as the page loads, this is a massive bump for user experience. In the past, because of <code>getServerSideProps</code> , there wouldn’t be any loading indicator that the page I clicked is loading, all I got was a page that was frozen.</p>
<h2 id="heading-the-trouble-of-migration">The trouble of migration</h2>
<p>In the process of migration, there were a ton of hiccups. So much so that I devised a plan:</p>
<ol>
<li><p>Migrate key app features first (exclude styling, and minor features)</p>
</li>
<li><p>Either prepend old API/page files with ‘_’ (i.e. ‘signup.tsx’ → ‘_signup.tsx’) OR place conflicting old API files in the ‘.old.api’ folder in the root of the project.</p>
</li>
<li><p>Implement proper access control in the now-migrated features.</p>
</li>
</ol>
<p>Using this plan, I was able to migrate key features (except login/signup) within the first day of migration. And finished up the migration of other features the very next day.</p>
<p>But realistically, this is how it went:</p>
<ol>
<li><p>Migrate features</p>
</li>
<li><p>Clean-up code</p>
</li>
<li><p>Test deploy</p>
</li>
<li><p>Build fails</p>
</li>
<li><p>Fix issue</p>
</li>
<li><p>Test deploy again</p>
</li>
</ol>
<p>Repeating steps 3-6 for about 5 times.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708326631458/9dd6d638-ee27-42bd-8932-5b4206d90d00.png" alt class="image--center mx-auto" /></p>
<p>If every failed build lowers my sanity by 10 points, I'd be dead by now.</p>
<h2 id="heading-use-client-use-server-what-exactly-am-i-supposed-to-use">Use client? Use server? What exactly am I supposed to use?</h2>
<p>Yep, trouble arises! With the new 'use client' and 'use server' directive, it is now simpler to dictate whether a component should be a normal React component or a Server Component.</p>
<p>The impressive mind-blowing feature I mentioned earlier about calling database calls directly in your components? It is only available in Server Components, understandably. This is because the component is being processed by the server before sending it to the client.</p>
<p>Only pages that require SEO content is marked as Server Components. This includes: the users page, and the user profile page. Other than that, it is client components.</p>
<h2 id="heading-does-authentication-have-to-be-confusing-reviewing-and-tackling-authentication-again">Does authentication have to be confusing? – Reviewing and tackling authentication (again)</h2>
<p>In the past, Musicn’s authentication flow has been:</p>
<ol>
<li><p>Log In/Sign Up with email and password</p>
</li>
<li><p>Link Spotify account to your Musicn account</p>
</li>
<li><p>Done</p>
</li>
</ol>
<p>This simple authentication flow proved problematic as we cross the roads of Email activation, Password reset, Password changing and etc. Which is why I wanted to offload the authentication providers to services like Auth0.</p>
<p>But after meticulous planning, I found out that is it possible to just let users Sign In with their Spotify account via OAuth2.0 and let Musicn handle the creation of their Musicn account based on their Spotify profile.</p>
<p>So that’s exactly what I did!</p>
<p>Now, Musicn’s authentication flow is:</p>
<ol>
<li><p>Log In using Spotify Account</p>
</li>
<li><p>If user doesn’t have a Musicn account, create one automatically based on their Spotify profile</p>
</li>
<li><p>Done</p>
</li>
</ol>
<p>And also did I tell you that this process is further simplified using a library I found called Lucia?<a target="_blank" href="https://lucia-auth.com/">https://lucia-auth.com/</a></p>
<p>Lucia has helped immensely by abstracting the confusing parts of authentication and session management and I’ll forever be thankful for this library.</p>
<p>The main reason I used Lucia is because it is both customizable and un-opinionated. Allowing me to store what I want in each session (that, btw, gets stored in my database via the Prisma Adapter for Lucia). And customizable in a sense that I am allowed to pass in the scopes needed to retrieve what I need from the access token.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>From migration to code clean-up, this experience has been humbling for me. This is what sets apart hobbyists and realists. As hobbyists, it would’ve been my instinct to immediately create a new repo and start from scratch using Next.js 13 but I avoided doing that because in a real work scenario (as backed up by my internship), we tend to upgrade versions and not rebuild and I just have to have a taste of what it feels like to migrate to a major version of a framework.</p>
<p>With the new features added in Next.js 13, I am just glad that the codebase got much more readable and cleaner. And I am really hoping to see what the future of Next.js brings with React Developer Team’s advancements in React – such as introducing form actions.</p>
]]></content:encoded></item><item><title><![CDATA[Monolith vs Microservices: Should You Make the Switch?]]></title><description><![CDATA[While intern-ing at a small company, one of the problems we face is our infrastructure of software. It was never built to scale. Sure, scaling is managed by our service providers but we're talking about maintenance of the application and urgent bug f...]]></description><link>https://blog.nabilridhwan.com/monolith-vs-microservices-should-you-make-the-switch</link><guid isPermaLink="true">https://blog.nabilridhwan.com/monolith-vs-microservices-should-you-make-the-switch</guid><category><![CDATA[Microservices]]></category><category><![CDATA[architecture]]></category><category><![CDATA[softwarearchitecture]]></category><category><![CDATA[engineering]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Wed, 06 Dec 2023 13:45:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ZiQkhI7417A/upload/43e2db7b6c97462cc10ba7d2dd533501.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While intern-ing at a small company, one of the problems we face is our infrastructure of software. It was never built to scale. Sure, scaling is managed by our service providers but we're talking about maintenance of the application and urgent bug fixes in the application. Here is the current infrastructure of our application:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701870137171/ecbe5201-a424-40df-8906-9525a750c5f0.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-the-problem">The Problem</h1>
<p>Now here comes a problem that occurs a lot. We receive notice that one of our modules are broken (Payment Module). This would require the whole backend to have a downtime while we fix/patch and re-deploy. Other modules (e.g. Search) would be affected too because of the downtime. In this modern world where there are millions, if not billions of users of the internet, is this really the way to go?</p>
<h1 id="heading-introducing-micro-services">Introducing Micro-services</h1>
<p>Micro-services solves the problem by lifting different modules of our application in a separate container. This allows for the easy plug-in-plug-out scenario. Let's say here is the proposed Micro-services architecture of our current app (not comprehensive):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701870151592/67ce675c-456f-42fb-ad0f-15a6f683e29f.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-so-how-does-micro-services-solve-the-problem">So how does Micro-services solve the problem?</h1>
<p>Simple, we do not need to bring down the whole backend to fix one module. We can instead plug-out, fix and plug it back in, redeploy the affected service without affecting other services (that are not reliant)</p>
<p>In the old architecture, the Backend is affected (as a whole)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701870172916/42efdaf0-c332-4ce1-b730-4bbcac4be6c8.png" alt class="image--center mx-auto" /></p>
<p>In the new architecture, only the Payment service is affected</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701870183302/15faac43-af74-4e51-aae3-ba0cc12130a8.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-companies-that-adopt-micro-services">Companies that adopt Micro-services</h1>
<p>While discovering this new technology, I am sure that I am not the only one who sees Micro-services the same way. Companies such as Shopee, Google and Netflix are adopting and using Micro-services in their products to serve large scale software.</p>
<h1 id="heading-testing-and-running-micro-services-locally">Testing and Running Micro-services locally</h1>
<p>You can run Micro-services locally using Docker. Writing backend services that adopts the Micro-services pattern is available using Nest.js</p>
]]></content:encoded></item><item><title><![CDATA[What Working at a Small Tech Company Taught Me as an Intern]]></title><description><![CDATA[Since March of 2023, I've been interning as a software developer at a small company. Like small as in, 4 developers working on the project with direct communications to the boss, who acts as a project manager/supervisor. Working in a small company ha...]]></description><link>https://blog.nabilridhwan.com/what-i-have-learned-as-a-software-developer-intern-and-a-small-company-thus-far</link><guid isPermaLink="true">https://blog.nabilridhwan.com/what-i-have-learned-as-a-software-developer-intern-and-a-small-company-thus-far</guid><category><![CDATA[internships]]></category><category><![CDATA[software development]]></category><category><![CDATA[Small business]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Sun, 10 Sep 2023 06:40:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/KvFz3IXf8MM/upload/e1d53668c55732b062b8a8ada4c991eb.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Since March of 2023, I've been interning as a software developer at a small company. Like small as in, 4 developers working on the project with direct communications to the boss, who acts as a project manager/supervisor. Working in a small company has its benefits such as not being too strict on the time you arrive at work (though, obviously, this is not universal, although highly likely). In the upcoming sections, I am going to talk about the things that I have observed and learned as a software developer intern.</p>
<h2 id="heading-you-have-to-simplify-the-way-you-are-explaining-things">You have to simplify the way you are explaining things.</h2>
<p>First, you grow up in college (polytechnic) with the fact that the friends or classmates you have are well-versed in your course (or study). Everyone around you know what you mean by "debugging" and "fixing that <em>one bug</em>". And it was somewhat of a culture shock to me that in the real world, not everyone understands the "under-the-hood" processes that happen. This is highly emphasized when my supervisor (or boss) asks for the timeline and I have to explain that there will be some setbacks to the initial timeline because of technical limitations (in which I go in-depth) until the confusion sinks in their face and I realized that they do not understand a single bit of what I have just explained.</p>
<p>I find it easier to use analogies in real life or maybe a run-down version of what is happening.</p>
<h2 id="heading-you-are-expected-to-work-overtime">You are expected to work overtime.</h2>
<p>Sure, we get pushed around as interns. But I do have to remind everybody that the position I signed up for requires us to have experience in React, Typescript, Nest.js and other frameworks. The lie in "no prior experience needed" is a huge lie and should be the biggest sin in this world.</p>
<p>Obviously, <em>I'm joking</em>. But the real fact is that we are expected to work overtime even though not explicitly said. I feel pressured because I am the <em>project manager</em> and am expected to be the man between my team and the boss.</p>
<p>That also means that I need to work overtime to compensate for the things that we didn't do in the office, such as creating and reviewing Pull Requests, merging them and ensuring compatibility.</p>
<p>And I'm not being paid for overtime (but who am I to complain, After all, I am an <strong>intern</strong>).</p>
<h2 id="heading-however-small-your-company-is-team-structure-is-important">However small your company is, team structure is important.</h2>
<p>Small companies tend to not have the budget for their developers. And that is factual. When I entered the company, there was no structure. Everything was here and there and very disorganized.</p>
<p>This makes collaborating hard. Which is why I introduced structure within the software team. Some of them include using some elements of the Scrum methodology such as having a backlog, reviewing code, code review and retrospective. I also instilled the importance of VCS, Pull requests, branch naming conventions, conventional commits, atomic commits and many more. As a result of the <em>policies</em> I introduced, the software team became easier to manage. Especially for the current project manager who will be accepting PRs and merging PRs.</p>
<h2 id="heading-being-a-project-manager-is-fun-you-get-to-go-on-power-trips">Being a project manager is fun, you get to go on power trips.</h2>
<p>I am the project manager for this current sprint, which is the last leg before the release of the first version of the application. That also means that there is a strict timeline involved during this sprint.</p>
<p>First, this is hard. On top of the overtime that I have been doing, I also have to do more work past my working hours.</p>
<p>Second, managing the team must be stellar. In this last leg, it is obvious that there can't be any mess-ups and if there are any, it needs to be rectified immediately. I also had to delegate tasks to team members who specialize in them. This helps the team to make faster progress over time.</p>
<p>I believe that being a Project Manager allows me to on a power trip but I also realize to do it cautiously. Sure, you feel like you're managing the team but it is also important to consider the interest of the team and the goal at the end. Being on a power trip is beneficial if it benefits the team, boosts team morale while working towards our goal and should never be used to self-indulge in power (I learned this the hard way).</p>
<h2 id="heading-technical-debt-comes-to-bite-you-in-the-ass-again">Technical debt comes to bite you in the ass again.</h2>
<p>I have seen this first hand (and I am sure other people too!). You create a feature, you create the most bare-bones thing to get it working and by the time you want to clean up, you realise that you've used all of your time and have to push to the development branch.</p>
<p>Now there are two possible outcomes: Stay around to see the technical debt bite you in the ass or quit your job, find a new job and let the new people handle the technical debt.</p>
<p>Surely, being an intern for a year, we're on the former outcome. Yaouch! The technical debt in question is the "chat" feature that is enabled by Ably that is poorly implemented that bites us in the ass which caused slowdowns in the team's progress and productivity.</p>
<p>Remember, always write clean code and ensure you have time to clean up the code you wrote. If you do not have time, remember that you can always revisit it later, but make it your priority to revisit.</p>
<h2 id="heading-be-ready-to-switch-up-your-processes">Be ready to switch up your processes.</h2>
<p>In a small company, things tend to get disorganized, with no structure and that is the new norm. You just have to deal with it. There may be days where the reviewer would give a green light on the feature and the next day might request additional features (that does seem easy to them) but between you and me, we all know it's gonna take time. Other processes might also not be done properly such as UI design review. For instance, the current project we are working on is based on the web version which means that the components on the mobile version of the app are copied from the web (with some pizzazz – such that it works on mobile) and we are working without wireframes (well, mainly because our company has no designers) and these UI decisions are instead made by the boss. The best way I could say it is this: <em>"Some UI decisions are questionable"</em>.</p>
<p>So just keep in mind, that some processes that are taught to you in school may be switched up in the real world and these may be due to lack of manpower, costs or even time.</p>
<p>At the end of the day, we must remind ourselves that the projects we built are going to be used by our clients and not you, hence we don't need some of those <em>trendy 3D things that are trending in UI these days</em>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I've never been at the other end of the spectrum, working in a big company with a relatively big software team. I'm pretty sure big companies' software team have more structure in them and does code review for real (rather than our role-play positions). But at the same time, these past months have been a great experience to learn and do (and also manage a team). Being at a small company with a half-assed software team has never been easy and will never be but at least we got some perks.</p>
]]></content:encoded></item><item><title><![CDATA[5 Lessons I Learned While Creating My First WebSocket-Powered Game]]></title><description><![CDATA[In today's post, I want to share with you some of the things I have learned while trying to create my first-ever WebSocket-powered game.
As a web developer, I have always been fascinated by the possibilities of WebSockets, and I was eager to put my s...]]></description><link>https://blog.nabilridhwan.com/5-lessons-i-learned-while-creating-my-first-websocket-powered-game</link><guid isPermaLink="true">https://blog.nabilridhwan.com/5-lessons-i-learned-while-creating-my-first-websocket-powered-game</guid><category><![CDATA[SocketIO]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Sun, 04 Dec 2022 15:16:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670166780556/ED3IDmmBM.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today's post, I want to share with you some of the things I have learned while trying to create my first-ever WebSocket-powered game.</p>
<p>As a web developer, I have always been fascinated by the possibilities of WebSockets, and I was eager to put my skills to the test by creating a web-based game that uses this technology.</p>
<h2 id="heading-introducing-troof">Introducing Troof!</h2>
<p>Troof!, the online-based truth or dare game that brings people together through the power of honesty and adventure. In Troof!, players can chat and react to each other's truths and dares, creating a fun and interactive experience that fosters connection and bonding.</p>
<p>You can play Troof! here*:* <a target="_blank" href="https://troof.nabilridhwan.com">https://troof.nabilridhwan.com</a></p>
<p>The inspiration for Troof came about when my friend and I were playing truth or dare over a voice call. One annoying thing was that I had to repeat the question because they were in the middle of doing something else.</p>
<p>This is where the idea for Troof was born! - a truth or dare game that always displays the question to all players in the room, with the added functionality of chat. With Troof, you can play without the need for a voice call and <strong>never miss a question again</strong>.</p>
<h2 id="heading-the-planning-stage">The Planning Stage</h2>
<h3 id="heading-scalability">Scalability?</h3>
<p>Initially, I tried putting both my <a target="_blank" href="http://socket.io">socket.io</a> server together with next.js, but I quickly realized that this would not be scalable.</p>
<p>In order to improve performance and ensure that my application could handle a high volume of users, I decided to move all backend services to their own separate servers.</p>
<p>This allowed me to better manage and optimize my application, and ultimately provide a better user experience.</p>
<p>So Troof now has a frontend hosted on Vercel and a separate backend handling all the little socket.io stuff.</p>
<h3 id="heading-usage-of-typescript-in-this-project">Usage of TypeScript in this Project</h3>
<p>I knew from the start that I wanted to use TypeScript for both the front end and back end of my application.</p>
<p>The reason for this is that TypeScript is strongly typed, which means it is less prone to stupid mistakes.</p>
<p>By using TypeScript, I was able to catch errors early on and write cleaner, more reliable code.</p>
<p>Overall, I found that TypeScript greatly improved the development process and helped me create a more stable application.</p>
<h2 id="heading-the-development-stage">The Development Stage</h2>
<h3 id="heading-socketio-and-typescript">Socket.io and TypeScript?</h3>
<p>As I continued to develop my application, I realized that <a target="_blank" href="http://socket.io">socket.io</a> can be used with TypeScript to define how server-to-client and client-to-server events would look like. (Reference: <a target="_blank" href="https://socket.io/docs/v4/typescript/">https://socket.io/docs/v4/typescript/</a>).</p>
<p>An example of it would look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ServerToClientEvents {
    [EVENTS.PLAYERS_UPDATE]: <span class="hljs-function">(<span class="hljs-params">players: Player[]</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    [EVENTS.GAME_UPDATE]: <span class="hljs-function">(<span class="hljs-params">room: Room</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    [EVENTS.LEFT_GAME]: <span class="hljs-function">(<span class="hljs-params">playerRemoved: Player</span>) =&gt;</span> <span class="hljs-built_in">void</span>;

    [TRUTH_OR_DARE_GAME.INCOMING_DATA]: <span class="hljs-function">(<span class="hljs-params">log: Log, player: Player</span>) =&gt;</span> <span class="hljs-built_in">void</span>;

    [TRUTH_OR_DARE_GAME.LEAVE_GAME]: <span class="hljs-function">(<span class="hljs-params">room: Room</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    [TRUTH_OR_DARE_GAME.SELECT_TRUTH]: <span class="hljs-function">(<span class="hljs-params">room: Room</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    [TRUTH_OR_DARE_GAME.SELECT_DARE]: <span class="hljs-function">(<span class="hljs-params">room: Room</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    [TRUTH_OR_DARE_GAME.CONTINUE]: <span class="hljs-function">(<span class="hljs-params">log: Log, player: Player</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    [TRUTH_OR_DARE_GAME.JOINED]: <span class="hljs-function">(<span class="hljs-params">log: Log, player: Player</span>) =&gt;</span> <span class="hljs-built_in">void</span>;

    <span class="hljs-comment">// Messages</span>
    [MESSAGE_EVENTS.MESSAGE_NEW]: <span class="hljs-function">(<span class="hljs-params">message: MessageUpdate</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    [MESSAGE_EVENTS.MESSAGE_ANSWER]: <span class="hljs-function">(<span class="hljs-params">message: MessageUpdate</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    [MESSAGE_EVENTS.MESSAGE_REACTION]: <span class="hljs-function">(<span class="hljs-params">message: MessageUpdate</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    [MESSAGE_EVENTS.MESSAGE_SYSTEM]: <span class="hljs-function">(<span class="hljs-params">message: SystemMessage</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}
</code></pre>
<p>This interface defines how Server to Client events are made. So every <code>io.emit</code> or <code>socket.to().emit()</code> follows the interface structure above.</p>
<p>This interface is then used in the initialization of the socket, as shown below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> io = <span class="hljs-keyword">new</span> Server&lt;ServerToClientEvents&gt;(server);
</code></pre>
<p>This allows TypeScript to scream at us if we give an unexpected payload for an event and also gives us nice auto-completion.</p>
<p>This interface <code>ServerToClientEvents</code> and <code>ClientToServerEvents</code> are the same on both the backend and front end so it gives us really strong code maintainability.</p>
<p>By using <a target="_blank" href="http://socket.io">socket.io</a> and TypeScript together, I was able to create a more robust and efficient communication system between the client and server.</p>
<p>This helped me avoid common pitfalls and write better, more reliable code.</p>
<h3 id="heading-clean-architecture-for-socketio">Clean architecture for Socket.io</h3>
<p>I learned to make my code cleaner by following best practices</p>
<pre><code class="lang-typescript">io.on(<span class="hljs-string">"connection"</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Current active sockets: "</span>, io.engine.clientsCount);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`A user connected (<span class="hljs-subst">${socket.id}</span>)`</span>);

    roomHandler(io, socket);
    gameHandler(io, socket);
    messageHandler(io, socket);

    socket.on(<span class="hljs-string">"disconnect"</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`A user disconnected (<span class="hljs-subst">${socket.id}</span>)`</span>);
    });
});
</code></pre>
<p>In each of the "handler" files, I pass in <code>io</code> and <code>socket</code> as such:</p>
<p>In <code>roomHandler.ts</code>,</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> roomHandler = <span class="hljs-function">(<span class="hljs-params">
    io: Server&lt;ClientToServerEvents, ServerToClientEvents&gt;,
    socket: Socket&lt;ClientToServerEvents, ServerToClientEvents&gt;
</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Registered room handler"</span>);

    <span class="hljs-keyword">const</span> joinRoomHandler = <span class="hljs-keyword">async</span> (obj: RoomIDObject) =&gt; {
        <span class="hljs-comment">// Server to Client events</span>
        io.emit(<span class="hljs-string">'...'</span>, {});
    };

    <span class="hljs-comment">// Listener here (Client to Server events)</span>
    socket.on(EVENTS.JOIN_ROOM, joinRoomHandler);
}
</code></pre>
<p>Notice that I specifically cast the <code>io</code> and <code>socket</code> to use my generics while also highlighting where the client-to-server and server-to-client events happen.</p>
<h3 id="heading-the-troubles-with-using-socketio-with-nextjs">The troubles with using Socket.io with Next.js</h3>
<p>One of the challenges I faced during development was dealing with <a target="_blank" href="http://socket.io">socket.io</a> and next.js. Every time a user visited a new page, they would use a new connection of socket, which caused performance issues and made it difficult to manage multiple connections.</p>
<h4 id="heading-the-usesocket-hook">The useSocket hook</h4>
<p>The <code>useSocket</code> hook is meant to be a simple hook that can be used by every file that needs a socket instance. It checks if there is already a <code>socket</code> or if <code>useEffect</code> already ran, in which it will return.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useSocket</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [socket, setSocket] = useState&lt;Socket&lt;
        ServerToClientEvents,
        ClientToServerEvents
    &gt; | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);

    <span class="hljs-keyword">const</span> [effectRan, setEffectRan] = useState(<span class="hljs-literal">false</span>);

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"USESOCKET: USEEFFECT RUNNING"</span>);
        <span class="hljs-keyword">if</span> (effectRan || socket) <span class="hljs-keyword">return</span>;
        clientSocketInitializer();

        <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-keyword">if</span> (socket) {
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"USESOCKET: Disconnecting socket"</span>);
                (socket <span class="hljs-keyword">as</span> Socket).disconnect();
            }
        };
    }, [socket, effectRan]);

    <span class="hljs-keyword">const</span> clientSocketInitializer = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"USESOCKET: Client Socket Initializer Ran"</span>);
        <span class="hljs-keyword">const</span> url = process.env.NEXT_PUBLIC_SERVICES_URL!;
        <span class="hljs-keyword">const</span> s = io(url);
        setSocket(s);
        setEffectRan(<span class="hljs-literal">true</span>);
    };

    <span class="hljs-keyword">return</span> { socket };
}
</code></pre>
<p>This hook is only used in the context I created:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> SocketProvider = <span class="hljs-function">(<span class="hljs-params">{ children }: SocketProviderProps</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { socket } = useSocket();
    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;SocketProviderContext.Provider value={socket}&gt;
                {children}
            &lt;/SocketProviderContext.Provider&gt;
        &lt;/&gt;
    );
};
</code></pre>
<p>However, despite my efforts, I was unable to find a satisfactory workaround for this problem. I am still looking for solutions to improve the performance and reliability of <a target="_blank" href="http://socket.io">socket.io</a> in next.js.</p>
<h3 id="heading-not-everything-can-be-replicated-in-development">Not everything can be replicated in development!</h3>
<p>One challenge I faced during deployment was the issue of users continuously pressing the "continue" button. This problem was discovered while testing and playing with my friends.</p>
<p>Since the backend was regionally far, it would take a few hundred milliseconds to process each request. This led to the game skipping multiple users' turns.</p>
<p>To address this problem, we implemented a simple solution - a loading state that would be set to true when the user clicks on continue and then set to false when new data is received from the backend socket (in this case, the next player).</p>
<p>This allowed us to prevent the game from skipping turns and provided a smoother, more enjoyable user experience.</p>
<p>It is important to test your application after deployment in order to ensure that it is performing at its best. Testing allows you to measure the speed of the backend response and get a real-world user experience. By regularly testing your application, you can identify and address any performance issues before they become major problems. This will help you provide a better user experience and improve the overall stability of your application.</p>
<h3 id="heading-the-affair-of-messages-and-real-time-communication">The Affair of Messages and Real-time communication.</h3>
<p>In a recent new feature that is released to Troof! I added a feature where people can reply to other people's messages.</p>
<p>This feature means that the <code>chat</code> table needs to be changed. So I added a new column called <code>reply_to</code> which is an ID of chat.</p>
<p>So if I have a chat ID of 5 saying "hello", and a chat ID that replies to 5 with "hey".</p>
<p>The table would look something like this:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>ID</td><td>content</td><td>reply_to</td></tr>
</thead>
<tbody>
<tr>
<td>5</td><td>hello</td><td>null</td></tr>
<tr>
<td>6</td><td>hey</td><td>5</td></tr>
</tbody>
</table>
</div><p>However, it means that for every message we sent, we need to wait for the database to finish writing (which eats time), and then we will know what the ID of the "hello" message is.</p>
<p>The <strong>OLD</strong> way is shown below</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670408179997/mxDjUQ0sl.png" alt class="image--center mx-auto" /></p>
<p>The items in red are what are taking up a lot of time.</p>
<p>So I sat down pondered and wondered to myself "what is a better way for users to have that <em>real-time feeling</em> and also have the items written to the databases at the end of the day?"</p>
<p>Then I had an idea. What if I replaced the chat's ID with UUID and then on the backend, generate a UUID and write it to the database? <strong>This way, I do not need to wait for the writing to be complete because I will be the one supplying the ID.</strong></p>
<p>So here it is, the <strong>NEW</strong> way:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670408339825/9H-zBe0_A.png" alt class="image--center mx-auto" /></p>
<p>Note that the brown part is what changed and the green part is the background processes.</p>
<p>Using this new method. I can keep that real-time communication by the message with the ID broadcasted back and then have it written to the database in the background. I do not need to wait for the database to write!</p>
<h2 id="heading-authentication-over-socketio">Authentication over Socket.io?</h2>
<h3 id="heading-to-send-your-jwt-token-and-as-part-of-every-websocket-request">To send your JWT Token and as part of every WebSocket request</h3>
<p>First, make sure that your initialization of the client Socket.io passed it's token.</p>
<pre><code class="lang-typescript">io(<span class="hljs-string">"http://localhost:3030"</span>, {
    auth: {
        token,
    },
});
</code></pre>
<h3 id="heading-at-the-backend">At the backend...</h3>
<p><code>io.use</code> uses express' middleware pattern. This snippet below is of the <code>index.ts</code> server file and it's not part of any event handlers.</p>
<pre><code class="lang-typescript">io.use(<span class="hljs-function">(<span class="hljs-params">socket, next</span>) =&gt;</span> {
    <span class="hljs-comment">// This token is part of the auth.token sent above</span>
    <span class="hljs-keyword">const</span> { token } = socket.handshake.auth;

    <span class="hljs-keyword">if</span> (!token) {
        <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Authentication error"</span>));
    }

    <span class="hljs-keyword">const</span> verifiedData = JWT.verify&lt;PlayerIDObject&gt;(
        token,
        process.env.JWT_SECRET!
    );

    <span class="hljs-keyword">if</span> (!verifiedData) {
        <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Authentication error - cannot verify token"</span>));
    }

    socket.data.player_id = verifiedData.player_id;
    next();
});
</code></pre>
<h3 id="heading-accessing-the-playerid-in-your-listeners-is-as-easy-as">Accessing the "player_id" in your listeners is as easy as...</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> selectTruthHandler = <span class="hljs-keyword">async</span> (obj: RoomIDObject &amp; PlayerIDObject) =&gt; {
        <span class="hljs-comment">// ...</span>
        <span class="hljs-keyword">const</span> current_player_id = getCurrentPlayerID();

        <span class="hljs-comment">// Note that the data.player_id we did above is part of the socket object now!</span>
        <span class="hljs-keyword">if</span> (socket.data.player_id !== current_player_id) {
            <span class="hljs-comment">// ...</span>
        }
}
</code></pre>
<p>As you can see above, you can use JWT to have authentication as part of your Socket.io application to process if the user can have access to your sockets or not.</p>
<h3 id="heading-this-has-helped-me-to">This has helped me to</h3>
<ul>
<li><p>Disallow unauthorized "continue" turns in the game if the event is not received by the current player or the party leader.</p>
</li>
<li><p>Disallow getting unauthorized information about a player.</p>
</li>
<li><p>Disallow random people from joining rooms (someone using a POST request and etc.)</p>
</li>
</ul>
<h2 id="heading-end-to-end-encryption-of-messages">End-to-end encryption of messages</h2>
<p>As a programmer, my top priority is ensuring the security of my programs and systems. I understand the potential consequences of security breaches and take the necessary steps to protect against them.</p>
<p>I learn this stuff either as part of my curriculum at school or as self-learning.</p>
<h3 id="heading-asymmetric-encryption">Asymmetric Encryption</h3>
<p>I learned about the concept of Asymmetric encryption as part of an elective in school called "Cybersecurity Essentials" and how it is an end-to-end encryption system. One particular word that pops up in my mind is "RSA".</p>
<h3 id="heading-what-is-rsa">What is RSA?</h3>
<p>RSA is a widely used encryption and digital signature algorithm. It is named after its creators, Ron Rivest, Adi Shamir, and Leonard Adleman, who developed it in 1977. RSA uses the mathematical properties of prime factorization to encrypt and decrypt messages, making it a popular choice for securing sensitive data. The algorithm has been widely adopted in a range of applications, including securing online transactions, encrypting email messages, and providing authentication for users accessing networked systems.</p>
<h3 id="heading-why-was-rsa-my-choice">Why was RSA my choice?</h3>
<p>It was my choice because mainly it has a private and public key, and its asymmetric!</p>
<h3 id="heading-on-my-way-to-make-messages-end-to-end-encrypted">On my way to make messages end-to-end encrypted.</h3>
<p>I make up ideas on how do I make messages encrypted in-store and in transit and I made the data visualization below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670770476775/Ky62jI60b.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-the-main-points-are">The main points are</h4>
<ol>
<li><p>All messages in transit from the user to the server are encrypted using the room's public key.</p>
</li>
<li><p>All messages in the database is encrypted using the public key. (The backend decrypts the public key encrypted data with the private key)</p>
</li>
<li><p>All messages in transit from the server to the user are encrypted using the room's private key.</p>
</li>
<li><p>The client (or user) decrypts any new messages from the server using their public key.</p>
</li>
</ol>
<h3 id="heading-result">Result</h3>
<p>All messages are end-to-end encrypted</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670770744157/izbAgEqXD.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-debugging-socketio-using-postman">Debugging Socket.io using Postman?</h2>
<p>Postman recently introduced the ability for us to debug Socket.io events just like how we always use Postman to debug our REST API Endpoints.</p>
<p>I am able to send and listen to events just and this helps a lot rather than manually making a request everytime.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670770847089/cD8KVRxt5.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In conclusion, I learned a lot while developing my first ever WebSocket-powered game. I learned the importance of thinking about scalability and the benefits of using a separate backend. I also discovered the power of using TypeScript and <a target="_blank" href="http://socket.io">socket.io</a> together, which greatly improved the maintainability of my frontend and backend code. Finally, I learned the value of testing your application after deployment in order to identify and address any performance issues. Overall, I found that this project taught me many valuable lessons and I am excited to continue exploring the world of web development.</p>
<h3 id="heading-special-thanks">Special thanks</h3>
<p>I would like to extend a special thanks to Jaslyn, Xuan Rong, and Windy for playing with me after deployment. Their feedback and testing helped me discover some game-breaking bugs that I was able to fix. Their support and encouragement made this project a success and I am grateful for their contribution. Thank you!</p>
]]></content:encoded></item><item><title><![CDATA[I Rebuilt My Slow Web App with Next.js and Serverless - Here’s What Changed]]></title><description><![CDATA[Wait, why is there another Musicn?
When I first created the other Musicn, I was putting my skills at that time to use. I used my knowledge in API design, React, and the PERN stack to create the application.
As time passes, new technologies emerge, an...]]></description><link>https://blog.nabilridhwan.com/i-rebuilt-my-slow-web-app-with-nextjs-and-serverless-heres-what-changed</link><guid isPermaLink="true">https://blog.nabilridhwan.com/i-rebuilt-my-slow-web-app-with-nextjs-and-serverless-heres-what-changed</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[devlog]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Mon, 26 Sep 2022 03:55:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/npxXWgQ33ZQ/upload/v1664164532102/3zNCpDvrK.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-wait-why-is-there-another-musicn">Wait, why is there another Musicn?</h1>
<p>When I first created the other <a target="_blank" href="https://musicnapp.herokuapp.com">Musicn</a>, I was putting my skills at that time to use. I used my knowledge in API design, React, and the PERN stack to create the application.</p>
<p>As time passes, new technologies emerge, and I want to use them to create the best possible application.</p>
<h1 id="heading-introducing-the-new-musicn">Introducing, The new Musicn</h1>
<p>Musicn-next (or the new Musicn) is powered by Next.js and Serverless architecture. The speed is unbelievable as compared to the previous Musicn.</p>
<h2 id="heading-super-fast-musicn">Super-fast Musicn?</h2>
<p>The problem with the old <a target="_blank" href="https://musicnapp.herokuapp.com">Musicn</a> is that it is a slow website. It is really slow. Each API request can take up to 2 seconds, which is such a big no-no, especially for users.</p>
<p>In research by Jakob Nielsen titled <strong>"Respone Times: The 3 Important Limits"</strong>, he said:</p>
<ul>
<li><strong>0.1 second is about the limit for having the user feel that the system is reacting instantaneously</strong>, meaning that no special feedback is necessary except to display the result.</li>
<li><strong>1.0 seconds is about the limit for the user's flow of thought to stay uninterrupted</strong>, even though the user will notice the delay. Normally, no special feedback is necessary during delays of more than 0.1 but less than 1.0 seconds, but the user does lose the feeling of operating directly on the data.</li>
<li><strong>10 seconds is the limit for keeping the user's attention focused on the dialogue.</strong> For longer delays, users will want to perform other tasks while waiting for the computer to finish, so they should be given feedback indicating when the computer expects to be done. Feedback during the delay is especially important if the response time is likely highly variable since users will then not know what to expect.</li>
</ul>
<p>It is important to note that the response time is not the same as the time it takes to render the page and the time it takes to send the response to the user. And it is my mission to make the response time as fast as possible.</p>
<h1 id="heading-the-initial-problem">The Initial Problem</h1>
<h2 id="heading-converting-all-current-existing-apis">Converting all current existing APIs</h2>
<p>The old Musicn has many endpoints. And a bulk of them is focused on authentication, and my goal with the new Musicn is to make it a full-stack application. <strong><em>This new goal came to light so that development will be maintained on ONE codebase.</em></strong></p>
<h3 id="heading-solution">Solution</h3>
<p>However, Next.js allows us to make API routes simply by making a file in the <code>pages/api</code> directory. The mapping of the route will be the filename. So <code>user.ts</code> will map to <code>/api/user</code> in the URL. How cool! Moreover, Next.js API's API and syntax are akin to Express.js, making the switch even easier!</p>
<h2 id="heading-better-solutions-to-query-data-from-the-database">Better solutions to query data from the database</h2>
<p>The old Musicn uses <code>pg</code> for ultimate speed. But there is a trade-off regarding readability and how developers can get on board with development. It is hard because you need to type in SQL databases manually, and then you need to know the table names, column names, the datatype, and relations. AH! IT'S A MESS!</p>
<p><img src="https://media3.giphy.com/media/TgITYhN9sPj4aAOagG/giphy.gif?cid=ecf05e4799ygku4mlnu5mgzphutsvvmtnx8pkbmwyu78ylod&amp;rid=giphy.gif&amp;ct=g" alt="Angry" /></p>
<p>To integrate an ORM into the old Musicn will be counter-productive and many of its codes need to change, and that will make everyone go insane.</p>
<h3 id="heading-solution-1">Solution</h3>
<p>This new Musicn has one goal, and that is to use an ORM to get data. And Prisma is the ORM of choice. it is our choice because of how developer-friendly it is, the autogeneration of the schema files, and many other features that make it stand out from other ORMs such as Sequelize and TypeORM.</p>
<h1 id="heading-the-shortcomings">The shortcomings</h1>
<p>No implementation has 0 shortcomings, it's always a compromise. And building the new Musicn, I am humbled by some of the shortcomings.</p>
<h2 id="heading-next-js-middleware">Next JS middleware</h2>
<p>Although Next.js has a similar syntax to Express.js. One of the things I missed in Express.js is the middleware. After one hour of looking for middleware solutions and then looking for more, even trying the new Middleware feature introduced by Vercel. I used an old method:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> APITokenHandler <span class="hljs-keyword">from</span> <span class="hljs-string">'@/util/APITokenHandler'</span>;
<span class="hljs-keyword">import</span> { NextApiRequest, NextApiResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'next'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withProtect</span>(<span class="hljs-params">
    handler: IHandler,
    rejectWhen: APITokenHandler.REJECT_WHEN = 'none'
</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">req: NextApiRequest, res: NextApiResponse</span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(
            <span class="hljs-string">`=====CHECKING FOR TOKEN IN <span class="hljs-subst">${req.url}</span>, (REJECT TYPE = <span class="hljs-subst">${rejectWhen}</span>)=====`</span>
        );

        <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Cleanup code</span>
        <span class="hljs-keyword">if</span> (rejectWhen === <span class="hljs-string">'none'</span>) {
            <span class="hljs-keyword">if</span> (!APITokenHandler.hasAPIToken(req)) {
                <span class="hljs-keyword">return</span> APITokenHandler.reject(rejectWhen, req, res);
            }
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (rejectWhen === <span class="hljs-string">'has'</span>) {
            <span class="hljs-keyword">if</span> (APITokenHandler.hasAPIToken(req)) {
                <span class="hljs-keyword">return</span> APITokenHandler.reject(rejectWhen, req, res);
            }
        }

        <span class="hljs-keyword">return</span> handler(req, res);
    };
}
</code></pre>
<p>And then, it is used by wrapping the API handler:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> withProtect(handler <span class="hljs-keyword">as</span> IHandler);
</code></pre>
<h1 id="heading-deployment">Deployment</h1>
<p>Vercel provides hosting for Next.js sites, and it's a one-click solution. Register for an account, link your GitHub, select your repo and deploy! (and also some configurations of environment variables)</p>
<p>Some tweaks I have done are:</p>
<h2 id="heading-changing-the-country-of-where-the-serverless-function-runs">Changing the country of where the serverless function runs</h2>
<p>By changing the Serverless Function Region, the speed of API calls increased significantly. (and obviously will introduce some extra time if someone is further away from the Singapore server)
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664164111507/HEAA_xLQ-.png" alt="image.png" /></p>
<h2 id="heading-using-caching-headers">Using Caching headers</h2>
<blockquote>
<p>Read more: https://nextjs.org/docs/going-to-production#caching</p>
</blockquote>
<p>Using caching, I can cache responses for the APIs and pages. This makes it fast! I even made a class to handle caching headers for me:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> Cache {
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> headerName: <span class="hljs-built_in">string</span> = <span class="hljs-string">'Cache-Control'</span>;

    <span class="hljs-comment">/**
     * Revalidates the cache in the edge server while showing the old stale data meanwhile.
     * @param res NextApiResponse
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> revalidateInBackground(res: NextApiResponse): <span class="hljs-built_in">void</span> {
        res.setHeader(<span class="hljs-built_in">this</span>.headerName, <span class="hljs-string">'s-maxage=1, stale-while-revalidate'</span>);
    }

    <span class="hljs-comment">/**
     * Caches the data in the edge server and expires after the given seconds, to which, it'll refetch and cache new data again.
     * @param res NextApiResponse
     * @param expiresAfterSeconds number
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> inEdgeServer(
        res: NextApiResponse,
        expiresAfterSeconds: <span class="hljs-built_in">number</span>
    ): <span class="hljs-built_in">void</span> {
        res.setHeader(<span class="hljs-built_in">this</span>.headerName, <span class="hljs-string">`s-maxage=<span class="hljs-subst">${expiresAfterSeconds}</span>`</span>);
    }
}
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>I have learned a lot about the React ecosystem and the ecosystem of Musicn. I have redefined endpoints to be more useful and as simple as possible.</p>
<p>Along the development journey, I learned more about technologies and how they work, and how it is important.</p>
<p>I learned even more stuff from when I built the old Musicn.</p>
<ul>
<li>Caching</li>
<li>Server-side rendering</li>
<li>Next.js</li>
<li>Prisma (in production)</li>
<li>Serverless architecture</li>
</ul>
<p>Rebuilding applications using new shiny technology is always fun, it allows you to explore and learn more (even refresh your memory on what you thought you knew).</p>
<p>My message to upcoming aspiring developers: Build what you're passionate about, and continue building it. There's never a "complete" software, there's always room for improvement, and you shall pursue it in terms of watching talks of technologies and figuring out a way how to integrate it with your product.</p>
]]></content:encoded></item><item><title><![CDATA[Run a Signal Proxy using DigitalOcean Droplets and Cloudflare Domains! #IRanASignalProxy]]></title><description><![CDATA[I have already set up a proxy. Please email me at contact@nabilridhwan.com privately!

Read more here: https://signal.org/blog/run-a-proxy/
This article uses DigitalOcean Droplets and a Domain name from Cloudflare to run a Signal Proxy and focuses on...]]></description><link>https://blog.nabilridhwan.com/run-a-signal-proxy-using-digitalocean-droplets-and-cloudflare-domains-iranasignalproxy</link><guid isPermaLink="true">https://blog.nabilridhwan.com/run-a-signal-proxy-using-digitalocean-droplets-and-cloudflare-domains-iranasignalproxy</guid><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Fri, 23 Sep 2022 05:26:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/BPfPrYLQZuQ/upload/v1664162476821/NxEwQZAGJ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>I have already set up a proxy. Please email me at <code>contact@nabilridhwan.com</code> privately!</p>
</blockquote>
<p>Read more here: https://signal.org/blog/run-a-proxy/</p>
<p>This article uses DigitalOcean Droplets and a Domain name from Cloudflare to run a Signal Proxy and focuses on being as user-friendly as possible.</p>
<h1 id="heading-what-youll-need">What you'll need</h1>
<ul>
<li>A VPS (DigitalOcean) ($4/month) - Free credits for students under the GitHub Student Developer pack</li>
<li>A Domain Name (Cloudflare) ($14 per year)</li>
</ul>
<h1 id="heading-creating-a-digitalocean-droplet">Creating a DigitalOcean Droplet</h1>
<ol>
<li>Create a new Droplet in DigitalOcean. The basic $4.00/mo plan will help.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663909397784/ANholYcVR.png" alt="1.png" /></li>
<li>Create new SSH Keys by clicking the new SSH Keys. The generation of SSH keys could be found in the original DigitalOcean documentation. But in this case, I used 1Password to generate an SSH key.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663909493887/x-nnPPnZ8.png" alt="2-create-ssh-keys.png" /></li>
</ol>
<h1 id="heading-configure-domain-in-cloudflare-dashboard">Configure domain in Cloudflare Dashboard</h1>
<ol>
<li>Copy your Droplet's IP Address
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663909850631/tNi8hMF80.png" alt="1-copy-ip-address.png" /></li>
<li>Go to your Cloudflare dashboard, log in and configure one of your domains. (We are going to add a subdomain)</li>
<li>Configure accordingly by making an <code>A</code> record with the name which points to the IP address you copied.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663909951540/eyhjHo6qB.png" alt="image.png" /></li>
<li>Click save.</li>
<li>Wait. (Different timing for different domain name providers but mine took approximately ~10 minutes)</li>
</ol>
<h1 id="heading-installing-the-proxy-on-your-new-droplet">Installing the proxy on your new Droplet</h1>
<p><strong>Note, you can follow the instructions written by Signal: https://signal.org/blog/run-a-proxy/</strong></p>
<ol>
<li>After creating the droplet, wait a little while for the droplet to set up, and then right-click on the three dots and click on 'Access Console.'
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663909649019/DKGr_476Z.png" alt="3-access-console.png" /></li>
<li>You'll be redirected to a new page, remember to log in as <code>root</code> and click on launch droplet console.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663909685270/SYho-JRQz.png" alt="4-login-as-root.png" /></li>
<li>From here on, you can follow the article written by Signal: https://signal.org/blog/run-a-proxy/</li>
</ol>
<h1 id="heading-what-i-did">What I Did</h1>
<p>If you didn't follow the article, you could follow what I did.</p>
<ol>
<li>Install docker, docker-compose, and git by running <code>sudo apt update &amp;&amp; sudo apt install docker docker-compose git</code>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663910443454/PQYQp60yB.png" alt="7-install-docker-docker-compose-git.png" /></li>
<li>Clone the Signal TLS Proxy repo from GitHub by running <code>git clone https://github.com/signalapp/Signal-TLS-Proxy.git</code> and change directory to the newly cloned repo by running <code>cd Signal-TLS-Proxy</code>.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663910511870/P2462KhcC.png" alt="9-clone-and-cd.png" /></li>
<li>Run the helper script provided by Signal that configures and provisions a TLS certificate from Let's Encrypt by running <code>sudo ./init-certificate.sh</code>. At some point, you'll be asked to enter your domain name. Enter the domain name you configured.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663911242702/EJzeN5JnL.png" alt="10-configure-cert.png" /></li>
<li>Use Docker Compose to launch the proxy by running <code>sudo docker-compose up --build -d</code>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663910599597/t-ylPW0xJ.png" alt="11.1-run-docker-compose-build-detatched-sudo.png" /></li>
</ol>
<h1 id="heading-voila">Voila!</h1>
<p>You're done! Share your Proxy with the world. A recommendation is not to share your proxy URL in public since they can just add your IP to a blacklist. Instead <em>Signal</em> encourages people to DM each other!</p>
<h1 id="heading-credits">Credits</h1>
<p>Signal for having an easy-to-follow article: https://signal.org/blog/run-a-proxy/</p>
<h1 id="heading-footnote">Footnote</h1>
<p>Sorry if this article is not explained in detail on the background information. It's just a simple article to get users with resources to join and help!</p>
]]></content:encoded></item><item><title><![CDATA[What have I discovered when building Musicn?]]></title><description><![CDATA[Musicn? What's that?
I had this long desire to create a web application that allows people to see what I listen to on Spotify. I have had several attempts at it. Until I made Musicn. Musicn allows a Spotify user to sign up for an account and have the...]]></description><link>https://blog.nabilridhwan.com/what-have-i-discovered-when-building-musicn</link><guid isPermaLink="true">https://blog.nabilridhwan.com/what-have-i-discovered-when-building-musicn</guid><category><![CDATA[JWT]]></category><category><![CDATA[Express]]></category><category><![CDATA[React]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Nabil Ridhwan]]></dc:creator><pubDate>Fri, 11 Mar 2022 02:08:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/DBGwy7s3QY0/upload/v1646963626960/TcOCJBa7q.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-musicn-whats-that">Musicn? What's that?</h1>
<p>I had this long desire to create a web application that allows people to see what I listen to on Spotify. I have had several attempts at it. Until I made <a target="_blank" href="https://musicnapp.herokuapp.com">Musicn</a>. Musicn allows a Spotify user to sign up for an account and have their own Spotify profile to share with others. When others click the link, they can see their current listening, top songs of the month, and recently played songs.</p>
<h1 id="heading-lets-talk-about-storing-data">Let's talk about Storing data</h1>
<h2 id="heading-relational-vs-non-relational-database">Relational vs Non-Relational database</h2>
<p>I knew I wanted to use a relational database. I was introduced to the concept of a relational database in the previous semester in school, and I wanted to get my feet more <em>wet</em>. Since I'm a student, I wanted to go cheap since I'm broke and have no money 😭.</p>
<h2 id="heading-choice-of-database">Choice of database</h2>
<p>I settled on <a target="_blank" href="https://supabase.com/">Supabase</a> just because it's PostgreSQL, user-friendly because you can insert into the database with just four lines of code without typing any SQL and comes with a table editor in its online dashboard.
<img src="https://media.giphy.com/media/DffShiJ47fPqM/giphy.gif" /></p>
<p>I used the official Supabase library for Javascript and realized that database requests take long (around 200+ ms per query). The issue became apparent to me while I was doing Authorization, and I realized that if I have no credentials, it rejects the request in a mere 7ms. However, with credentials, it had to do some queries, resulting in 200+ ms of query time!
<img src="https://media.giphy.com/media/26ufcVAp3AiJJsrIs/giphy.gif" /></p>
<p>I knew something was wrong because the query times for the database should be way shorter than that (based on my school final assignment for the year). </p>
<p>I made a huge realization. The realization is that the Supabase library fetches the Supabase API, which takes around 200+ ms to resolve, and it all started making sense. So instead, I used a library called <a target="_blank" href="https://www.npmjs.com/package/pg">pg</a>. And my query times are shortened to 90-100+ ms. Sure not a big difference but after several testing, I concluded that the difference it made to the Heroku deployment is <strong>46.8% faster!</strong></p>
<h1 id="heading-talking-about-security">Talking about Security?</h1>
<h2 id="heading-security-of-user-data">Security of user data</h2>
<p>Security plays a massive role in Musicn since it is hosted publicly for anyone to use. The data that are used and collected are:</p>
<h4 id="heading-musicn-when-signing-up-for-an-account">Musicn (when Signing up for an account)</h4>
<ul>
<li>Email</li>
<li>Username</li>
<li>Password</li>
</ul>
<h4 id="heading-spotify-when-linking-your-spotify-account">Spotify (When linking your Spotify account)</h4>
<ul>
<li>Email</li>
<li>Refresh Token</li>
<li>Display Name</li>
<li>Profile Picture URL</li>
</ul>
<p>It's essential to keep these data locked away and secure. In terms of obtaining user data, I took extra measures such as:</p>
<ul>
<li>Not exposing the Spotify's Refresh Token online (If it is exposed, it allows the attackers to get the token and make a request to get the user's email. This is dangerous, and hence Musicn acts as a Proxy API)</li>
<li>Only display crucial information for displaying on the page (This means creating multiple model files to fetch the different sets of data from the database)</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646963044763/a1Gy53ZIr.png" alt="cool.drawio.png" /></p>
<blockquote>
<p>How Musicn works in terms of Proxy API to Spotify Web API</p>
</blockquote>
<p>Using Supabase Javascript Library
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646962080481/gIS3uE23I.jpeg" alt="old.jpeg" /></p>
<p>Using pg Library
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646962124419/VvJstcXy6.jpeg" alt="new.jpeg" /></p>
<p>Note how the fetch request for <code>nabil</code> is way faster (1.18s vs. 558ms) (ignore the top_playing and currently_playing fetches because that uses Musicn as a Proxy API for Spotify Web API)</p>
<h2 id="heading-security-of-jwt-tokens">Security of JWT tokens</h2>
<p>Musicn allows users to edit their profile by heading to the Profile page. Users can change their username so they can visit their friend's profile by going to a link such as <a target="_blank" href="https://musicnapp.herokuapp.com/user/nabil">https://musicnapp.herokuapp.com/user/nabil</a> so hence it is a must to store the JWT token somewhere as part of its front end so that we can send requests to the backend for Authorization to the Profile page.</p>
<p>So I searched online to get some idea on how to store tokens securely because, in my previous assignment, we stored it in LocalStorage, and it just screams <strong>SECURITY BREACH</strong> to me! <strong>For those who don't know, storing JWT or anything remotely "secretive" in the LocalStorage is a big no-no!</strong></p>
<p>After countless hours spent watching excellent talks by developers, I settled on using cookies. At first, it alarmed me because it is stored on the browser, but you can set some parameters to make the cookies way more secure upon further research. You can read this <a target="_blank" href="https://cheatcode.co/tutorials/how-to-implement-secure-httponly-cookies-in-node-js-with-express">excellent article</a> written by Ryan Glover explaining more about implementing safe cookies.</p>
<pre><code class="lang-js">res.cookie(<span class="hljs-string">"cookie-name"</span>, {
    <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>,
    ...options
})
</code></pre>
<p>For my explanation, <code>httpOnly</code> cookies do not allow the browser to access the cookie via Javascript code.</p>
<h1 id="heading-misc-security-stuff">Misc Security Stuff</h1>
<p>Like what they say, security is obscurity, and that means not letting the attackers know what your application is built on or what database it uses. <em>But Ironically</em>, Musicn is open-sourced, and pretty much the world knows it's built on</p>
<ul>
<li>Express</li>
<li>React</li>
<li>Tailwind CSS</li>
<li>Supabase</li>
<li>pg</li>
</ul>
<p>But if you're building an actual world application, you can remove the <code>x-powered-by</code> header, which tells the attacker what framework you are using, and using the backend as a proxy to APIs make Musicn somewhat more secure (because no keys are exposed)</p>
<h1 id="heading-using-react-as-a-frontend-on-the-same-domainport-as-the-express-app">Using React as a frontend on the same domain/port as the Express app?</h1>
<p>One group of developers builds the backend and the frontend separately, but I was dead on making the backend and frontend run on the same domain.</p>
<p>In the building phase, I used fetch in the React application to fetch API requests. It went something along the lines of:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">"http://localhost:4000/api/user"</span>)
</code></pre>
<p>And then it dawned on me. What if I changed the port number? What if I want to change the host? Well, adding a variable may help. Something like:</p>
<pre><code>const host <span class="hljs-operator">=</span> <span class="hljs-string">"http://localhost:4000"</span>
fetch(host <span class="hljs-operator">+</span> <span class="hljs-string">"/api/user"</span>)
</code></pre><p>But that means I have to write the host variable at every component file I created. Another alternative you could do is to export the <code>host</code> variable out of a single Javascript file but it became troublesome every single time I switch between development and production. Do I have to find my way to change the value of <code>host</code>?</p>
<p>That is when I found "Proxying" in React. Since I know that my React app will run on the same host and domain as my backend, I can proxy every fetch request to this host defined in <code>package.json</code> in the React project folder.</p>
<pre><code class="lang-json">     ...
    <span class="hljs-string">"proxy"</span>: <span class="hljs-string">"http://localhost:4000"</span>
} <span class="hljs-comment">// EOF</span>
</code></pre>
<p>From then on, all my fetch requests can look something like this</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">"/API/user"</span>)
</code></pre>
<p>So every fetch request in <strong>development</strong> will pass it through to <code>http://localhost:4000</code>, and later in production, the <code>proxy</code> value is <strong>discarded</strong> and will pass through the domain.</p>
<p>You can read more about what I've talked about <a target="_blank" href="https://create-react-app.dev/docs/proxying-api-requests-in-development/">here</a></p>
<h1 id="heading-in-conclusion">In conclusion</h1>
<p>Creating Musicn is a wonderful journey. I researched how to make my application way more secure and pleasant to use. Here are some take-aways:</p>
<ul>
<li>Using pg instead of Supabase can give you speed advantages but <em>results may vary</em> (depends on your use case, if you're using Supabase Auth, then yeah, you have to stick with it)</li>
<li>Security is obscurity (API only shows data that is needed and does not show everything)</li>
<li>Secure Cookies using <code>httpOnly</code> and <code>secure</code> options.</li>
<li>Proxying API requests for development in React application.</li>
</ul>
]]></content:encoded></item></channel></rss>