<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Obum on Medium]]></title>
        <description><![CDATA[Stories by Obum on Medium]]></description>
        <link>https://medium.com/@obumnwabude?source=rss-66ac74c8b338------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*dOW0u4bYM7mf2HuT0Izanw.jpeg</url>
            <title>Stories by Obum on Medium</title>
            <link>https://medium.com/@obumnwabude?source=rss-66ac74c8b338------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 19:37:45 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@obumnwabude/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Road to Mastery — Being a Google Developer Expert]]></title>
            <link>https://medium.com/google-developer-experts/road-to-mastery-being-a-gde-6157b05ed803?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/6157b05ed803</guid>
            <category><![CDATA[cloud]]></category>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[google]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Sat, 27 Dec 2025 21:21:18 GMT</pubDate>
            <atom:updated>2026-10-22T13:23:05.855Z</atom:updated>
            <content:encoded><![CDATA[<h3>Road to Mastery — Being a Google Developer Expert</h3><h3>Table of Contents</h3><p>· <a href="#a660">It’s been 5 years …</a><br>· <a href="#782e">What Changed with me?</a><br>· <a href="#b97d">Entering Mobile Development</a><br>· <a href="#4517">Being in the Flutter World</a><br>· <a href="#0d3d">Entering Blockchain &amp; Web3</a><br>· <a href="#c41d">The Maestro I’ve become</a><br>· <a href="#0dab">The Expert’s Paradox</a><br>· <a href="#ad8c">The #GDE Exposure</a><br>· <a href="#4660">Honorary Mentions</a><br>· <a href="#5e0d">Parting Words</a></p><h3>It’s been 5 years …</h3><p>It’s been 5 years since I last shared my developer career story. That was back in 2020, when I had become a <a href="https://goo.gle/gdg">Google Developer Groups</a> on Campus (GDGoC) Lead. At the time, the GDGoC program was called Developer Student Clubs (DSC). <a href="https://stories.obumnwabude.com/u-1d5d0a7462e">In that piece, I wrote about my story</a> from 2015, when I was first exposed to programming in secondary school, through 2020, when I was deeply involved in the community.</p><p>Now, in 2025, another five years have passed.</p><p>In this retrospective, I will walk you through my unique experiences, lessons learned, more community engagements, and of course, about being in the Google Developer Experts (GDE) program.</p><h3>What Changed with me?</h3><p>In 2020, I was a full-stack <strong>web</strong> developer. Today, I’m a full-stack <strong>blockchain</strong>, <strong>mobile</strong>, and <strong>web</strong> developer. Back then, I was a <strong>GDGoC lead,</strong> organizing local events in school. Now, I’m a <strong>GDE,</strong> mentoring others, speaking at greater events, and sharing expert knowledge on Google technologies.</p><p>At the time, I had built a card game with Java and followed various tutorial projects. I had also built the <a href="https://web.archive.org/web/20201024150415/https://obumnwabude.com/">first version of my portfolio website</a> and delivered my first freelance website gig. Shout out to my cousin, <a href="https://facebook.com/collinsbukatti.nwabude.1">Ebuka</a>, for that referral.</p><p>Since then, I have worked with various teams and built multi-user production software. I have learned about system architectures and gained mind-blowing developer experiences. I won a big hackathon, published many articles, and spoke at multiple events. I have met many people and, of course, have grown older 😎.</p><p>It has been an additive process. Learning a new technology does not discard old ones. New experiences do not invalidate previous ones. I am simply stacking knowledge which in turn builds expertise.</p><p>Overall, I keep learning. I’ve learned a lot about Cloud and AI. I build with AI and manage deployments on Google Cloud. Last year, I earned the <a href="https://www.credly.com/badges/94e53f51-9c70-48c8-b87c-5a9dd868ca54/public_url">Associate Cloud Engineer Certification</a>. This year, I’ve trained different audiences on “building with AI”.</p><iframe src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=twitter&amp;url=https%3A//x.com/i/status/1944141663644885143&amp;image=" width="500" height="281" frameborder="0" scrolling="no"><a href="https://medium.com/media/688cf9b4e52b4fa9477bba60b992360e/href">https://medium.com/media/688cf9b4e52b4fa9477bba60b992360e/href</a></iframe><h3>Entering Mobile Development</h3><p>By the end of 2019, I was already this good in many web technologies. I wanted to venture into something different. In my locality, in most freelance gigs, mobile apps are the next in demand after websites. So I felt like digging into apps.</p><p>To get started, I took the time to overview mobile technology and decide which to use. I did some research and checked out Android <em>native</em> with Kotlin, React Native with JavaScript, and Flutter with Dart. After “testing the waters”, I knew Flutter was for me.</p><p>Flutter had the most seamless developer experience. Its hot-reload feature and cross-platform ability were mind-blowing. I loved how Flutter code felt close to plain English (due to widget names and syntax). The Flutter community was also very inviting.</p><p>I spent time reading the <a href="https://dart.dev/">Dart</a> and <a href="https://flutter.dev/">Flutter</a> docs. They were easy to grasp because I was already good at coding and UI. To build my skills further, I rebuilt the Joker card game I<a href="https://github.com/obumnwabude/joker-java"> had done in Java</a> with better <a href="https://github.com/obumnwabude/joker/tree/pick-rules">algorithms and data structures in Dart</a>. I also read a lot of code from open-source Flutter apps to get the hang of it.</p><h3>Being in the Flutter World</h3><p>I made cool cash with Flutter. Over the years, I worked with <a href="https://www.linkedin.com/company/vorbtech/">Vorbtech Innovative Solutions</a> to build apps for clients. I worked with <a href="https://www.linkedin.com/company/coders-triangle/">Coders Triangle</a> to build CTLearn, with <a href="https://www.linkedin.com/company/lokdon/">LokDon</a> to build WalletSMSLockr, and with <a href="https://www.linkedin.com/company/dadofood/">Dado Food</a> to maintain their ecosystem of apps (Customer, Rider, and Vendor) across Google Play and Apple stores.</p><p>There were other side gigs I delivered successfully.</p><p>Getting those jobs was purely from the community. I was always referred or recommended by others. I didn’t get to apply at any point. Indeed, the community is truly priceless.</p><p>Around 2022, I got accepted as a freeCodeCamp contributor. As a contributor, I write articles that are read by developers across the community. I <a href="https://www.freecodecamp.org/news/author/obumnwabude/">wrote articles</a> about things I had learned and things I had figured out while studying and using Flutter.</p><p><a href="https://www.linkedin.com/posts/obumnwabude_joining-freecodecamp-as-a-contributor-activity-6911558176509235200-sgTJ?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAACtpU6YBim85Y9wQZIgjO1YsZoEI4osafOA">🥳🥳🥳 Joining freeCodeCamp as a contributor! | Obumuneme Nwabude | 19 comments</a></p><p>The first article was about “<a href="https://www.freecodecamp.org/news/how-to-implement-any-ui-in-flutter/">How to Implement Any UI in Flutter</a>”. Surprisingly, I received the most “thanks tweets” for it. Guess it is really helpful to beginners.</p><p>I also wrote articles on Flutter that got published on <a href="https://css-tricks.com/author/obumnwabude/">CSS-Tricks</a> and <a href="https://web.archive.org/web/20231204031325/https://sweetcode.io/author/onwabude/">Sweetcode</a>.</p><p>Aside from coding and writing articles, in 2022, during my free time, I <a href="https://stackoverflow.com/users/13644299/obum">answered questions in Stackoverflow</a> on anything I know. Additionally, across these years, I have had many <a href="https://obumnwabude.com/community">speaker and workshop sessions</a> over the years on Flutter.</p><p>Naturally, I’m a good orator. I use this skill to teach and share about Flutter. I’ve had sessions on UI, animations, forms, state management, CustomPaint, Flutter for web developers, Flutter with blockchain, panel sessions, and more.</p><p>In my opinion, my most outstanding Flutter session was at <a href="https://devfestlagos.com/"><strong>#DevFestLagos2024</strong></a>. It was a highly technical workshop on “How to build Android Bubbles in Flutter”. It was inspired by my experience an building SMS app. At the time, there was no content online on the topic. The <a href="https://www.youtube.com/watch?v=FAVlRf6usco">recorded session is on Youtube</a> and the <a href="https://github.com/keepdeploying/bubbles_in_flutter_workshop">workshop code is on GitHub</a>.</p><figure><img alt="Obum speaking at DevFest Lagos 2024." src="https://cdn-images-1.medium.com/max/1024/1*skfG34F_4sZCAsMiiwvzWQ.jpeg" /><figcaption>Obum speaking at DevFest Lagos 2024.</figcaption></figure><h3>Entering Blockchain &amp; Web3</h3><p>As with my habit of adopting something new, by the end of 2020, the next buzzing tech word in my locality was Crypto. I embarked on my blockchain journey, overviewed Solidity and Rust, and got started building on-chain.</p><p>There are interesting engineering differences in <a href="https://en.wikipedia.org/wiki/Web3">Web3 vs Web2</a>. I guess I will put up an article about this later. At its core, blockchain apps focus on distributed access and security concerns.</p><p>In all honesty, Web3 is a still a working progress. Yes we have working blockchains and continuous adoption. However, there is still technical groundwork to do for seamless real-world integrations.</p><p>For example, you can’t run long loops (for, while, …) within the chain, or the transaction will fail. Or is it the <a href="https://medium.com/@0s.and.1s/ethereums-solidity-part-iv-smart-contract-byte-code-optimization-315bef0dd1d2">24kb limit of Solidity contracts</a> you have to handle? Or is it the awesome gas fees 😂</p><p>Crypto is a work-in-progress AND it has its proofs-of-work.</p><p>Regardless, I’m now a guru in this domain. Together with my co-founder, we’ve built <strong>three</strong> solid Web3 <a href="https://www.investopedia.com/terms/d/decentralized-applications-dapps.asp">dApp</a>s with production users. One of which is <a href="https://chainbills.xyz/">Chainbills</a> that won 2nd place in the <a href="https://earn.superteam.fun/listing/build-multichain-apps-using-wormhole">Wormhole side-track of the Solana Renaissance Colosseum hackathon in 2024</a>.</p><p><a href="https://earn.superteam.fun/listing/build-multichain-apps-using-wormhole">Best Multichain Project | Superteam Earn</a></p><p>Why crypto? Well, I admire the idea of decentralization: giving power to the community.</p><p>It is difficult, or almost impossible, to fully run things successfully in a decentralized way. We need some centralization at certain points. “Yin-Yang” mixture kind of. Either way, blockchain is cool. I’m glad to have entered there and I encourage you to check out the technologies we have here.</p><h3>The Maestro I’ve become</h3><p>Programming languages usually vary in syntax and few specialties. In general, you can re-build the exact same software in another language. Semantics in language usage is usually the difference.</p><p>Figuring this out explains why it is not about the number of programming languages you know, but how good you are at core programming. In diverse software categories, there are always similar code patterns.</p><p>I’ve grasped that core programming and it’s like second nature to me now. In fact, because of this, it is now effortless for me to venture into a new programming domain (I’m thinking of the next one already 😂)</p><p>I have improved to the point that I know what a given block of code should do just by reading it. When I read code, I instantly understand the execution flow.</p><p>Nowadays, I mostly do code reviews for projects rather than programming like I did before. I quickly spot UI flaws, logic errors, and security mishaps in codebases of languages I know.</p><p>Behind the scenes, it has been years of efforts that naturally resulted in mastery. You know you have become good at something when you can do so much of it while also teaching or inspiring people.</p><iframe src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=twitter&amp;url=https%3A//x.com/i/status/2003859656037249081&amp;image=" width="500" height="281" frameborder="0" scrolling="no"><a href="https://medium.com/media/4396b03f54fe91afec772c7e4870a7e1/href">https://medium.com/media/4396b03f54fe91afec772c7e4870a7e1/href</a></iframe><p>They say programming is quite different from coding. The difference is in the thinking. The <em>engineering</em> in software engineer is that you can plan the solution or algorithm of a problem. That’s the programming part.</p><p>At CTO levels, we do this across the system and in the bits of each sub-service. AI can’t do that. It can write the code we tell it to. But, it can’t have the entire product context at all times like we humans always do. That’s by the way.</p><p>I’m not saying I’m perfect. Learning never ends and I keep growing. But once in a while, it’s okay to look back and celebrate how far we’ve come.</p><p>After gaining solid grounds in Blockchain technologies, I embarked on Cloud &amp; AI. With my technical expertise, it was effortless to grow far and fast in these domains.</p><p>At <a href="https://devfestabuja.com/">DevFest Abuja</a> this year, I anchored a workshop on “Leveraging BigQuery for Business Data in Google Cloud”.</p><iframe src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=twitter&amp;url=https%3A//x.com/i/status/2004098795068703183&amp;image=" width="500" height="281" frameborder="0" scrolling="no"><a href="https://medium.com/media/acafc8234050b9af1520d79601f1b45f/href">https://medium.com/media/acafc8234050b9af1520d79601f1b45f/href</a></iframe><h3>The Expert’s Paradox</h3><p>Sometimes, what makes a <em>maestro</em> (master) is strength in multiple related domains, not only high expertise in one. At this high level of orchestrating systems, I clearly see how technical knowledge in different areas comes together to make me a good system admin. You know, “jack of all trades” kind of approach.</p><p>Notwithstanding, I’m young and I’ve been busy. By busy, I mean very busy. Work, life, community, and other responsibilities consume so much of my time. I’m happy they’ve been fruitful. I pray the future remains brighter than ever.</p><p>I rarely have time these days, not even to share updates on social media. There are many technical articles I want to write and publish, but there’s no time. Hopefully, in this new year, I will do better.</p><p>Still because of time constraints, it took me a long while to draft this retrospective story. I had it all planned in my mind but no time to write it down. I’ve been busy being an expert 😅. I am glad that this story is finally in written words.</p><h3>The #GDE Exposure</h3><p>The <a href="https://goo.gle/gde">Google Developer Experts (GDE)</a> program consists of individuals who meet two criteria:</p><ol><li>Have advanced-level knowledge and experience in a specific Google technology AND</li><li>Continuously contribute to the community around that technology.</li></ol><p>The program is both a community for its members and an award for their voluntary contributions. Community contributions include open-source code activity, published libraries, writing articles, making videos, speaking at conferences, Stackoverflow answers, etc. Once you have a track record of such over the years, you are a good candidate for the program.</p><p>At various events, I met mentors and individuals who introduced and recommended me to the GDE program. By mid-2024, I applied, was interviewed, and got accepted.</p><iframe src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=twitter&amp;url=https%3A//x.com/i/status/1816886399842595237&amp;image=" width="500" height="281" frameborder="0" scrolling="no"><a href="https://medium.com/media/6a7df5682d87782a6a06c443e20079ef/href">https://medium.com/media/6a7df5682d87782a6a06c443e20079ef/href</a></iframe><p>As a GDE, I have enjoyed countless benefits from Google:</p><ul><li><a href="https://g.dev/obumnwabude">Global Recognition</a> and Listing on the <a href="https://developers.google.com/community/experts/directory">Experts Directory</a></li><li>Access to the global and local network of GDEs</li><li>More speaking opportunities</li><li>Invitations to Google-run events</li><li><a href="https://developers.google.com/program/plans-and-pricing">Google Developer Premium Subscription</a> (my favorite; it provides other premium resources)</li><li>etc…</li></ul><p>Becoming an Expert has catapulted my developer career. I have learned much more and met more industry leaders.</p><p>I volunteer because I naturally can do it and others benefit from it. I have helped and mentored various individuals. I will continue this especially as the GDE program now enables me to make more impact.</p><p>Recently, thanks to Google, I was at Nairobi, Kenya for the GDE Sub-Saharan Africa (SSA) #TrainTheTrainer event. I met with fellow GDEs from the region, learned more about AI and Hardware, and gave feedback about Google Products. It was a unique networking experience to spend quality time with other Experts.</p><p>I was very active and notorious at the event — helping with minor tasks, exchanging contacts and taking photos with everyone present. After all, we are a closed group 😎.</p><p><a href="https://www.linkedin.com/posts/obumnwabude_trainthetrainer-activity-7394655669859262464-7ab0?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAACtpU6YBim85Y9wQZIgjO1YsZoEI4osafOA">#trainthetrainer | Obumuneme Nwabude</a></p><figure><img alt="Obum at the Google Kenya Office for the GDE SSA #TrainTheTrainer, Nairobi 2025." src="https://cdn-images-1.medium.com/max/800/1*APCuSzLPRJHkYAKfcljjow.jpeg" /><figcaption>Obum at the Google Kenya Office for the GDE SSA #TrainTheTrainer, Nairobi 2025.</figcaption></figure><h3>Honorary Mentions</h3><p>That’s it for my 5-year story. But before we part, I would like to mention a few people and share my future plans.</p><p>First is my co-founder, Maxwell. We first met at a bank and were sharing our displeasure with the money systems. Next day, we were coincidentally together in a hospital. One thing led to another and now we are family.</p><p>Max is one-of-a-kind. His resilience needs to be studied. I hold him in high regard. He pushed me deep into the crypto space and he has been involved in many parts of my personal life. Thank you, sir.</p><figure><img alt="Obum with Maxwell at TEDx Enugu 2024." src="https://cdn-images-1.medium.com/max/1024/1*VSmJuzr3BaShQgkodHhXZg.jpeg" /><figcaption>Obum with Maxwell at TEDx Enugu 2024.</figcaption></figure><p>Another person is the one who introduced me to the DSC program (now GDGoC). We met during the Google Africa Developer Scholarship. He was the mentor of my peer learning group. By following his lead, I was exposed to many technologies and industry leaders.</p><p>Today, <a href="https://x.com/geektutor">Akinjobi Sodiq</a> works with the Google Developer Ecosystem. But to me, he remains my first eye-opener and I owe him great respect. Thank you sir.</p><figure><img alt="Obum with Sodiq at GDE SSA #TrainTheTrainer, Nairobi 2025." src="https://cdn-images-1.medium.com/max/959/1*YJM26JmMp8MVMVB8MhAH0A.jpeg" /><figcaption>Obum with Sodiq at GDE SSA #TrainTheTrainer, Nairobi 2025.</figcaption></figure><p>Next on my list is <a href="https://x.com/auwalms">Auwal MS</a>. He was the program manager for the DSC program in SSA and later was in charge of GDGs for some time. He guided me and gave me perfect advice at strategic points. I owe a good part of my success to him. Thank you sir.</p><figure><img alt="Obum with Auwal at DevFest Owerri 2025." src="https://cdn-images-1.medium.com/max/962/1*IHatbXSY6MWXTWnfy29xkQ.jpeg" /><figcaption>Obum with Auwal at DevFest Owerri 2025.</figcaption></figure><p>Someone else I celebrate is <a href="https://x.com/DeniseAllela">Eunice Allela</a>. She oversees GDEs in the SSA region. We met last year, before I became a GDE. She was the final person to recommend me for the program. Since then, I haven’t let her rest. Sorry ma, but it’s my little way of trying to stay impactful. Thanks for the many things you do.</p><figure><img alt="Obum with Allela at GDE SSA #TrainTheTrainer, Nairobi 2025." src="https://cdn-images-1.medium.com/max/960/1*rHJdqN3VKDaLMDM0gyMwIQ.jpeg" /><figcaption>Obum with Allela at GDE SSA #TrainTheTrainer, Nairobi 2025.</figcaption></figure><p>There are many others who deserve honorary mentions here. I’m quite social and have a huge “people dictionary”. I’m sorry I can’t call out everybody, but believe me, I sincerely appreciate you. Is it <a href="https://x.com/AliFestus">Festus</a> that kept recommending me? Is it <a href="https://x.com/phenomenuel">Phenom</a>, <a href="https://x.com/yfdtweets">Leo</a>, <a href="https://igabbygreat/">Gabriel</a>, … who fulfilled one task or the other for me?</p><p>Or is it the multiple peers, colleagues, GDG organisers, fellow GDEs? Naming everybody would fill this entire story. Thanks, everyone; you are all the real MVPs. We will see at the top! 💪</p><h3>Parting Words</h3><p>Thank you for reading this far.</p><p>Among the many things that inspire me, <strong>ability</strong> stands out.</p><blockquote><em>“If you can think it, you can do it.” <br>“Why keep it for later if you can do it now?”<br>“Believe you can and you’re halfway there.”<br>“Start where you are. Use what you have. Do what you can.”<br>“You can do anything you want if you stick to it long enough.”<br>…</em></blockquote><p>Looking into the future, I intend to help create more community impact in Cloud and AI as I’ve become so proficient in them as well. I’m also preparing to take the Google Cloud <a href="https://cloud.google.com/learn/certification/data-engineer">Professional Data Engineer</a> Exam.</p><p>As for the next technology I will pick up, I am strongly considering hardware: ARM, Arduino, microcontrollers, Raspberry Pi, etc. This time, the inspiration is thanks to <a href="https://x.com/robert_thas">Robert</a>.</p><p>I haven’t fully decided yet if I will write code for those systems. Well, let’s see how it goes.</p><p>Cheers to continuous growth and development.<br>Cheers to more knowledge, expertise, and adventure.<br>Cheers to your success — yes, the success of you reading this 😇.</p><p>See ya,<br>Bye 👋.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6157b05ed803" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-developer-experts/road-to-mastery-being-a-gde-6157b05ed803">Road to Mastery — Being a Google Developer Expert</a> was originally published in <a href="https://medium.com/google-developer-experts">Google Developer Experts</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to structure any  booking/reservation system with Firebase]]></title>
            <link>https://blog.keepdeploying.com/how-to-structure-any-booking-reservation-system-with-firebase-e7f1774e848e?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/e7f1774e848e</guid>
            <category><![CDATA[design]]></category>
            <category><![CDATA[planning]]></category>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[system]]></category>
            <category><![CDATA[firebase]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Fri, 15 Sep 2023 11:17:48 GMT</pubDate>
            <atom:updated>2026-01-25T13:28:29.871Z</atom:updated>
            <content:encoded><![CDATA[<h3>How to structure any booking/reservation system with Firebase</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IL8AxRh2AfayDspkVUCzhA.jpeg" /><figcaption><a href="https://www.freepik.com/free-vector/landing-page-woman-making-appointments_8685797.htm#query=online%20reservation&amp;position=35&amp;from_view=keyword&amp;track=ais">Image by Freepik</a></figcaption></figure><p>Many businesses have a booking system. For example:</p><ul><li>Booking a consultant/therapist,</li><li>Reserving a hotel room,</li><li>Reserving a hall/an auditorium for an event,</li><li>etc.</li></ul><p>All booking systems have one thing in common: leaseable services (or resources). Customers can use these resources for a set period of time.</p><p>Booking is planning the availability of such resources. Booking/Reservation systems are important because they help with avoiding clashes among customers. The resource(s) can only be available for one customer at a time.</p><p>This article tells how to plan any booking system with Firebase. If you are not using Firebase, you will still find some concepts in this article useful. Especially the idea of a template timetable.</p><h4>Table of Contents</h4><p>∘ <a href="#055e">How does booking work?</a><br> ∘ <a href="#1b7f">Constraints of a Unit Time per Slot</a><br> ∘ <a href="#a9ef">Various degrees of booking complexity</a><br> ∘ <a href="#38a9">How to structure an availability/template timetable</a><br> ∘ <a href="#8835">A Firebase briefing</a><br> ∘ <a href="#d36b">Overview of a Booking System with Firebase</a><br> ∘ <a href="#66da">How to structure the reservation documents in Firestore</a><br> ∘ <a href="#9a56">How to use a calendar in the UI of a booking/reservation system</a><br> ∘ <a href="#9cbd">How to query for available slots in your UI with Firestore</a><br> ∘ <a href="#6aa3">How to query for available slots in your UI with Cloud Functions</a><br> ∘ <a href="#538d">How to make a reservation with Cloud Functions</a><br> ∘ <a href="#9ac0">Miscellaneous Concepts</a><br> ∘ <a href="#2499">Thank You!</a></p><h4>How does booking work?</h4><p>Before tech became standard, bookings were manual. A secretary had to keep some calendar. To make a reservation, the secretary would check for available slots.</p><p>A prospective customer will agree with the secretary on a particular time slot. This slot becomes the booking or the reservation.</p><p>Now many apps have gone digital. We do reservations and have retained the manual booking algorithm.</p><p>The process is straight to the point:</p><ul><li>Check for available slots.</li><li>Display them (in a calendar).</li><li>On a customer’s request, reserve a chosen slot.</li></ul><p>A customer can’t make a reservation on an already-taken slot.</p><p>From the above, we can draft out the following 3 functionalities of booking systems:</p><ol><li>Having time slots for when the resource is available.</li><li>Booking/Reserving the resource based on a customer’s choice.</li><li>Ensuring that no other customer books/reserves a taken time slot.</li></ol><p>These 3 points will be helpful to us as we explore how to structure any booking system.</p><h4>Constraint of a Constant Time per Unit Slot</h4><p>Most booking systems (if not all) have a constraint on the lease of their resources. The constraint is, per lease, the reservable time slot is an integer multiple of a constant.</p><p>That is, the entire system knows that there is a base duration for how long a customer is using a resource. It could be an hour, 2 hours, a day, etc. This base time is the time per unit slot for that resource.</p><p>For example, you can book a hotel room for a day or for integer multiples of a day. You can’t book a hotel room for half a day or for one and a quarter days at once. Only a day or two days at a stretch or 3 days, etc.</p><p>In the context of the hotel room, the time per unit slot is 1 day.</p><p>Constraining this value to a constant is important for most booking/reservation systems. All code in the system uses this constant to determine how long the resource(s) will be in use.</p><p>This constraint is the basis of booking. If it doesn’t exist, the booking process becomes very difficult to manage.</p><p>Everything is possible. Such systems exist where there is no such constraint of a constant.</p><p>If that is the system you are building, then the major part of this article might not help you. If you fall in this category, please share your use case. Let’s see if we can help plan your system.</p><h4>Various degrees of booking complexity</h4><p>Let’s take a hotel for example. When you reserve a room for a specific day, that day will no longer be available for any other reservation.</p><p>Managing hotel bookings (per room) is easy. This is because hotel rooms usually have daily slots. So we mark a day on a particular room as taken and that’s it.</p><p>Things get different when for a given resource, the time slots are a little dynamic.</p><p>Consider a therapist’s schedule. Customers can book hourly slots with the therapist.</p><p>At first, it seems easy to build a reservation system for this therapist. I mean, the therapist could be available from 9 to 5, every Monday to Friday.</p><p>Things change when there are working hours when this therapist is not available. Or when there is a known circumstance preventing a regular flow of availability. Or when there is the need for custom availability at custom times.</p><p>As you see, sometimes, in our products, the resources we are reserving don’t have easy slots. The booking process becomes more complex.</p><p>For the sake of clarity, let’s categorize these degrees of complexity. We will use these categories in consequent parts of this article.</p><p><strong>Level A</strong>. When we have to allocate a resource per day or per hour or per multiples of those.</p><p><strong>Level B</strong>. When we can’t do Level A because there are exceptions in some hours or some days. Our system needs to have an “availability timetable” for our resource(s). The system will use this timetable to make reservations.</p><p><strong>Level C</strong>. When we have many resources. And each resource has its own different availability timetable. And their timetables are different from each other.</p><p>Irrespective of your system’s complexity level, making reservations is the same process.</p><h4>How to structure an availability/template timetable</h4><p>This concept is more of an intuitive one. You don’t need it if you are in Level A or if your “reservable” has regular slots (like hotel rooms’ daily slots).</p><p>But, if it doesn’t, the following are tips on how you can plan the template timetable for the resource. The idea is to craft a data structure that tells the codebase when the resource is available.</p><p>I found it easier to use numbers for everything in the timetable. So when recording days of the week, 1 will be for Monday, 2 for Tuesday, etc.</p><p>Also, for hours in a day, using the 24-hour style is better than the 12-hour style. It is harder to code for AM/PM when 13 can mean 1 PM, 14 can mean 2 PM, etc.</p><p>Furthermore, these timetables should only record only the start times of available slots. This is because the time duration is the same for every lease (constraint of a unit time per slot). And by extension, there is no need for hardcoding end times in the timetable.</p><p>Besides, keeping track of both start and end times in the template has a disadvantage. Updating the timetable will be more demanding.</p><p>Let’s look at 7 example use cases with varying complexity.</p><pre><br>// 1. Varying start hours in a day, for every day of the resource&#39;s use.<br>[8, 10, 11, 13.5, 14, 15]<br>//    The above means that for every day, we can have 6 slots in a day<br>//    which each start from 8 AM, 10 AM, 11 AM, 1:30 PM, 2 PM, and 3 PM.<br><br>// 2. Varying days in a week, for every day of the resource&#39;s use.<br>[1, 3, 5, 6]<br>//    The above means that for every week, we can have 4 daily slots<br>//    which are Mondays, Wednesdays, Fridays, and Saturdays.<br><br>// 3. Same start hours on varying days of the week.<br>{<br>  &quot;hours&quot;: [8, 10, 14, 16],<br>  &quot;days&quot;: [1, 2, 4, 5]<br>}<br>//    The above means that the resource is available on Mondays, Tuesdays,<br>//    Thursdays, and Fridays (&quot;days&quot;). And on those days, <br>//    slots are from 8 AM, 10 AM, 2 PM, and 4 PM (&quot;hours&quot;).<br><br>// 4. Varyings dates in a month, for daily use of the resource.<br>[2, 3, 5, 6, 8, 10, 12, 14, 17, 18, 19, 21, 25]<br><br>// 5. Varying dates in a month, for varying days of the week.<br>{<br>   &quot;days&quot;: [5, 6, 7],<br>   &quot;dates&quot;: [2, 3, 4, 5, 8, 9, 11, 12, 15, 16, 22, 25, 27, 28]<br>}<br>//    The above means that the resource is only available on month dates,<br>//    that fall on weekends. <br><br>// 6. Varying start hours in varying days of the week.<br>{<br>  1: [8, 10, 11, 13.5, 14, 15],<br>  2: [9, 12, 14, 15, 16, 17],<br>  4: [8, 9, 10, 12, 14.5, 16],<br>  5: [11, 12, 13.75]<br>}<br>//   The above means that: <br>//   * Monday has a similar schedule to use case 1 above. <br>//   * Tuesday&#39;s slots are entirely different. <br>//   * The resource is not available for reservations on Wednesdays,<br>//     Saturdays, and Sundays (their keys: 3, 6, 7) are not included. <br>//   * Friday has only 3 slots unlike the others.<br>//   * And on Friday, the last slots starts from 1:45 PM<br><br>// 7. Varying start hours, for varying dates of the month.<br>{<br>  2: [8, 10, 12, 14, 16],<br>  3: [7, 9, 11, 13, 15],<br>  4: [9, 10, 11, 12, 16],<br>  // ... other dates and times<br>  15: [7, 8, 9],<br>  // ... other dates and times<br>  28: [9, 10, 11, 12, 13, 14, 15],<br>}<br>//    The above means that: <br>//    * The 2nd of every month has 5 slots (like the 3rds and 4ths). <br>//    * Every 15th has only 3 slots.<br>//    * 28ths have 7.<br>//    * (You can fit in for other dates).</pre><p>As the snippet shows, the timetable is the smallest data structure that tells the resources’ hiring frequency.</p><p>The above examples assume easy times per unit slot (hourly or daily). The same concept applies if you use a different time per unit slot. E.g. 15 minutes, an hour 30 minutes, 3 hours, 6 hours, 2 days, etc.</p><p>Some examples above are unrealistic. Look at the 7th example. I’m not sure there exist real-life businesses that lease out their services following it. Well, as developers, we should account for every possibility.</p><p>Remember that you don’t always need a template timetable. In fact, you <strong>should not </strong>craft one if you don’t need it.</p><p>Only create one if your system doesn’t allow a regular flow of use (every hour, every day, every 2 days, etc.).</p><p>Drafting a timetable is creating a data structure that tells the resources’ availability. I trust your intuition for doing it well as there is no one-fits-all method for this.</p><p>Having created a template timetable, where do you store it? Where do you place this data structure that every other part of the system should use?</p><p>The answer is it depends. You either hardcode the availability timetable or you save it in a database.</p><p>If your system is leasing one resource, save the timetable in the code. Also, if you are leasing a fixed number of resources <strong>that don’t easily change, </strong>then save the timetable in the code too.</p><p>When saving it in code, export it as a global constant. Every part of the code that needs to make reservations or display them will look up the timetable.</p><p>If the resources (or their details) often change, it means you are storing them in a database. In this scenario, the timetable should be in the database too. It should be there because it might also be changing.</p><p>These resources can have different template timetables. That is, your system is in Level C. In that case, store each timetable together with each resource. When leasing a resource, check and reserve while following its timetable.</p><p>Level C is the most complex. A practical example of a Level C system is a session/tutorial management system. A system that connects mentors/tutors and students.</p><p>In this system, tutors come and define when they are available. In turn, students come and reserve slots in the calendars of these tutors.</p><p>Users can specify their own availability timetables. We have no other choice but to save each timetable together with the resource (the tutor’s data in this case).</p><p>The time per unit slot of all resources in this very complex booking system is still constant.</p><p>It is also possible that resources have different times per unit slot in your system. In that case, for each resource, also save it’s time per unit slot in the database.</p><p>That will be a lot of complexity and I’m not sure of the app’s UI engineering to fit all possible use cases.</p><p>That has been a lot on the theory and engineering of reservation systems. Let’s switch over to Firebase and the implementations of booking.</p><h4>A Firebase briefing</h4><p>Firebase has <a href="https://firebase.google.com/products/auth">Authentication</a> to sign in users. Firebase Authentication allows email and password or federated identity sign-in. We can use Firebase Authentication to know which user is making a reservation.</p><p>Firebase also has <a href="https://firebase.google.com/products/firestore">Cloud Firestore</a>, a NoSQL, and a client-accessible database. By being NoSQL, you can store an entity’s data (a reservation) as a document. In turn, documents are grouped into collections (<em>reservations </em>collection).</p><p>With Firestore, apps can access documents and collections through the Firebase SDK. As such, you can query available slots and render them to the user directly from your UI.</p><p>Firebase also has <a href="https://firebase.google.com/products/functions">Cloud Functions</a>. Cloud Functions are invocable code that securely runs inside Google’s data centers. They are serverless endpoints that you invoke only when you need them.</p><p>A booking system can use Cloud Functions to make a reservation. You can liken the Cloud Function code to the manual secretary. It will check if the proposed slot is free and reserve it for the customer.</p><p>That is why we are using Cloud Functions for reservations. To ensure that nobody makes a booking on a slot that is already reserved.</p><p>Also, we want to reserve a “reservable” time slot. If the resource uses an availability timetable, we can check if the reserved time is part of it.</p><h4>Overview of a Booking System with Firebase</h4><p>First, define the resource(s) that customers can reserve.</p><p>State it/them out and have it (them) in mind. Event Centre, Hotel Rooms, Therapist Times, etc.</p><p>Next, figure out how often these resources are “reservable”. If your system falls in <strong>Level A</strong> (simplest) category, then you have less work to do.</p><p>You will build the system around this regular frequency of resources’ availability.</p><p>If your system falls in <strong>Level B or C </strong>(more complex categories), craft the template timetable(s). Store them in code or in Firestore. Other parts of the system will look up the timetable when reserving resources.</p><p>Once you’ve defined this time-slots abstraction, the next thing is to build your UI. In most cases, you will have a calendar that displays taken and available slots.</p><p>In most cases, users have to sign in to make a reservation (so that we know who is booking a resource).</p><p>Once in your app, users can view booked slots in the calendar. You can fetch reservations from Firestore (you would have stored them there).</p><p>Finally, to make a reservation, the system should make calls to Cloud Functions. You will have to create the functions yourself. In the functions’ code, you will store the reservation in Firestore.</p><p>To recap:</p><ol><li>Define the reservable resource(s).</li><li>Determine (from your product spec) the complexity level of the resource(s).</li><li>If in Level A (simple daily/hourly availability), use that logic when building.</li><li>If not in Level A, define the availability/template timetable. Other parts of the system will rely on this.</li><li>Build your UI and set up Firebase to retrieve slots based on the availability timetable of 3 or 4 above.</li><li>Use Cloud Functions to allow users to make reservations.</li></ol><p>From now on, we will look at some of the above flow in detail.</p><p>Note that we are using JavaScript to write Firebase-related code in this article. If it doesn’t suit you, re-implement the algorithms in your choice programming language.</p><h4>How to structure the reservation documents in Firestore</h4><p>There are some reservation systems that only one user accesses. The system is an aid to a real-life clerk in some office.</p><p>In such a system, this clerk or secretary creates all the reservations. You can see such simplicity may be in a hospital’s reception or the work of a personal assistant.</p><p>In this case, a reservation document will have the following:</p><ul><li>the start time of the reservation,</li><li>the end time (or the duration),</li><li>the customer’s data,</li><li>and any other necessary info.</li></ul><p>It is possible that there are many receptionists or clerks. In that case, also store the userId (or uid) of the person making the reservation.</p><p>If customers reserve slots themselves, store their <em>uid</em>s in the reservation document.</p><p>Storing their uid is necessary to keep track of who did what. Storing is also useful when fetching all the reservations made by a particular admin or user.</p><p>Below is a TypeScript interface. It defines a proposed type signature for your reservation document object.</p><pre>// reservation.ts model file<br>// (possible structure of a reservation document)<br><br>interface Reservation {<br>  &quot;uid&quot;: string, // if need be<br>  &quot;startTime&quot;: Timestamp,<br>  &quot;endTime&quot;: Timestamp, <br>  &quot;customerInfo&quot;: { // not necessary if you can obtain this from the <br>    &quot;name&quot;: string,  // customers database/info or from somewhere<br>    &quot;email&quot;: string, // else in the database. can also be useful for future<br>    &quot;phone&quot;: string, // reference if in case the user deletes their account<br>  },<br>  &quot;additionalInfo&quot;: { // for demo purposes, please use what is <br>    &quot;reasonForVisit&quot;: string, // relevant to your system.<br>    &quot;hasPaid&quot;: boolean<br>  },<br>  // any other neccessary data according to your system<br>}</pre><p>The above code can serve as a guide and should not be an absolute reference. Only declare properties that are relevant to your system.</p><p>The type signature of startTime and endTime is Timestamp. Timestamp is a valid data type in Cloud Firestore. It is the best option to represent a specific date and time in our database.</p><p>startTime and endTime are properties that all reservation documents should have. I mean, we are booking this resource for a specific time right?</p><p>Another variation is to store startTime and duration instead of startTime and endTime. Both do the same thing. If you are storing duration instead, choose whether to store it in <em>seconds </em>or in <em>minutes.</em></p><h4>How to use a calendar in the UI of a booking/reservation system</h4><p>Most if not all booking/reservation systems have a calendar in their UI. The calendar makes it easy for the user to get to the date they want the resource and then make the booking there.</p><p>Another advantage of the calendar is that it prevents the user from making a reservation in the past. The user could view past reservations through the calendar but not edit them. But can’t book anywhere on a date older now.</p><p>In apps and websites, users can view calendars in various modes: daily, weekly, and monthly. Calendars can also have custom combos like 3-day views, 2-week views, etc.</p><p>If your system is in Level A, the calendar’s minimum view mode will be a group higher than the Unit Time per slot. That is, if your resources have daily availability, you want to display the calendar in weeks or months.</p><p>If your system has a template timetable (is in Level B or C), the calendar’s view mode should be a time range that matches the range of the template timetable. This is specific to each template timetable.</p><p>When a user accesses the calendar page/screen, the UI code should fetch reservations. Using them, the code should disable calendar slots where bookings exist already.</p><p>How you implement this depends on how your system works. Let’s say your calendar is in a monthly-view display. And that your reservable is a daily resource. Disable the calendar’s days where there are already bookings.</p><p>It is possible that the calendar is in a weekly view display. And there is a weekly availability timetable for the reservable. First, fetch the timetable in the UI. Then disable the calendar spots that are not included in the timetable.</p><p>After that, fetch the reservations within the time frame of the displayed week. Then further disable the calendar for those days/hours. The calendar now has only empty slots to book from which the user can book.</p><p>If there are no empty slots, the user has to move the calendar to the next week. Your UI code should redo the above fetchings on the change of the “displayed time” range.</p><p>This pattern of disabling booked slots can work in any calendar view. The difference is that in daily view, you might be disabling hours instead of days.</p><p>This is an algorithm you can use whether you are coding for mobile or on the web. I trust you are a developer (or you are hiring one/some) and how to implement it in the target platform wouldn’t be an issue.</p><p>Besides, it will be hard to show a sample code for this as it will deviate from the scope of this article.</p><h4>How to query for available slots in your UI with Firestore</h4><p>All calendar views (day, week, or month) have the least date and the highest date in view. Or rather, there is always the oldest date and the latest date in the context of every calendar view.</p><p>To query the database for reservations within the calendar view, use these two dates.</p><p>For example, let’s say our UI is in weekly view, and the first day is Sunday, 16th July, and the last is Saturday 23rd July. In this context, 00:00 on the 16th is our oldest timestamp while 23:59 on the 23rd is our latest.</p><p>Use these two timestamps when querying the <em>reservations </em>collection in Firestore. Following is a snippet of what the query should look like in JavaScript.</p><p>oldestDateFromCalendarView and latestDateFromCalendarView are from your UI code.</p><pre>import { <br>  collection, <br>  getDocs, <br>  query, <br>  where, <br>  Timestamp<br>} from &#39;firebase/firestore&#39;;<br>const reservationsRef = collection(db, &#39;reservations&#39;);<br><br>const startTime = Timestamp.fromDate(oldestDateFromCalendarView);<br>const endTime = Timestamp.fromDate(latestDateFromCalendarView);<br>const q = query(<br>  reservationsRef, <br>  where(&#39;startTime&#39;, &#39;&gt;=&#39;, startTime), <br>  where(&#39;endTime&#39;, &#39;&lt;=&#39;, endTime), <br>);<br>const querySnapshot = await getDocs(q);<br>querySnapshot.forEach((doc) =&gt; {<br>  // update your UI by blocking calendar spots that match the dates here<br>  console.log(doc.id, &quot; =&gt; &quot;, doc.data());<br>});</pre><p>The above is okay if only admin(s) create reservations.</p><p>It is not good for systems where customers make bookings themselves. We don’t want a customer to see the details of reservations of other customers. That’s why we need to create a cloud function.</p><p>Also, Firebase has <a href="https://firebase.google.com/docs/firestore/security/get-started">Firestore Rules</a> to secure data access from client apps. Use them to ensure that only reservations’ creators can read or update reservations.</p><h4>How to query for available slots in your UI with Cloud Functions</h4><p>Let’s create a fetchReservations Cloud Function. It will query Firestore for reservations. It will return only the startTime and endTime properties of every reservation.</p><p>You shouldn’t use cloud functions if there is no sensitive data to hide from other customers. Or if secretaries or admins are the people making reservations. In such a case, querying Firestore from the frontend/UI should serve.</p><p>The Cloud Function we will create should be an <a href="https://firebase.google.com/docs/functions/callable"><em>onCall</em></a> Function. Following is what fetchReservations function should look like.</p><pre>const functions = require(&#39;firebase-functions&#39;);<br>const { initializeApp } = require(&#39;firebase-admin/app&#39;);<br>const { getFirestore, Timestamp } = require(&#39;firebase-admin/firestore&#39;);<br><br>initializeApp();<br>const db = getFirestore();<br>const reservationsRef = db.collection(&#39;reservations&#39;);<br><br>exports.fetchReservations = functions<br>  .runWith({ enforceAppCheck: true }) // Optional, but recommended.<br>  .https.onCall(async (data, context) =&gt; {<br>    // Optional, but recommended<br>    if (!context.app) {<br>      throw new functions.https.HttpsError(<br>        &#39;permission-denied&#39;,<br>        &#39;The function must be called from an App Check verified app.&#39;<br>      );<br>    }<br>    // Necessary to obtain the details of the signed in user.<br>    // Optional if any body can view reservations<br>    // Optional if the only admins/secretaries make reservations.<br>    if (!context.auth) {<br>      throw new functions.https.HttpsError(<br>        &#39;unauthenticated&#39;,<br>        &#39;The function must be called while authenticated.&#39;<br>      );<br>    }<br>    const { endTime, startTime } = data;<br>    if (!(endTime instanceof Timestamp)) {<br>      throw new functions.https.HttpsError(<br>        &#39;invalid-argument&#39;,<br>        &#39;&quot;endTime&quot; in data must be a valid Firestore Timestamp.&#39;<br>      );<br>    }<br>    if (!(startTime instanceof Timestamp)) {<br>      throw new functions.https.HttpsError(<br>        &#39;invalid-argument&#39;,<br>        &#39;&quot;startTime&quot; in data must be a valid Firestore Timestamp.&#39;<br>      );<br>    }<br>    try {<br>      const querySnapshot = await reservationsRef<br>        .where(&#39;startTime&#39;, &#39;&gt;=&#39;, startTime)<br>        .where(&#39;endTime&#39;, &#39;&lt;=&#39;, endTime)<br>        .get();<br>      return querySnapshot.docs.map((doc) =&gt; {<br>        // TODO: also extract any other public reservation data<br>        // from the following (if need be)<br>        const { endTime, startTime, uid } = doc.data();<br><br>        let isMine = false;<br>        if (uid == context?.auth?.uid) isMine = true;<br> <br>        // TODO: also return any other extracted data alongside <br>        return { endTime, startTime, isMine, id: doc.id };<br>      });<br>    } catch (e) {<br>      console.error(e);<br>      throw new functions.https.HttpsError(&#39;internal&#39;, e);<br>    }<br>  });</pre><p>The code of this cloud function enforces <a href="https://firebase.google.com/products/app-check">AppCheck</a>. AppCheck helps to make sure that calls to the cloud function come from our apps. The function also ensures that only signed-in users can view reservations.</p><p>The code then proceeds to verify that the caller provided valid Timestamps. As expected, the function returns an array of reservation objects. These reservations each have startTime and endTimes.</p><p>The extra isMine property tells the UI to allow edits or deletes by the user. (Well, that’s if you permit such in your product).</p><p>Also, we are returning the database id of each reservation. This is to know which reservation the user would like to edit.</p><p>This Cloud Function throws errors where they occur. It is the work of the UI code to appropriately catch and handle these errors.</p><p><a href="https://firebase.google.com/docs/reference/node/firebase.functions#functionserrorcode">These error types are from Firebase Cloud Functions and they respond with the appropriate HTTP status code.</a></p><p>The try block is to ensure that our Cloud Function will still respond to the UI if something unexpected takes place. Printing the catch block’s error to the console will show in the <a href="https://console.firebase.google.com/project/_/functions">Firebase Console Logs for Cloud Functions</a>.</p><p>On the frontend/UI code, you will call this fetchReservation function instead of fetching the reservations from Firestore directly. This will help mitigate the issue of privacy.</p><p>The frontend/UI code that calls the fetchReservation cloud function should look like the following:</p><pre>import { Timestamp } from &#39;firebase/firestore&#39;;<br>import { getFunctions, httpsCallable } from &#39;firebase/functions&#39;;<br><br>const functions = getFunctions();<br>const fetchReservations = httpsCallable(functions, &#39;fetchReservations&#39;);<br><br>const startTime = Timestamp.fromDate(oldestDateFromCalendarView);<br>const endTime = Timestamp.fromDate(latestDateFromCalendarView);<br>let reservations;<br><br>try {<br>  const result = await fetchReservations({startTime, endTime});<br>  reservations = result.data;<br>} catch (e) {<br>  // TODO: <br>  // instead of alerting the browser, show a custom toast<br>  alert(e);<br>}<br><br>if (reservations) {<br>  // TODO: <br>  // reservations where fetched successfully, disable calendar spots<br>  // that match the reservations&#39; times<br>}</pre><p>The above calls the cloud function and returns the list to the UI code. The code should now use it and block appropriate calendar spots.</p><p>So far, we have kept track of reservations and have shown them to the users. Now, let’s look at the last part of the reservations flow.</p><h4>How to make a reservation with Cloud Functions</h4><p>The Cloud Functions code for making reservations should do 2 core things:</p><ol><li>Ensure there is no booked slot on the requested booking time.</li><li>Make the booking by creating the reservation document in Firestore.</li></ol><p>Following is what themakeReservation function should look like.</p><pre>const functions = require(&#39;firebase-functions&#39;);<br>const { initializeApp } = require(&#39;firebase-admin/app&#39;);<br>const { getFirestore, Timestamp } = require(&#39;firebase-admin/firestore&#39;);<br><br>initializeApp();<br>const db = getFirestore();<br>const reservationsRef = db.collection(&#39;reservations&#39;);<br><br>exports.makeReservation = functions<br>  .runWith({ enforceAppCheck: true }) // Optional, but recommended.<br>  .https.onCall(async (data, context) =&gt; {<br>    // Optional, but recommended<br>    if (!context.app) {<br>      throw new functions.https.HttpsError(<br>        &#39;permission-denied&#39;,<br>        &#39;The function must be called from an App Check verified app.&#39;<br>      );<br>    }<br>    // Necessary to obtain the details of the signed in user.<br>    // Optional if only admins/secretaries can make reservations.<br>    if (!context.auth) {<br>      throw new functions.https.HttpsError(<br>        &#39;unauthenticated&#39;,<br>        &#39;The function must be called while authenticated.&#39;<br>      );<br>    }<br>    const { endTime, startTime, /* any other form data */ } = data;<br>    if (!(endTime instanceof Timestamp)) {<br>      throw new functions.https.HttpsError(<br>        &#39;invalid-argument&#39;,<br>        &#39;&quot;endTime&quot; in data must be a valid Firestore Timestamp.&#39;<br>      );<br>    }<br>    if (!(startTime instanceof Timestamp)) {<br>      throw new functions.https.HttpsError(<br>        &#39;invalid-argument&#39;,<br>        &#39;&quot;startTime&quot; in data must be a valid Firestore Timestamp.&#39;<br>      );<br>    }<br>    <br>    // TODO:<br>    // Also validate if the duration between startTime and endTime<br>    // is a mulitple of the Time per Unit Slot for the resource.<br>    // This is also where to lookup and use the availability/template<br>    // timetable if that applies to your system.<br><br>    // TODO: <br>    // Validate any other form data at this point.<br>    // Form data here means other data needed to make <br>    // a reservation according to your system.<br><br>    try {<br>      // fetch booked slots for the requested time.<br>      const querySnapshot = await reservationsRef<br>        .where(&#39;startTime&#39;, &#39;&gt;=&#39;, startTime)<br>        .where(&#39;endTime&#39;, &#39;&lt;=&#39;, endTime)<br>        .get();<br>      <br>      // ensure there is no booked slot on the requested booking time.<br>      if (!querySnapshot.empty) {<br>        throw new functions.https.HttpsError(<br>          &#39;invalid-argument&#39;,<br>          &#39;There are already bookings for the sent time slot.&#39;<br>        );<br>      }<br>      <br>      const reservationData = { <br>        endTime, <br>        startTime, <br>        uid: context.auth.uid, // if necessary<br>        /* TODO: add other validated form data */ <br>      };<br>      const newReservRef = await reservationsRef.add(reservationData);<br><br>      return { id: newReservRef.id, isMine: true, ...newReservRef.data() };<br>    } catch (e) {<br>      console.error(e);<br>      throw new functions.https.HttpsError(&#39;internal&#39;, e);<br>    }<br>  });</pre><p>The cloud function returns the created reservation and this time with its id. Use this returned object in the UI to disable the just-booked calendar spot.</p><p>The above code includes some comments. The comments tell where you should get form data, validate it, and add it to the reservation document.</p><p>Form data here is other details that the user provided when booking. These are details relevant to your system. Maybe if the user selected massage services while booking the hotel room. Or the patient’s history/story for the consultant/therapist. Or whatever.</p><p>The UI could get that info from some form that pops up after the user has selected a calendar spot. Send that info alongside the time slot to the makeReservation cloud function to book that user.</p><p>In the case where your system has multiple different resources, this is where to fit more details about the resource choice into the reservation document.</p><p>As for how to call this function from the frontend/UI code, the following JavaScript snippet should help.</p><p>startCalendarTime and endCalendarTime are from the time slot that the user selected.</p><pre>import { Timestamp } from &#39;firebase/firestore&#39;;<br>import { getFunctions, httpsCallable } from &#39;firebase/functions&#39;;<br><br>const functions = getFunctions();<br>const makeReservation = httpsCallable(functions, &#39;makeReservation&#39;);<br><br>const startTime = Timestamp.fromDate(startCalendarTime);<br>const endTime = Timestamp.fromDate(stopCalendarTime);<br>// TODO: obtain other form data from the UI<br>const reservationData = {startTime, endTime, /* other form data */ };<br>let madeReservation;<br><br>try {<br>  const result = await makeReservation(reservationData);<br>  madeReservation = result.data;<br>} catch (e) {<br>  // TODO: instead of alerting the browser, show a custom toast<br>  alert(e);<br>}<br><br>if (madeReservation) {<br>  // TODO: <br>  // reservation was successfully created, update the UI or navigate out<br>}</pre><h4>Miscellaneous Concepts</h4><p>Let’s look at 5 extra ideas that could be part of any booking system.</p><p><strong>1. Updating/Deleting Reservations</strong></p><p>Users should be able to update/delete their reservations. However, depending on your system, you might not allow this.</p><p>You could allow updating to a certain degree. Maybe updating appointment details only. Or allowing updates of time if the update is coming 3 days before the schedule.</p><p>Deletes are sensitive. For example, let’s say a hotel’s policy includes non-refundable payments. This hotel’s booking system should not allow deletes.</p><p>If you have gotten here, the code for updating or deleting wouldn’t be an issue for you.</p><p><strong>2. Listening to Real-Time Changes</strong></p><p>Firestore has real-time abilities. It is capable of updating your frontend code with data as that data changes elsewhere.</p><p>Booking is sensitive. We want to always be sure that a user doesn’t book on an already booked slot.</p><p>Using a listener, we can update the calendar UI with booking slots while the user is on the page. It makes the UI reactive and helps ensure the one customer per slot policy.</p><p>When fetching reservations from Firestore, fetch them through the query listener. This way, let’s say another user creates a reservation elsewhere. The calendar of the current user will auto-update with a block on that reservation.</p><p>Given the following query:</p><pre>const q = query(<br>  reservationsRef, <br>  where(&#39;startTime&#39;, &#39;&gt;=&#39;, startTime), <br>  where(&#39;endTime&#39;, &#39;&lt;=&#39;, endTime), <br>);</pre><p>Instead of a one-time fetch of the query:</p><pre>const querySnapshot = await getDocs(q);<br>querySnapshot.forEach((doc) =&gt; {<br>  // update your UI by blocking calendar spots that match the dates here<br>  console.log(doc.id, &quot; =&gt; &quot;, doc.data());<br>});</pre><p>Listen to changes and Firebase will handle the rest:</p><pre>onSnapshot(q, (querySnapshot) =&gt; {<br>  querySnapshot.forEach((doc) =&gt; {<br>    // update your UI by blocking calendar spots that match the dates here<br>    console.log(doc.id, &quot; =&gt; &quot;, doc.data());<br>  });<br>});</pre><p><strong>3. Temporary Unavailabilities</strong></p><p>How do we handle cases when the leasable resource would be unavailable for a normal time slot? How does the system exempt that particular unavailability circumstance?</p><p>For example, due to scheduled maintenance, a hotel can withhold some rooms on a particular day. A therapist can be on leave. A leasable hall can be on renovation.</p><p>An easy answer could be to create a reservation for that time slot and that solves the problem. Another way is to let the system be aware of these kinds of circumstances. And also check for them while checking reservations from the UI.</p><p>You shouldn’t worry about this when you start building. They rarely happen. Also, you are trying to build a <a href="https://en.wikipedia.org/wiki/Minimum_viable_product">Minimum Viable Product</a>. But if you come across the need to handle this scenario, you will scale through.</p><p><strong>4. The need for storing both start and end times in reservations</strong></p><p>At first sight, it may not make sense to store both startTime and endTime/duration in a reservation document. After all, just the startTime is necessary.</p><p>This is especially true, given the constraint of a unit time per slot. Every code knows the duration of a reservation and can calculate the endTime. This thought pattern is okay. But it doesn’t scale. We need to build robust systems.</p><p>Saving either the duration or the endTime in every reservation makes the data complete and less ambiguous. It makes easy access to or display of correct and complete data about a reservation anywhere.</p><p>It helps any new member joining the team to understand what’s happening (by observing patterns).</p><p>It also helps to maintain clean records of the system even if the system changes the unit time per slot for those resources.</p><p>Because at a future time when the unit time has been increased or decreased, past reservations should still show or use the previous “unit time per slot”.</p><p>For a concrete example, if a therapist changes session times to 45 minutes instead of the previous 1 hour, you will need to update the code and maybe the template timetable. You don’t have to worry about older reservations since they will still be displaying the previously saved 1 hour duration or endTime target.</p><p><strong>5. Chaining Multiple Time Slots</strong></p><p>This is another reason for storing the duration/endTime in the reservation document.</p><p>By chaining multiple time slots, we mean when a customer is reserving multiples of the unit time at once.</p><p>For example, a customer is using a hotel room for 3 days at a go. A patient wants more time with a consultant so they go for 2 sessions at once. Or an event spans 3 days and uses the same hall as the venue.</p><p>Handling this is intuitive in the booking system. The calendar should allow selecting multiple spots at once and that’s it.</p><h4>Thank you!</h4><p>Thank you for being part of this journey. It has been a long ride. Firebase makes life easy. Building a booking/reservation system should not be hard either.</p><p>If you plan booking/reservation systems well, you will properly execute them. And that is irrespective of the complexity. And as we’ve seen you can build them on Firebase.</p><p>We covered many use cases of booking. I hope we’ve discussed your use case (or parts of it at least).</p><p>Toast to the many systems you build.</p><p>Cheers!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e7f1774e848e" width="1" height="1" alt=""><hr><p><a href="https://blog.keepdeploying.com/how-to-structure-any-booking-reservation-system-with-firebase-e7f1774e848e">How to structure any  booking/reservation system with Firebase</a> was originally published in <a href="https://blog.keepdeploying.com/">keepdeploying</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to create a CORS proxy server with Firebase Functions]]></title>
            <link>https://blog.keepdeploying.com/how-to-create-a-cors-proxy-server-with-firebase-functions-f4be840026b5?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/f4be840026b5</guid>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[cors]]></category>
            <category><![CDATA[firebase-cloud-functions]]></category>
            <category><![CDATA[firebase]]></category>
            <category><![CDATA[https]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Fri, 27 Jan 2023 07:13:00 GMT</pubDate>
            <atom:updated>2023-11-24T00:09:10.292Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lBrceA_yfvmiUGvUvOLO8g.png" /></figure><h3>About CORS</h3><p>CORS (Cross-Origin Resource Sharing) is a policy that helps with user security. It is a mechanism by which the browser permits or denies access to data on a website (domain name) from another.</p><p>Let’s say you are browsing on example1.com. You want to download a picture hosted at example2.com. By default, the browser will not allow you to do so. The reason is to ensure that you don’t obtain malicious data from example2.com.</p><p>But then what about <a href="https://en.wikipedia.org/wiki/Content_delivery_network">CDN</a>s and hosting static assets on different domains? Why aren’t they blocked by CORS?</p><p>Well, all backend codes have the ability to add specific HTTP headers to an HTTP response. When they add such headers, they tell the browser: “Hey, it is safe to get data from me”.</p><p>When you enable CORS on a given backend (by adding appropriate headers on the HTTP response), the browser will permit access to data from that server. So if example2.com now has CORS enabled, example1.com can safely download the image hosted at example2.com.</p><p>Note that CORS is solely a browser problem. It doesn’t exist in the context of API tools like Postman, <a href="https://httpie.io/">httpie</a>, cURL, etc.</p><h3>How to enable CORS</h3><p>There are 2 ways to solve the CORS problem. We’ve seen the first one already. That is, adding headers on the response of an API call.</p><p>All CORS headers start with “Access-Control”. For example:</p><ul><li>Access-Control-Allow-Origin,</li><li>Access-Control-Allow-Headers,</li><li>Access-Control-Allow-Methods,</li><li>etc.</li></ul><p>As their name indicates, these response headers specify what kind of entity should be allowed in the CORS policy. For example, Access-Control-Allow-Origin tells the browser which domain names can access data.</p><p>On the backend side, you can give a specific set of domain names to Access-Control-Allow-Origin. You could also set this header to an asterisk (*). This is a wildcard. It will permit calls from all browsers at all times. The same is the case with other Access-Control headers.</p><p>What about when you don’t have access to the backend code? Or when you are just consuming an API and CORS surprises you (after all it worked in Postman)? Or you don’t even understand the process of setting HTTP response headers? What could you do?</p><p>This is where a proxy comes in. Proxy servers are the second way to solve CORS issues.</p><h3>What is a proxy server?</h3><p>Proxy servers are intermediaries. A proxy server is a man-in-the-middle between your browser and the CORS-disabled backend.</p><p>Proxies simply carry out the API call you are trying to make from the browser. The difference lies in the fact that the proxy server does not make this call inside the browser. As such, there are no CORS limits.</p><p>The proxy server adds the appropriate CORS headers on the response from the API call. Consequently, your browser won’t complain and at the same time, you obtain the data you wanted from the target server (or example2.com).</p><h3>What are Firebase Functions?</h3><p><a href="https://keepdeploying.com/67bbbcd7ddd6">Firebase is a serverless platform</a>. It is a Backend-as-a-Service that permits you to quickly build and deploy various parts of your architecture. Firebase Functions are endpoints that run isolated business logic and return immediately.</p><p>An invocation (or HTTP call) to a Firebase Function is FREE (well technically). Firebase gives us <a href="https://firebase.google.com/pricing"><strong>two million free invocations</strong></a><strong> </strong>monthly. After which we pay $0.4 for any extra million invocations.</p><p>Now 2,000,000 HTTP calls are much for a personal CORS proxy server. And even if you exceed that quota, you will still be making an invocation at $0.4 / 1,000,000 invocations. That gives you a negligible cost of $0.0000004 per invocation. So we can safely say that an invocation is free.</p><p>What’s the reason for mentioning billing and money? Well, to use Firebase Functions, you will need to set up billing with Google Cloud if you haven’t yet done so. And if it is your first time in the Google Cloud Platform (GCP), you are eligible for <a href="https://console.cloud.google.com/free">$300 of GCP credits for 3 months</a>.</p><h3>What we will be doing</h3><p>We will create a personal proxy server with the help of Firebase Functions. Firebase Functions is a good choice because:</p><ul><li>Invocations are cheap.</li><li>Each invocation always returns independently (this is serverless stuff).</li><li>We leverage Google’s global infrastructure (high speed and scaling).</li></ul><p>The proxy is a personal one in the sense that you can use it to bypass CORS issues when using APIs you don’t control. Members of your organization can also leverage such a technique.</p><p>Of course, make sure that the data you are accessing is safe. The essence of bypassing CORS is to get things done and not to defeat the security patterns put in place by browser specifications.</p><h3>Setup a Firebase project</h3><ul><li>Go to the <a href="https://console.firebase.google.com/">Firebase Console</a>.</li><li>Create a new Firebase project (or choose an existing one).</li><li><a href="https://console.firebase.google.com/project/_/functions">Enable billing</a> for that project. Go to Functions and Click on “Upgrade Project”. It will take you to GCP and you enter your card. Skip this step if you already have billing setup.</li><li>On your PC, <a href="https://nodejs.org/">download and install NodeJS</a>. Skip this step if you have it installed.</li><li>Next, install Firebase locally. Run the following command. Of course, no need if you are already a <em>Firebaser</em>.</li></ul><pre>npm install -g firebase-tools</pre><ul><li>Sign in to Firebase in the terminal. Run the following command.</li></ul><pre>firebase login</pre><ul><li>Next is to create a local project folder. Let’s name it proxy.</li></ul><pre>mkdir proxy &amp;&amp; cd proxy</pre><ul><li>Now initialize Firebase Functions. The following command will ask you to choose a Firebase project. Choose that you had set aside earlier. Choose JavaScript (instead of TypeScript) for a programming language. Install dependencies.</li></ul><pre>firebase init functions</pre><h3>Set up Cloud Functions as a proxy server</h3><p>Now it is time to go about coding. We will have to write JavaScript code that will serve as the proxy server. But we need two packages from npm for our code to work.</p><ol><li>We need the <a href="https://www.npmjs.com/package/cors">cors</a> package to do the actual CORS work. It will add the appropriate CORS headers to all our responses.</li><li>We need the <a href="https://www.npmjs.com/package/node-fetch">node-fetch</a> package. It makes the API call inside Cloud Functions.</li></ol><p>To install these packages, first, change the current directory of the terminal into the functions folder (created by Firebase in the previous step). Run the following command.</p><pre>  cd functions</pre><p>Then, run the following command to install the packages.</p><pre>npm install cors node-fetch@2.6.1</pre><p>Notice that we pinned the node-fetch to its version 2.x. The reason is that version 3.x and above are exported as ESM-only modules. When you import them with require , they won’t work. Special shout out to <a href="https://medium.com/u/191d5d8d6da4">Olivier de Broqueville</a> for noticing this.</p><p>Open the generated proxy project folder in your IDE. Open index.js file found in the functions folder. Delete its contents and paste the following code inside.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/439f6eab8e4392b33afedc3e0a18a31b/href">https://medium.com/media/439f6eab8e4392b33afedc3e0a18a31b/href</a></iframe><p>The above code declares a <em>cors</em> Cloud Function. The function first rejects any call to it that lacks a valid URL value in the request query string. It then sets up the HTTP call, makes the call, and responds in a CORS-enabled way.</p><p>That’s it about the code for a proxy server.</p><p>Now, we need to deploy the Cloud Function. Run the following command to make it go live.</p><pre>firebase deploy --only functions</pre><p>After a successful deployment, you can now use the cloud function as a proxy server. To use the proxy, use the following: https://us-central1-&lt;project-id&gt;.cloudfunctions.net/cors?url=&lt;encoded-url&gt;.</p><p>Where &lt;project-id&gt; is the unique project ID of your Firebase project and &lt;encoded-url&gt; is the URL that you want to bypass CORS.</p><p>Remember to append the encoded URL in the query string. You can skip encoding if there are no queries on the URL.</p><h3>See the proxy in action</h3><ul><li>Visit any website (let’s say google.com) on a new browser tab.</li><li>Open the browser developer tools (Press Cmd/Ctrl + Shift + i).</li><li>Click on the console tab.</li><li>Try fetching any other website (let’s say medium.com). It should report a CORS error. For example:</li></ul><pre>await fetch(&#39;https://medium.com&#39;);</pre><ul><li>Retry fetching but this time, prefix your proxy server’s endpoint. It should go successfully. For example:</li></ul><pre>await fetch(&#39;https://us-central1-&lt;project-id&gt;.cloudfunctions.net/cors?url=https%3A%2F%2Fmedium.com%0A&#39;);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oZFniidJtJEzRRdjJ85qcA.gif" /></figure><p>Note that us-central1 is the default location of new Firebase/GCP projects. If you are not comfortable with it, or you want a data center closer to your location, you can choose a different project location <strong>at the point of creating the project</strong>. However, this project location can’t be changed once it has been set.</p><p>Also, you can encode or decode URLs at <a href="https://urlencoder.org/">urlencoder.org</a>.</p><h3>Cheers!</h3><p>So far, we’ve seen why CORS errors occur and how to build a proxy server with Firebase Cloud Functions.</p><p>The proxy server is one tool you can build with Firebase. Another tool you can build is a URL shortener. The best part? You can <a href="https://medium.com/firebase-developers/firebase-url-shortener-7754377478e0">build your own URL shortener for FREE entirely on Firebase. Read how to do it here.</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f4be840026b5" width="1" height="1" alt=""><hr><p><a href="https://blog.keepdeploying.com/how-to-create-a-cors-proxy-server-with-firebase-functions-f4be840026b5">How to create a CORS proxy server with Firebase Functions</a> was originally published in <a href="https://blog.keepdeploying.com/">keepdeploying</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Restore Poison Messages (or Dead Letters) from Memphis broker vs RabbitMQ]]></title>
            <link>https://obumnwabude.medium.com/restore-poison-messages-memphis-rabbitmq-71649a671584?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/71649a671584</guid>
            <category><![CDATA[rabbitmq]]></category>
            <category><![CDATA[dead-letter-queue]]></category>
            <category><![CDATA[memphis-dev]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Thu, 22 Sep 2022 21:21:46 GMT</pubDate>
            <atom:updated>2022-09-22T21:21:46.018Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*2vLC1-WmuZcRlHqmDKfchQ.png" /></figure><p>In this article, you will understand possible the causes of poison messages in a message broker. You will also learn how to restore them in both Memphis.dev and RabbitMQ.</p><h3>Table of Contents</h3><ul><li>What is Service Oriented Architecture?</li><li>What is a message broker?</li><li>About Memphis.dev</li><li>About RabbitMQ</li><li>What are poison messages?</li><li>How to Restore Poison Messages from Memphis</li><li>How to Restore Poison Messages from RabbitMQ</li><li>Summary</li></ul><h3>What is Service Oriented Architecture?</h3><p>Service Oriented Architecture (SOA) involves splitting different parts of your application into services. That way you can have services for Authentication, Batch-Processing, Managing Videos, Notifications, etc.</p><p>Separating core functionalities into services help your platform to scale appropriately. You also get to enjoy the benefit of the whole system not breaking down when one service malfunctions. In addition, you can improve each service without tampering others.</p><p>Such backend features usually carry out asynchronous tasks. These asynchronous activities don’t complete at the same time. If a user is interacting on your platform, you don’t want the user to wait for long processes to complete. You want to continue interactivity. At the same time, you want a service to tell you when it is done with its job. This is where message brokers come in.</p><h3>What is a message broker?</h3><p>A message broker is a communication tool used within service-oriented platforms. A message broker (or message queue) is a man-in-the-middle for services in your backend.</p><p>With message brokers, some services can send messages to the broker whereas some can receive. Services that send messages are called <em>producers</em>. On the other hand, services that receive messages are called <em>consumers.</em></p><p>A “message” in message broker stands for <em>what</em> the service sends. It could be an event, piece of data, a task, etc. A “message” represents what the services communicate.</p><p>As a <a href="https://en.wikipedia.org/wiki/Broker">broker</a>, a message broker is an intermediary for messages. As a <a href="https://en.wikipedia.org/wiki/Queue_(abstract_data_type)">queue</a>, a message broker permits you to stock up messages till consumers get them. Message brokers complete the scaling of Service Oriented platforms. Message brokers ensure that services <em>appropriately</em> communicate with each other.</p><p><a href="https://memphis.dev/">Memphis.dev</a> and <a href="https://www.rabbitmq.com/">RabbitMQ</a> are example message brokers. They are both open source. Both Memphis.dev and RabbitMQ have a dashboard (or UI) to visualise and manage “messages”.</p><p>You can install both of them on any Operating System with <a href="https://docker.com/">Docker.</a> If you don’t have Docker installed, <a href="https://docs.docker.com/desktop">install Docker for your Operating System here</a>.</p><figure><img alt="Screenshot of Docker Desktop showing Memphis and RabbitMQ containers" src="https://cdn-images-1.medium.com/max/880/0*B_EwZk2Ldg7wASce.png" /></figure><h3>About Memphis.dev</h3><p>Memphis.dev is a real-time data processing platform. It is an open-source, real-time data processing platform for in-app streaming use cases.</p><p>Run the following command in your terminal to install Memphis.</p><pre>curl -s https://memphisdev.github.io/memphis-docker/docker-compose.yml -o docker-compose.yml &amp;&amp; docker compose -f docker-compose.yml -p memphis up</pre><p>When the command completes, open localhost:9000 in the browser, it should show you the Memphis UI. Execute the following steps to get started:</p><ul><li>Enter your email, full name, and set a password. Your email address is important for account recovery. Create account.</li><li>Enter hello for both factory and station names. These are just for demo purposes. Click Next.</li><li>Enter hello for app username. Click next.</li><li>Click or skip through the next welcome onboarding code and steps. Then launch the dashboard.</li></ul><figure><img alt="Screenshot of Memphis dashboard after setup" src="https://cdn-images-1.medium.com/max/880/0*6ceIJlH8s1g6FMpH.png" /></figure><h3>About RabbitMQ</h3><p>RabbitMQ is the most widely deployed open source message broker. With tens of thousands of users, RabbitMQ is one of the most popular open source message brokers.</p><p>Run the following command in your terminal to install RabbitMQ.</p><pre>docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.10-management</pre><p>When the command completes, open localhost:15672 in the browser, it should show you the RabbitMQ UI. Sign in with guest as both username and password. These are the default credentials when you set up RabbitMQ with Docker.</p><figure><img alt="Screenshot of RabbitMQ dashboard after setup" src="https://cdn-images-1.medium.com/max/880/0*ilKZL7KT0p5pSY-Y.png" /></figure><h3>What are poison messages?</h3><p>Poison messages are those messages that have not reached their destination. The message broker had received them but it found it hard to deliver them. At times, it could not deliver them because the consumer may be offline. So the broker will keep sending the messages till they finally become <em>poison</em>.</p><p>Another reason why poison messages could occur is because of acknowledgment. In a normal setup, when a consumer receives and uses up a message from the broker, the consumer <em>acknowledges</em> that message. Acknowledging a message tells the broker that that message has been received. Acknowledging is important. It is the broker’s way of ensuring proper communication across services.</p><p>Asides acknowledging, a consumer can reject a message (negative acknowledgment). When a consumer receives a message and rejects it or does not acknowledge it, the message broker will keep on retrying. It keeps retrying because it wants to ensure that the message gets across to the consumer. After all, that’s why it is a broker in the first place!</p><p>Also, there is usually a predefined number of times a broker would keep retrying messages before those messages become poison. You can also set this “number of times”. Also, you can instead configure a message broker to keep retrying but for a <em>period of time</em> (like 5mins, 2hours, 7days, etc.)</p><p>We call these failed messages <em>poison</em> because they introduce unintended effects to the system. If it was a user-initiated task, this user may never get the intended value delivered. If the value were a paid feature, such failure could cause a terrible customer experience (and loss of money). On the broker’s side, it could reduce platform fidelity. As it could seem that the message broker is not reliable for its job.</p><p>Poison messages are also called dead letters. “Dead letters” suit properly for poison messages for two reasons:</p><ol><li>Messages are letters in the context of the broker’s transactions. A letter has a destination likewise a message.</li><li>The letters are “dead” because they did not reach their appropriate destination.</li></ol><p>Rather than discarding failed or unacknowledged messages, message brokers keep retrying as stated above. If retry time or number of retries exceed the preset maximum, message brokers store these poison messages (or dead letters). You then need to restore these poison messages to ensure smooth running of your system. How you restore poison messages (or dead letters) depend on the message broker you are using.</p><h3>How to Restore Poison Messages from Memphis</h3><p>To restore poison messages in Memphis, resend the poison messages and acknowledge them.</p><p>To see this in action, let’s use NodeJS to create a simple “Hello World” usecase of Memphis. We will write code that sends and receives “Hello World” messages to and from Memphis. However, the consuming part will not acknowledge the messages. This way Memphis will keep sending till they become poison messages.</p><p>Create a new memphis-poison folder, install memphis, and run the code for the above setup. Run the following commands in your terminal to do the above.</p><pre>mkdir memphis-poison <br>cd memphis-poison <br>npm init -y<br>npm install --save memphis-dev</pre><p>Create a new index.js file inside the newly created memphis-poison folder. Paste the following JavaScript code inside.</p><pre>const memphis = require(&#39;memphis-dev&#39;);</pre><pre>(async function () {<br>  try {<br>    await memphis.connect({<br>      host: &#39;localhost&#39;,<br>      username: &#39;hello&#39;,<br>      connectionToken: &#39;memphis&#39;<br>    });</pre><pre>    const consumer = await memphis.consumer({<br>      stationName: &#39;hello&#39;,<br>      consumerName: &#39;hello_consumer1&#39;,<br>      consumerGroup: &#39;hello_consumer_group&#39;,<br>    });<br>    consumer.on(&#39;message&#39;, (message) =&gt; {<br>      console.log(message.getData().toString());</pre><pre>      // commented out the following line to produce poison messages<br>      message.ack();</pre><pre>    });<br>    consumer.on(&#39;error&#39;, (error) =&gt; {<br>      console.log(error);<br>    });</pre><pre>    const producer = await memphis.producer({<br>      stationName: &#39;hello&#39;,<br>      producerName: &#39;hello_producer1&#39;<br>    });<br>    for (let index = 0; index &lt; 10; index++) {<br>      await producer.produce({<br>        message: Buffer.from(&#39;Hello world&#39;)<br>      });<br>      console.log(&#39;Message sent&#39;);<br>    }<br>    console.log(&#39;All messages sent&#39;);<br>  } catch (ex) {<br>    console.log(ex);<br>    memphis.close();<br>  }<br>})();</pre><p>The above code connects to Memphis broker. It produces 10 “Hello World” messages and consumes them. Run the file by running the following command in the terminal. Ensure you are within the memphis-poison folder inside the terminal.</p><pre>node index.js</pre><p>Notice that the terminal keeps receiving the “Hello World” messages. This is because the message.ack(); line is commented out. That code is supposed to acknowledge the messages but as it is commented out, the messages become poison messages.</p><p>The default Time-To-Live (TTL) for messages in Memphis is 30 seconds. So the code will keep receiving “Hello World”s for up to half a minute before it stops and becomes poison.</p><p>Check the Memphis UI dashboard to see the poison messages. It will create a Dead Letter Station (DLS) and keep the poison messages there.</p><figure><img alt="Screenshot of Memphis dashboard showing poison messages" src="https://cdn-images-1.medium.com/max/880/0*k9WAU9PMG9GzrM5l.jpg" /></figure><p>To restore the poison messages, simply restore and acknowledge them.</p><ul><li>Select all of the poison messages and click on resend.</li><li>Get back to the code, uncomment the message.ack(); line, and re-run the file.</li></ul><p>This will re-produce 10 “Hello World”s, then consume them, and also restore the poison messages from the previous run. And that’s how to restore poison messages from Memphis broker.</p><h3>How to Restore Poison Messages from RabbitMQ</h3><p>To restore poison messages in RabbitMQ, create a Dead Letter Exchange (DLX) for those messages.</p><p>To see this in action, let’s use NodeJS to create a simple “Hello World” use case of RabbitMQ. We will write code that sends and receives “Hello World” messages to and from RabbitMQ. However, the consuming part will negatively acknowledge the messages. This way RabbitMQ will keep sending till they become poison messages.</p><p>Create a new rabbitmq-poison folder, install rabbitmq, and run the code for the above setup. Run the following commands in your terminal to do the above.</p><pre>mkdir rabbitmq-poison <br>cd rabbitmq-poison <br>npm init -y<br>npm install --save amqplib</pre><p>Create a new index.js file inside the newly created rabbitmq-poison folder. Paste the following JavaScript code inside.</p><pre>const amqp = require(&#39;amqplib/callback_api&#39;);</pre><pre>amqp.connect(&#39;amqp://localhost&#39;, function (error0, connection) {<br>  if (error0) throw error0;</pre><pre>  const queue = &#39;hello&#39;;<br>  const msg = &#39;Hello World!&#39;;</pre><pre>  connection.createChannel(function (error1, channel) {<br>    if (error1) throw error1;</pre><pre>    channel.assertQueue(queue, {<br>      durable: false,<br>      messageTtl: 10000<br>    });</pre><pre>    for (let index = 0; index &lt; 10; index++) {<br>      channel.sendToQueue(queue, Buffer.from(msg));<br>      console.log(` [*] Sent ${msg}`);<br>    }<br>    console.log(&#39;All messages sent&#39;);</pre><pre>    console.log(` [*] Waiting for messages in ${queue}. To exit press CTRL+C`);<br>    let receiveCount = 1;<br>    channel.consume(queue, (msg) =&gt; {<br>      console.log(` [${receiveCount}] Received ${msg.content}`);<br>      receiveCount++;</pre><pre>      // the following negatively acknowledges the message to make it poison<br>      channel.nack(msg);<br>    });<br>  });<br>});</pre><p>The above code connects to RabbitMQ broker. It produces 10 “Hello World” messages and consumes them.</p><p>In channel.assertQueue, we’ve set the messageTtl (Messages’ Time To Live - TTL) to 10000 (10 seconds actually). We are setting this so that RabbitMQ can stop retrying after 10 seconds. RabbitMQ doesn’t set a default TTL. So we can say that RabbitMQ’s default TTL for messages is infinity. We are setting this TTL to ensure that the messages quickly become poison messages.</p><p>In channel.consume, we are keeping count of how many times the consumer received messages from RabbitMQ. The receiveCount quickly gets huge under 10 seconds and finally stops counting (the consumer stops receiving). It gets this high to tell how much RabbitMQ keeps retrying messages to ensure they get delivered.</p><p>Still in channel.consume, the channel.nack(msg); line causes the messages to be negatively acknowledged. Given that the messages have not been acknowledged and that they have a 10-second time-to-live, they become poison messages.</p><p>Run the file by running the following command in the terminal. Ensure you are within the rabbitmq-poison folder inside the terminal.</p><pre>node index.js</pre><p>To restore the poison messages, create a Dead Letter Exchange (DLX) for those messages. To create this DLX, you will need to setup a different channel for consuming these dead letters (or poison messages). This DLX will consume the dead letters immediately after they get negatively acknowledged.</p><p>Create a different dlx.js file within the rabbitmq-poison folder. Paste the following code inside</p><pre>const amqp = require(&#39;amqplib/callback_api&#39;);</pre><pre>amqp.connect(&#39;amqp://localhost&#39;, function (error0, connection) {<br>  if (error0) throw error0;</pre><pre>  const queue = &#39;helloDlx&#39;;<br>  const msg = &#39;Hello World!&#39;;</pre><pre>  connection.createChannel(function (error1, channel) {<br>    if (error1) throw error1;</pre><pre>    channel.assertQueue(queue, { deadLetterExchange: &#39;dlx&#39; });</pre><pre>    let sendCount = 1;<br>    const interval = setInterval(() =&gt; {<br>      channel.sendToQueue(queue, Buffer.from(msg));<br>      console.log(` [${sendCount}] Sent ${msg}`);</pre><pre>      if (sendCount &gt; 10) {<br>        clearInterval(interval);<br>        console.log(&#39;All messages sent&#39;);<br>      } else {<br>        sendCount++;<br>      }<br>    }, 500);</pre><pre>    console.log(` [*] Waiting for messages in ${queue}. To exit press CTRL+C`);<br>    let receiveCount = 1;<br>    channel.consume(queue, (msg) =&gt; {<br>      console.log(` [${receiveCount}] Received ${msg.content}`);<br>      receiveCount++;</pre><pre>      // makes poison messages and send them to the dead letter exchange above<br>      channel.nack(msg);<br>    });<br>  });</pre><pre>  connection.createChannel(function (error2, channel) {<br>    if (error2) throw error2;</pre><pre>    channel.assertExchange(&#39;dlx&#39;, &#39;direct&#39;);</pre><pre>    let receiveCount = 1;<br>    channel.consume(queue, (msg) =&gt; {<br>      console.log(` [${receiveCount}] Restored Poison ${msg.content}`);<br>      receiveCount++;<br>    });<br>  });<br>});</pre><p>The above code connects to RabbitMQ broker. It uses a different queue (helloDlx) from the queue in index.js (hello) to show the restoring process. The main difference between this code and the code index.js is that this code introduces the Dead Letter Exchange (DLX).</p><p>In this example, the name of the DLX is simply &#39;dlx&#39;. You should use more meaningful DLX names in your codebases. DLX are backup kind of services to ensure that the poison messages get properly handled.</p><p>When creating the DLX (inside the second channel), we used channel.assertExchange(&#39;dlx&#39;, &#39;direct&#39;);. However, when setting the primary queue for the messages (inside the first channel), we indicated the DLX in the options parameter with channel.assertQueue(queue, { deadLetterExchange: &#39;dlx&#39; });.</p><p>The above code also sends a message every 0.5 seconds till they reach 10 messages. This speed reduction is to see the restoring process in action.</p><p>Run this dlx.js file in your terminal:</p><pre>node dlx.js</pre><p>This consumes the dead letter messages immediately. Notice that the “Hello World” messages get restored after their first negative acknowledgment.</p><h3>Summary</h3><p>To restore poison messages in Memphis, simply resend and acknowledge them. To restore poison messages in RabbitMQ, setup a dead letter exchange for them.</p><p>The manner of creating poison messages in this article was mainly for demo purposes. In real life, you will encounter poison messages from unforeseen circumstances. Consuming services could get faulty and hence cause unacknowledged messages. Either way, you’ve learned how to restore poison messages from Memphis broker and RabbitMQ.</p><p>Cheers!</p><h3>Helpful Resources</h3><ul><li><a href="https://docs.memphis.dev/memphis-new/dashboard-ui/troubleshooting/dead-letter">Dead Letter Station (DLS) — Memphis Docs</a></li><li><a href="https://www.rabbitmq.com/dlx.html">Dead Letter Exchanges (DLX) — RabbitMQ Docs</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=71649a671584" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to build a real-time app with NestJS and Memphis broker]]></title>
            <link>https://obumnwabude.medium.com/memphis-broker-nestjs-real-time-app-3361bbb2fd2a?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/3361bbb2fd2a</guid>
            <category><![CDATA[message-queue]]></category>
            <category><![CDATA[grpc]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[memphis-broker]]></category>
            <category><![CDATA[nestjs]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Wed, 24 Aug 2022 11:40:45 GMT</pubDate>
            <atom:updated>2022-08-24T11:40:45.098Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iDr9qlO2xKnGEfrsTHviTA.png" /></figure><h3>Introduction</h3><p>Major systems have all become digital. Usage of applications keeps increasing. Scaling your apps is now implicit.</p><p>Usually, your application will consist of multiple services. These services will each be in charge of doing something different. Let’s say you’re building a chat application. You could have a messages service, a notification service, a status/stories service, a call service, an authentication service, etc.</p><p>One or more of these services have to communicate with each other. You want your app to scale properly. You’ll choose the best communication method for your platform’s services. You need a message broker.</p><p>A message broker is an intelligent middleman for your application’s services. A message broker receives “messages” from one or more services. It then delivers these “messages” to other services that want to consume them. This way, your platform scales and meets your users’ demands.</p><p>A message broker is made up of 3 parts. A queue, producers, and consumers. The queue is the manager of messages. The producers send messages to the queue while the consumers receive them.</p><h3>What is Memphis Broker?</h3><p><a href="https://memphis.dev/">Memphis</a> is a modern, powerful, and open-source message broker. Memphis takes away the excess work for data engineers and developers. Memphis broker gives you good developer experience. It reduces your development time by 90%.</p><p>Memphis is unique. It comes with a UI and a CLI. You can use the UI to visualize the movements of data. You can use the CLI to run important commands on the broker. There is also the sandbox at <a href="https://sandbox.memphis.dev/">sandbox.memphis.dev</a> for interacting to see what it looks like without installing it.</p><p>Memphis is available for Go, Java, NodeJS, and Python. More platforms will be supported soon. In Memphis, a queue is called a station. And many stations can be found in a factory. When you start using Memphis for the first time, you will at least need to create a factory and a station inside that factory.</p><h3>How to set up Memphis broker using Docker</h3><p><a href="https://docker.com/">Docker</a> is a popular tool for executing and managing software. It provides containers for software execution. It guarantees that a container will have the required dependencies for a given program to run.</p><p>You can use Docker to install Memphis. The advantage of using Docker is that you are assured that Memphis will work properly everywhere. Memphis will be the same in your local development and when your app is in production.</p><p>To install Memphis, you need to have Docker installed. If you don’t have Docker installed, <a href="https://docs.docker.com/desktop">install Docker for your Operating System here</a>. Start Docker and ensure it’s running.</p><p>Run the following command in your terminal to install Memphis. It’ll fetch the Docker compose file for Memphis set up and set up Memphis appropriately.</p><pre>curl -s https://memphisdev.github.io/memphis-docker/docker-compose.yml -o docker-compose.yml &amp;&amp; docker compose -f docker-compose.yml -p memphis up</pre><p>If the command completes successfully, Memphis should be up and running in Docker. If you encounter any issues, consider checking your internet connection.</p><p>This is a one-time installation of Memphis. In consequent times, when you need to use Memphis, simply launch Docker Desktop and ensure that the Memphis containers are running.</p><p>Carry out the following instructions to complete the Memphis setup in the UI:</p><ul><li>Open localhost:9000 in the browser, it should show you the Memphis UI.</li><li>Sign in with root and memphis. These are the default credentials of the “root” user when you set up Memphis with Docker.</li><li>Enter chat as the factory name. Enter chat as the station name. Click “Next”.</li><li>Enter chat as the application user name. Click “Create app user”. It should show you “memphis” as the connection token (password). This is the default.</li><li>Skip through the next steps and access the dashboard.</li></ul><p><strong>Note:</strong> You can use a different factory, station, and username instead of chat above. We are using chat to keep things simple. Use names that are meaningful to your project. The Memphis UI permits you to manage factories, stations, and users. You can create more users with varying passwords/tokens. This ensures security.</p><h3>What we will be building</h3><p>Many applications now need to have a real-time ability. Real-time event-driven architectures use message brokers for many-to-many inter-service communication. Examples of real-time apps include online games, admin dashboards, chat apps, etc.</p><p>Let’s build a simple chat app. Because it is a simple app, it will have only two services for now. One for all messages and the other for sending messages. The app should have more services but these two are enough to show how to use Memphis.</p><p>Let’s call this chat app FastChat. In this article, you’ll learn how to build FastChat using Memphis broker and NestJS. To keep things simple, FastChat won’t save chat messages to any database. It’ll keep them in memory while the application is alive. FastChat mainly focuses on showing you how to use Memphis broker.</p><p><a href="https://github.com/obumnwabude/fast-chat/tree/article">The final codebase for FastChat is here on GitHub.</a></p><figure><img alt="Recording of setting up Memphis UI" src="https://cdn-images-1.medium.com/max/1024/1*atYtwONo5z6GFsQPSvbHGw.gif" /></figure><h3>What is NestJS?</h3><p>NestJS is an open-source JavaScript framework for server-side applications. It leverages TypeScript’s features to build robust apps. You can structure your Nest project around controllers, modules, and services to properly modularize the code.</p><p>FastChat has two services: AllMessages and SendMessage service. These two services will intercommunicate using Memphis broker. We will group them into a Nest module (ChatMessagesModule) because they relate to the same entity.</p><p>FastChat also has a BrokerModule. This module will configure Memphis broker. It’ll import the MemphisModule and connect the Nest app to Memphis. The BrokerModule will also expose a BrokerService through which other parts of the application can access Memphis.</p><h3>How to set up NestJS</h3><p>To install NestJS, you need to have NodeJS installed. If you don’t have NodeJS installed, download and install it from here.</p><p>To install NestJS, open a new terminal instance and run the following command:</p><pre>npm install -g @nestjs/cli</pre><p>Now create a new NestJS project. We will use fast-chat for the project name. Run the following command:</p><pre>nest new fast-chat -p npm</pre><p>The -p flag is use npm as package manager. Open the newly created fast-chat project in your favorite code editor.</p><h3>How to set up the ConfigModule in NestJS</h3><p>Next, we have to set up Memphis broker inside NestJS. But during the setup, we have to connect NestJS to Memphis broker. For such connection, we need at least three properties:</p><ul><li>host: The domain name or IP address where Memphis is found, e.g. localhost</li><li>username: The user that NestJS will act on their behalf, e.g. root</li><li>connectionToken: The password (token actually) of the above user.</li></ul><p>The above three are sensitive information. For security purposes, they shouldn’t be added to source control. Rather, we need to access them through environment variables.</p><p>NestJS provides a <a href="https://docs.nestjs.com/techniques/configuration">ConfigModule</a> for providing environment variables. It uses <a href="https://github.com/motdotla/dotenv">dotenv</a> internally. Create a file with .env as name directly inside the fast-chat project folder. Paste the following inside:</p><pre>MEMPHIS_HOST=localhost<br>MEMPHIS_USERNAME=chat<br>MEMPHIS_TOKEN=memphis</pre><p>These are the default credentials. You should use more secure credentials in production. That is the very reason of use the ConfigModule in the first place.</p><p>Add the NestJS’ ConfigModule to the fast-chat project. Run the following command to install it.</p><pre>npm install --save @nestjs/config</pre><p>Import the ConfigModule in AppModule. NestJS auto-created the src/app.module.ts file among other files when we created the project. This file contains the declaration of AppModule. Replace the contents of AppModule with the following:</p><pre>import { Module } from &#39;@nestjs/common&#39;;<br>import { ConfigModule } from &#39;@nestjs/config&#39;;<br>import { AppController } from &#39;./app.controller&#39;;<br>import { AppService } from &#39;./app.service&#39;;</pre><pre>@Module({<br>  imports: [ConfigModule.forRoot({ isGlobal: true })],<br>  controllers: [AppController],<br>  providers: [AppService],<br>})<br>export class AppModule {}</pre><p>Here, we are importing the ConfigModule and setting the isGlobal to true. Doing this will auto-import the ConfigModule in all other modules we will create (which is what we want). In consequent files that need any values from the .env file, you will only access them from ConfigService (coming later on).</p><p>How to setup Memphis broker in NestJS Run the following command to install Memphis in FastChat:</p><pre>npm install --save memphis-dev</pre><p>Generate the BrokerModule (to set up Memphis) with the following command:</p><pre>nest generate module broker</pre><p>This will create a new broker.module.ts file inside a new broker folder. Delete the contents of this new broker.module.ts file and paste the following:</p><pre>import { Module } from &#39;@nestjs/common&#39;;<br>import { MemphisModule } from &#39;memphis-dev/nest&#39;;</pre><pre>@Module({<br>  imports: [MemphisModule.register()],<br>  exports: [BrokerService],<br>})<br>export class BrokerModule {}</pre><p>The above imports and registers the MemphisModule. It also exports a BrokerService (which we will create right away. This BrokerService connects the app to Memphis (when NestJS starts up). This BrokerService is what other parts of the Nest app will use to interact with Memphis.</p><p>Run the following command to create the BrokerService:</p><pre>nest generate service broker</pre><p>This will create a new broker.service.ts file inside the broker folder. Delete its contents and paste the following:</p><pre>import { Injectable, OnModuleInit } from &#39;@nestjs/common&#39;;<br>import { ConfigService } from &#39;@nestjs/config&#39;;<br>import { MemphisService } from &#39;memphis-dev/nest&#39;;</pre><pre>@Injectable()<br>export class BrokerService implements OnModuleInit {<br>  consumer;<br>  producer;</pre><pre>  constructor(<br>    private configService: ConfigService,<br>    private memphisService: MemphisService,<br>  ) {}</pre><pre>  async onModuleInit(): Promise&lt;void&gt; {<br>    try {<br>      await this.memphisService.connect({<br>        host: this.configService.get&lt;string&gt;(&#39;MEMPHIS_HOST&#39;),<br>        username: this.configService.get&lt;string&gt;(&#39;MEMPHIS_USERNAME&#39;),<br>        connectionToken: this.configService.get&lt;string&gt;(&#39;MEMPHIS_TOKEN&#39;),<br>      });<br>      this.consumer = await this.memphisService.consumer({<br>        stationName: &#39;chat&#39;,<br>        consumerName: &#39;chatConsumer&#39;,<br>        consumerGroup: &#39;chatConsumers&#39;,<br>      });<br>      this.producer = await this.memphisService.producer({<br>        stationName: &#39;chat&#39;,<br>        producerName: &#39;chatProducer&#39;,<br>      });<br>    } catch (error) {<br>      console.error(error);<br>      this.memphisService.close();<br>    }<br>  }<br>}</pre><p>The BrokerService imports the ConfigService and the MemphisService. NestJS injects these services using its automatic dependency injection.</p><p>The BrokerService implements the OnModuleInit lifecycle method. This is the right place to connect Memphis broker. It is the right place because NestJS calls this method when initializing dependencies. Notice that it connects to Memphis using environment variables from the ConfigService.</p><p>The BrokerService also exposes references to the producer and consumer entities of Memphis broker. Other services will use these references to produce or consume “messages” (or events).</p><p>Note: Use meaningful consumer and producer names. Use meaningful names that can distinguish which project a particular producer is producing from or from which is a consumer consuming.</p><p>Now we’ve successfully set up Memphis broker. We now need to create the ChatMessages services and link them with Memphis.</p><p>But Before we head over to creating services let’s review another useful tool. Servers usually expose REST APIs that clients will call. However, there are alternative methods for server-to-client communication. One of such methods is gRPC.</p><h3>What is gRPC?</h3><p><a href="https://en.wikipedia.org/wiki/Remote_procedure_call">RPC</a> means Remote Procedure Call. It means calling a function that is located remotely (in the server).</p><p><a href="https://grpc.io/">gRPC</a> is an open-source RPC framework for client-server communication. gRPC exposes server functions or microservices to clients. The clients too use gRPC to access data from the server(s). gRPC is available for all programming languages.</p><p>gRPC is faster than REST APIs. It is faster because it uses Protocol Buffers to exchange data (rather than JSON or XML). <a href="https://developers.google.com/protocol-buffers">Protocol Buffers (ProtoBuf)</a> is a neutral way of expressing structured data. ProtoBuf is easy to understand.</p><p>FastChat uses gRPC because gRPC is the go-to way for client-server communication in a Service Oriented Architecture (SOA).</p><h3>How to set up gRPC in NestJS</h3><p>To set up gRPC, run the following command while inside the fast-chat directory:</p><pre>npm i --save @grpc/grpc-js @grpc/proto-loader @nestjs/microservices</pre><p>It adds necessary dependencies for using gRPC in NestJS. Next, create the protobuf (.proto) file for the services in FastChat.</p><p>Create a new folder in the src folder, name this new folder chat-message. Create a new file inside this new folder. Name the new file chat-message.proto. Paste the following into this src/chat-message/chat-message.proto</p><pre>syntax = &quot;proto3&quot;;</pre><pre>package ChatMessage;</pre><pre>message Empty { }</pre><pre>message ChatMessage {<br>  string author = 1;<br>  string text = 2;<br>  int64 time = 3;<br>}</pre><pre>service SendMessageService {<br>  rpc send (ChatMessage) returns (Empty) {}<br>}</pre><pre>service AllMessagesService {<br>  rpc all (Empty) returns (stream ChatMessage) {}<br>}</pre><p>This is what protobuf looks like. It is language agnostic. The first line is mandatory and it tells the version of protobuf in use. In this case, &quot;proto3&quot;, (the latest version).</p><p>message in protobuf refers to a custom type we create. ChatMessage contains an author (a string), the message’s text (string) and when the time the message was sent. int64 is a safe way of representing time as a timestamp.</p><p>This protobuf file is not a TypeScript file. By default, NestJS includes all TypeScript files when building the server. If a given NestJS project needs to monitor or include non-TypeScript files into the building process, Nest has to be aware of those files.</p><p>The src/chat-message/chat-message.proto above file is an example. To tell Nest about it, add it to the nest-cli.json file. This file is directly inside the fast-chat project folder. Include the following in the JSON file:</p><pre>  &quot;compilerOptions&quot;: {<br>    &quot;assets&quot;: [&quot;**/*.proto&quot;],<br>    &quot;watchAssets&quot;: true<br>  }</pre><p>One more config step for gRPC involves adding gRPC as a microservice into NestJS. Delete the contents of src/main.ts file and paste the following:</p><pre>import { NestFactory } from &#39;@nestjs/core&#39;;<br>import { MicroserviceOptions, Transport } from &#39;@nestjs/microservices&#39;;<br>import { join } from &#39;path&#39;;<br>import { AppModule } from &#39;./app.module&#39;;</pre><pre>async function bootstrap() {<br>  const app = await NestFactory.create(AppModule);<br>  app.connectMicroservice&lt;MicroserviceOptions&gt;({<br>    transport: Transport.GRPC,<br>    options: {<br>      package: &#39;ChatMessage&#39;,<br>      protoPath: join(__dirname, &#39;chat-message/chat-message.proto&#39;),<br>    },<br>  });<br>  await app.startAllMicroservices();<br>  await app.listen(3000);<br>  console.log(`Application is running on: ${await app.getUrl()}`);<br>}<br>bootstrap();</pre><h3>How to create a service with NestJS</h3><p>Now that gRPC is properly set up, let’s implement our AllMessages and SendMessage services. These services will interact with the ChatMessage type or interface. We need an equivalent of these type in TypeScript (separate from what we declared in protobuf).</p><p>Create a new chat-message.interface.ts file inside the chat-message folder. Paste the following inside.</p><pre>export interface ChatMessage {<br>  author: string;<br>  text: string;<br>  time: number;<br>}</pre><p>Next, create a ChatMessagesModule that will contain the AllMessages and SendMessage services. Run the following command to create the module.</p><pre>nest generate module chat-message</pre><p>This will create a new chat-message.module.ts file inside the chat-message folder. Given that we will use Memphis broker in the services, import the BrokerModule into this newly generated ChatMessageModule. Delete the contents of the chat-message.module.ts file and paste the following:</p><pre>import { Module } from &#39;@nestjs/common&#39;;<br>import { BrokerModule } from &#39;src/broker/broker.module&#39;;</pre><pre>@Module({<br>  imports: [BrokerModule]<br>})<br>export class ChatMessageModule {}</pre><p>gRPC requires that NestJS code implments the methods and services defined in the protobuf file. To implement these services, we will use a controller. A controller in NestJS holds logic for API endpoints. But given that FastChat uses gRPC, the controller will rather hold gRPC functions and services.</p><p>Create a new ChatMessageController. Run the following command:</p><pre>nest generate controller chat-message</pre><p>It will create a new chat-message.controller.ts file inside the chat-message folder. Replace the contents of this file with the following:</p><pre>import { Controller } from &#39;@nestjs/common&#39;;<br>import { GrpcMethod } from &#39;@nestjs/microservices&#39;;<br>import { Observable, ReplaySubject } from &#39;rxjs&#39;;<br>import { BrokerService } from &#39;src/broker/broker.service&#39;;<br>import { ChatMessage } from &#39;./chat-message.interface&#39;;</pre><pre>@Controller(&#39;chat-message&#39;)<br>export class ChatMessageController {<br>  private readonly chatMessages$ = new ReplaySubject&lt;ChatMessage&gt;();</pre><pre>  constructor(private brokerService: BrokerService) {}</pre><pre>  onModuleInit(): void {<br>    this.brokerService.consumer.on(&#39;message&#39;, (message) =&gt; {<br>      this.chatMessages$.next(<br>        JSON.parse(message.getData().toString()) as ChatMessage<br>      );<br>      message.ack();<br>    });<br>  }</pre><pre>  @GrpcMethod(&#39;AllMessagesService&#39;, &#39;all&#39;)<br>  all(): Observable&lt;ChatMessage&gt; {<br>    return this.chatMessages$.asObservable();<br>  }</pre><pre>  @GrpcMethod(&#39;SendMessageService&#39;, &#39;send&#39;)<br>  async send(chatMessage: ChatMessage): Promise&lt;void&gt; {<br>    await this.brokerService.producer.produce({<br>      message: Buffer.from(JSON.stringify(chatMessage))<br>    });<br>  }<br>}</pre><p>ChatMessageController declares a chatMessages$ <a href="https://rxjs.dev/api/index/class/ReplaySubject">ReplaySubject</a>. This is an entity that we can push values to and listen to values from.</p><p>ChatMessageController obtains the BrokerService via dependency injection. In the OnModuleInit lifecycle hook, we consume “messages” from the BrokerService’s consumer, and emit them to chatMessages$ ReplaySubject. message.ack(); tells Memphis that the “message” is acknowledged (so that it won’t send that “message” again).</p><p>ChatMessageController also implements the two communicating services of this real-time app. SendMessageService takes a ChatMessage and produces it to the BrokerService. In turn, AllMessagesService provides as chatMessages$ ReplaySubject as an Observable.</p><p>Production and Consumption of broker messages can only be done with strings. This is why we convert ChatMessages to and fro strings when sending and receiving them from the BrokerService’s producer and consumer.</p><p>At this point, we’ve successfully setup a simple real-time app with NestJS and Memphis broker.</p><h3>[Optional]: Test the Real-time chat app with gRPC client</h3><p>With the way gRPC works, you technically don’t make REST API requests. You have to access your backend services from another gRPC SDK (client or server).</p><p>We can test the above chat services. One way is to create another gRPC client inside the NestJS project folder. This client will make calls to the services. In turn, you can observe their action in the Memphis Broker UI.</p><p>Update the AppModule to import the gRPC ClientsModule. Replace the contents of src/app.module.ts with the following:</p><pre>import { Module } from &#39;@nestjs/common&#39;;<br>import { ConfigModule } from &#39;@nestjs/config&#39;;<br>import { ClientsModule, Transport } from &#39;@nestjs/microservices&#39;;<br>import { join } from &#39;path&#39;;<br>import { AppController } from &#39;./app.controller&#39;;<br>import { AppService } from &#39;./app.service&#39;;<br>import { BrokerModule } from &#39;./broker/broker.module&#39;;<br>import { ChatMessageModule } from &#39;./chat-message/chat-message.module&#39;;</pre><pre>@Module({<br>  imports: [<br>    ConfigModule.forRoot({ isGlobal: true }),<br>    BrokerModule,<br>    ChatMessageModule,<br>    ClientsModule.register([<br>      {<br>        name: &#39;CHAT_MESSAGE&#39;,<br>        transport: Transport.GRPC,<br>        options: {<br>          package: &#39;ChatMessage&#39;,<br>          protoPath: join(__dirname, &#39;chat-message/chat-message.proto&#39;)<br>        }<br>      }<br>    ]),<br>  ],<br>  controllers: [AppController],<br>  providers: [AppService]<br>})<br>export class AppModule {}</pre><p>Generate a new ClientController that will call the gRPC methods. Run the following command in the terminal:</p><pre>nest generate controller client</pre><p>This will create a new client.controller.ts inside a new client folder. Replace the contents of this new file with the following:</p><pre>import {<br>  Body,<br>  Controller,<br>  Inject,<br>  OnModuleInit,<br>  Post,<br>  Res<br>} from &#39;@nestjs/common&#39;;<br>import { ClientGrpc } from &#39;@nestjs/microservices&#39;;<br>import { Response } from &#39;express&#39;;<br>import { firstValueFrom, Observable } from &#39;rxjs&#39;;<br>import { ChatMessage } from &#39;../chat-message/chat-message.interface&#39;;</pre><pre>interface AllMessagesService {<br>  all(): Observable&lt;ChatMessage&gt;;<br>}</pre><pre>interface SendMessageService {<br>  send(chatMessage: ChatMessage): Observable&lt;void&gt;;<br>}</pre><pre>@Controller(&#39;client&#39;)<br>export class ClientController implements OnModuleInit {<br>  private allMessagesService: AllMessagesService;<br>  private sendMessageService: SendMessageService;</pre><pre>  constructor(@Inject(&#39;CHAT_MESSAGE&#39;) private readonly client: ClientGrpc) {}</pre><pre>  onModuleInit(): void {<br>    this.allMessagesService =<br>      this.client.getService&lt;AllMessagesService&gt;(&#39;AllMessagesService&#39;);<br>    this.sendMessageService =<br>      this.client.getService&lt;SendMessageService&gt;(&#39;SendMessageService&#39;);</pre><pre>    this.allMessagesService.all().subscribe((chatMessage: ChatMessage) =&gt; {<br>      console.log(chatMessage);<br>    });<br>  }</pre><pre>  @Post(&#39;&#39;)<br>  async sendMessage(<br>    @Body() chatMessage: ChatMessage,<br>    @Res() res: Response<br>  ): Promise&lt;void&gt; {<br>    await firstValueFrom(this.sendMessageService.send(chatMessage));<br>    res.json({ status: true, message: &#39;Acknowledged&#39; });<br>  }<br>}</pre><p>This ClientController subscribes to the all method of the AllMessagesService and prints every ChatMessage to the console. It also exposes a /client POST endpoint to receive messages and send them to Memphis broker (through the send method of SendMessageService).</p><p>Run nest start to start the NestJS application.</p><p>Use any API tester (cURL, Postman, ThunderClient extension on VS Code, etc.) to send a post request to localhost:3000/client. The body of the post request should reflect a valid ChatMessage like:</p><pre>{<br>  &quot;author&quot;: &quot;UserA&quot;,<br>  &quot;text&quot;: &quot;Test Chat Message&quot;,<br>  &quot;time&quot;: 1661096069756<br>}</pre><p>You should receive an acknowledgement feedback. Check the Memphis UI at localhost:9000, you should notice the sent message (event). Check the NestJS logs and notice that it printed your ChatMessage. Send more POST requests and notice more messages and logs.</p><p><a href="https://github.com/obumnwabude/fast-chat/tree/article">The final codebase for FastChat is here on GitHub.</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-f8AKGdetfmtgOWg" /></figure><h3>Why did we use Memphis broker?</h3><p>In a message broker (like Memphis), messages are like events. They are what services use to communicate.</p><p>FastChat above is a simple usecase of a message broker. You might not see the essence of producing and consuming messages if this is the only thing you want to build. Besides we placed the two services in the same .proto file. We also implemented both services with the same controller. These were because of simplicity.</p><p>In a big project, you will separate your services. They might be in different NestJS projects and deployed from different servers. Yet, they will be communicating via Memphis broker. If you were building a chat infrastructure, other services like notifications, authentication, security, etc. are reasons why you should use Memphis broker.</p><p>You will use a Memphis broker because it ensures many-to-many (N:N) communication across your application’s services. It also ensures that every service receives the events or “messages”.</p><h3>Summary</h3><p>Memphis broker stands out in its speed and ease-of-use among other message brokers. You can install it with Docker. It has a UI and CLI through which you can manage your projects. You can also manage factories, stations, and users with Memphis broker.</p><p>The above FastChat is a simple real-time app to get you started with Memphis broker in NestJS. In summary, to build a real-time app with NestJS and Memphis broker:</p><ul><li>Connect to Memphis broker using the MemphisService and the connection parameters.</li><li>Then produce and consume messages as you need in your application.</li></ul><p>Happy Coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3361bbb2fd2a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to build a URL shortener for free with Firebase]]></title>
            <link>https://medium.com/firebase-developers/firebase-url-shortener-7754377478e0?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/7754377478e0</guid>
            <category><![CDATA[url-shorteners]]></category>
            <category><![CDATA[firestore]]></category>
            <category><![CDATA[firebase-cloud-functions]]></category>
            <category><![CDATA[hosting]]></category>
            <category><![CDATA[firebase]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Thu, 30 Jun 2022 12:10:29 GMT</pubDate>
            <atom:updated>2022-12-12T13:17:27.157Z</atom:updated>
            <content:encoded><![CDATA[<p>You could choose to build a URL shortener for commercial purposes. That’s like bit.ly, short.io, tinyurl.com, etc. In this case, you choose to make the service open to the public. Anyone can shorten URLs on your platform.</p><p>However, you can decide to create a URL shortener for personal or organizational branding purposes, so only you or your organization can shorten links with that shortener. Some organizations have URL shorteners for themselves: Google has g.co, LinkedIn has lnkd.in and Telegram has t.me.</p><p>Your reason for building a URL shortener will reflect how you structure the shortener’s front end. That is whether the public can access an interface where they can edit URLs or only you or permitted members in your organization can have such access.</p><p>Whatever your reason would be, the logic and backend for URL shortening are the same. The flow is simple:</p><ol><li>Capture the link after the short domain name.</li><li>Look up the long URL.</li><li>If there has been a valid stored long URL, redirect to this long URL.</li><li>Else, if there is no long URL set for a given short link, respond with 404.</li></ol><h3>On Domain Names</h3><p>Domain names are human-readable website names. They are the “whatever dot com” you type in a browser to visit a particular website. For example: medium.com, telegram.org and flutter.dev.</p><p>As implied in the act of URL <em>shortening,</em> you need a <strong>short</strong> domain name to build a URL shortener. It needs to be short to fulfill the demand of the URLs being shorter than their longer equivalents.</p><p>You can buy domain names at various domain registrars. <a href="https://obum.me/namecheap">Namecheap</a> is one of them. I got obum.me from there. Namecheap also gives free Domain privacy for life.</p><h3>Architecture</h3><p>To build a URL shortener, you first need web hosting. The short domain name will be tied to this hosting. As such, if people click on any shortened link (or open such a link in a browser), their request can be handled in some way.</p><p>Secondly, you need a database to store shortened links and their long URL. And finally, you need a backend engine to redirect the users to the long URL (if one was stored in the database) or 404.</p><h3>Firebase</h3><p><a href="https://firebase.google.com/">Firebase</a> is a Backend-as-a-Service that helps you build, improve, and grow applications. It provides you with tools that individually or together make your app robust.</p><p>Firebase is a platform that comes with many services like <a href="https://firebase.google.com/docs/analytics">Analytics</a>, <a href="https://firebase.google.com/docs/auth">Authentication</a>, <a href="https://firebase.google.com/docs/database">Realtime Database</a>, <a href="https://firebase.google.com/docs/hosting">Hosting</a>, <a href="https://firebase.google.com/docs/cloud-messaging">Cloud Messaging</a>, etc. Firebase has the 3 parts of backend that we need for a URL shortener: <a href="https://firebase.google.com/docs/firestore">Cloud Firestore</a>, <a href="https://firebase.google.com/docs/functions">Cloud Functions</a>, and <a href="https://firebase.google.com/docs/hosting">Firebase Hosting</a>.</p><h3>Cloud Firestore</h3><p><a href="https://firebase.google.com/docs/firestore">Cloud Firestore</a> is a NoSQL database from Firebase. It allows you to create, update, store, and delete data as you want. With Cloud Firestore, you are in charge of data access and structure.</p><p>You will first need a Firebase project. Create one at <a href="https://console.firebase.google.com/">console.firebase.google.com</a>. You can choose an existing project if you wish.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bLofnNU0lMOdvd7cP9hK7Q.gif" /></figure><p>Create a Firestore Database in the project <a href="https://console.firebase.google.com/project/_/firestore">here</a> (Select Cloud Firestore from the left sidebar). Create the database in production mode.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fmga8GjqmwdZbBXAAK7YRw.gif" /></figure><h3>Cloud Functions</h3><p><a href="https://keepdeploying.com/67bbbcd7ddd6">Firebase is a serverless platform</a>. It provides you with “one-time” callable backend endpoints (Cloud Functions). A given cloud function just runs the logic it is supposed to and ends.</p><p>You can configure a cloud function to handle http requests. That way the function will take care of the backend engine for the URL shortener. It will check the request endpoint, look up the database, and redirect as appropriate.</p><h3>A note on Billing</h3><p>You need to write code for the functions, but before that, you have to set up billing. Please don’t fret. This article’s title says “free” and it is not a lie.</p><p>Firebase is linked to the Google Cloud Platform. It is a cloud platform and like all of them, they have some pay-as-you-go features.</p><p>With Cloud Functions, you have <a href="https://firebase.google.com/pricing"><strong>2 million free invocations per month</strong></a><strong>. </strong>In other words, you would have had more than 2 million requests on that URL shortener Firebase project before Firebase will charge you.</p><p>And if you exceed (firstly, congrats on that, your marketing is optimum), you will be charged as little as <strong>$0.4</strong> for any extra million invocations. This is cheap. In reality, before you get to this point, you should have monetized well enough to cover that cost.</p><p>For the record, for the 2 years (and counting) that I have been using Firebase, I have never received any bill. Besides, you can add a $1 <a href="https://firebase.google.com/docs/projects/billing/avoid-surprise-bills">alert/budget to receive emails</a> if any resource usage wants to increase.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Wf20UUbSYoUDmwOowICQFQ.gif" /></figure><h3>Setup Billing</h3><p>To set up billing, Go to the <a href="https://console.firebase.google.com/project/_/functions">functions page</a> in your project and click on “Upgrade project”. Add your card or link with a Google Cloud Platform billing account and you are done. You can also add the warning alert/budget at this stage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i3dHK_haVoFi74mvu5-FgA.gif" /></figure><h3>Setup Cloud Functions</h3><p>Configure cloud functions code with the following steps:</p><ol><li>NodeJS and Firebase<br>a. Download and Install NodeJS from <a href="https://nodejs.org/">here</a> if you don’t have it installed.<br>b. Run the following command in a terminal instance to install Firebase locally.</li></ol><pre>npm install -g firebase-tools</pre><p>c. Sign in with your Google Account. Run the following command to log in. It will take you to the browser.</p><pre>firebase login</pre><p>2. Setup functions code</p><p>a. Create a new folder for the project and change focus to it in the terminal. Run the following command.</p><pre>mkdir url-shortener &amp;&amp; cd url-shortener</pre><p>b. Run the following command in a terminal instance to initialize functions. The prompt will guide you through a series of intuitive questions. However, preferably choose JavaScript and also install dependencies.</p><pre>firebase init functions</pre><p>c. Open the newly created url-shortener project in your preferred editor. Paste the following JavaScript code into the newly created index.jsfile found in the functions folder.</p><p>In addition to checking the database and redirecting as appropriate, the code saves every request to the database (you can use this for analytics purposes in the future). The code also keeps counts of requests and created and deleted links. This is the preferred way to keep counters in NoSQL databases because if the app grows big, getting the total count by fetching all documents and summing is rather expensive.</p><p>Change the example.com domain for the home route (“/”) to your domain of choice. If you have none, remove that line entirely.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e9f5ca268d001a7a2fe9f40dc4895b97/href">https://medium.com/media/e9f5ca268d001a7a2fe9f40dc4895b97/href</a></iframe><p>3. Deploy</p><p>Run the following command to deploy the cloud functions code. It will do different things, and if all is well, it will successfully deploy the functions.</p><pre>firebase deploy --only functions</pre><h3>A note on Counting and Logging</h3><p>The third Firebase component of our URL shortener is hosting. But before proceeding to Firebase Hosting, let’s look at two improvements that can be done with the counter and with logging in the Cloud Functions code above.</p><p>With regards to the counter, the code tracks created and deleted links. This was the former way to do things. Besides, counting is implemented as two individual cloud functions. Their invocations add to the Firebase billing or charge.</p><p>The new and better way to know the count of Firestore documents is simply the <a href="https://firebase.google.com/docs/firestore/query-data/aggregation-queries">COUNT</a> function. It is available on the server-side for all collections and queries. So, instead of keeping a separate collection for counters as in the code above, you can just call collection.count() where ever you need the total number of documents in a collection.</p><p>As concerns logging, you can use <a href="https://cloud.google.com/logging">Cloud Logging</a> to log all the requests. It logs request and request metadata better than the code above. It also allows you to quickly check the amount of requests logged at high scale levels.</p><h3>Firebase Hosting</h3><p><a href="https://firebase.google.com/docs/hosting">Firebase Hosting</a> offers free static-files hosting for up to 10GB. In this URL shortener, you will really never use any of that space. Everything is handled by Cloud Functions. Configure Firebase Hosting with the following steps.</p><p>1. Create a new empty folder inside your url-shortener project folder. Name this new folder public.</p><p>2. Paste the following setup code into the firebase.json file that was created by Firebase when you set up Cloud Functions. It tells Firebase to send all requests to the hosting to the app cloud function that you had deployed.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b06db4fbb3f3e099b2dc9a02e00bdd57/href">https://medium.com/media/b06db4fbb3f3e099b2dc9a02e00bdd57/href</a></iframe><p>3. Deploy</p><p>Run the following command to deploy and set up hosting. It will do different things, and if all is well, it will successfully deploy to some custom link. (&lt;your-project-id&gt;.web.app)</p><pre>firebase deploy --only hosting</pre><p>4. Domain Name</p><p>Go to <a href="https://console.firebase.google.com/project/_/hosting/sites">the hosting dashboard</a> (while on a wide screen). You will see the “Add custom domain” button, click it and follow the flow to set up the short domain for the URL shortener you bought.</p><p>If you didn’t buy a domain, your shortened URL will be longer, but the URL shortener will still work.</p><h3>Congratulations!</h3><p>You have successfully set up a URL shortener with Firebase. To test this, go to Cloud Firestore and add a link. Create a links collection. Leave the id to be auto-generated. Add a short key with value as the shortened part of the URL (after the slash). Then add a second long key with value as the long link to where the user will be redirected to. Save.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Cnx17EtSzMxbPiX60R1bVw.png" /></figure><p>If you visit &lt;your-short-domain&gt;/short or &lt;your-project-id&gt;.web.app/short, your browser should auto redirect you to the value of long.</p><p>If things don’t go right, drop a comment and we will solve it.</p><h3>Frontend</h3><p>Firebase is a backend service and this article was focused on the backend. Like I said at the beginning of the article, the front-end of the URL shortener greatly depends on whether the URL shortener is open to the public or not.</p><p>In fact, if opened to the public, you would have more work to do. You will have to set up <a href="https://firebase.google.com/docs/auth">Firebase Authentication</a> and link it with the frontend. In addition, links will have to have an extra ownerId to ensure that only their creators can edit them. You will also have to edit <a href="https://firebase.google.com/docs/firestore/security/get-started">Firestore Security Rules</a> to ensure security.</p><p>In my case, I built a URL shortener with obum.me. I use it to share links to my articles or profiles. The front-end is some rather private admin dashboard that <a href="https://github.com/obumnwabude/obum.me">I built with Angular Material</a>.</p><h3>Keep Deploying</h3><p>URL shorteners are helpful in many ways. We need them in copies, flyers, social media, etc. to share links in a rather attractive way. You can use existing URL shortener platforms, however, they could have a paid tier with the features you need.</p><p>Building your own URL shortener gives you full control. You can edit the short and long parts of a given URL at any time. Firebase gives you the ability to create a URL shortener with its backend services all for free as we have seen.</p><p>Cheers to the many URL shorteners you will build and deploy!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7754377478e0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/firebase-developers/firebase-url-shortener-7754377478e0">How to build a URL shortener for free with Firebase</a> was originally published in <a href="https://medium.com/firebase-developers">Firebase Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding Serverless Microservices Backend Architecture]]></title>
            <link>https://blog.keepdeploying.com/understanding-serverless-microservices-backend-architecture-67bbbcd7ddd6?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/67bbbcd7ddd6</guid>
            <category><![CDATA[hosting]]></category>
            <category><![CDATA[deployment]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[firebase]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Tue, 08 Feb 2022 23:08:39 GMT</pubDate>
            <atom:updated>2022-02-09T00:24:10.691Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*CI2DI1Zl_O3amEFoAejEQw.png" /></figure><p>Though called “serverless&quot; (without <a href="https://en.wikipedia.org/wiki/Server_(computing)">server</a>), serverless doesn’t mean that there are technically no servers. However, in such backend systems, no engine needs to always listen to requests as it’s common with <a href="https://en.wikipedia.org/wiki/Monolithic_application">monolithic systems</a>. It feels like there is no server and hence <em>serverless.</em></p><p><a href="https://en.wikipedia.org/wiki/Serverless_computing">Serverless</a> is a type of <a href="https://microservices.io/">Microservices</a> architecture where backend routes are run/executed only when needed. Serverless involves the browser doing the majority of the website’s workload.</p><p>The frontend is usually coded with heavy JavaScript so the browser executes new data/<a href="https://en.wikipedia.org/wiki/User_interface">UI (User Interface)</a> rendering and router management without the backend. Such websites are commonly called <a href="https://en.wikipedia.org/wiki/Single-page_application">Single Page Applications (SPAs)</a>. They could be made with plain/vanilla JavaScript or with JavaScript frontend frameworks like <a href="https://reactjs.org/">React</a>, <a href="https://angular.io/">Angular</a>, <a href="https://vuejs.org/">Vue</a>, …</p><h3>Serverless is Fast</h3><p>The frontend app in serverless architecture uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API">modern browser APIs to cause changes in the browser’s routing</a> without reloading the web page. The idea of not reloading the web page is an admirable innovation. When a page is reloaded, the browser will need to re-download a set of similar HTML, CSS, and JavaScript files it had previously downloaded, to re-render the very page. This takes a great deal of time, especially if the user has a poor internet connection.</p><p>So instead of wasting such network request times, the frontend JavaScript can make calls to serverless <a href="https://en.wikipedia.org/wiki/API">API</a>s and update the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model">DOM (Document Object Model)</a> and frontend UI with needed data in the background. The frontend app could have some loading animations to keep the user waiting when the user initiates an interaction that will require backend data (like submitting a form, loading user profile, updating crucial information, …). Consequently, the user spends less time browsing and will have an overall better user experience.</p><p>Also, the backend transfers a lower amount of bytes when the frontend needs it — lower in that HTML and CSS are not re-downloaded, making the browsing a lot faster.</p><p>Browsers are faster in executing JavaScript already present in the user’s device than in sending requests to servers, waiting for the servers to construct the needed files (based on the requested route), downloading the response files, and <em>re-rendering the entire UI</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*pyXRfCtpvlqDWn4idWrmcw.png" /></figure><h3>Static Hosting</h3><p>In entirely standalone/full-blown backend systems, where the backend engine does more work, the server needs to be listening at all times on some particular port to send responses back to requests. The backend is usually never idle because it’s in charge of every bridle and bit of the website.</p><p>In serverless architectures, the frontend is built as some set of static files for the UI. A separate hosting is available for these static files and all assets (like icons and images) necessary for the frontend. <a href="https://pages.github.com/">GitHub Pages</a>, <a href="https://firebase.google.com/docs/hosting">Firebase Hosting</a>, <a href="https://vercel.com/">Vercel</a>, <a href="https://www.netlify.com/">Netlify</a>, <a href="https://pages.cloudflare.com/">Cloudflare Pages</a> … offer such static site hosting.</p><p>Such hosting is usually free or may come at negligible cost. Reasons being: first, it responds with files that don’t change (static files) and it leverages <a href="https://en.wikipedia.org/wiki/Content_delivery_network">CDNs (Content Delivery Networks)</a>. Secondly, it has no complex backend engine like Java, Python, NodeJS, PHP, … to keep listening for routes.</p><p>These files could be hardcoded HTML, CSS, and JavaScript files, or the build files of Frontend JavaScript frameworks. Note that the word static doesn’t mean that the website doesn’t have dynamic features. Static here is used relative to monolithic systems that build the HTML and CSS files in real-time when requested in contrast to already having the files <em>statically</em> available.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*zhrEZAAOHbnqYEANnR-sgg.png" /></figure><h3>Functions</h3><p>Everything that is sent to the Frontend is public and as such, important and private processes should not be exposed to frontend JavaScript. Backend business logic can be run by one-time callable API routes or <em>functions</em>, that process requests and return data when needed. <a href="https://firebase.google.com/products/functions">Firebase Cloud Functions</a>, <a href="https://aws.amazon.com/lambda/">AWS Lambda</a>, <a href="https://azure.microsoft.com/en-us/services/functions/">Azure Functions</a>, <a href="https://vercel.com/docs/concepts/functions/introduction">Vercel Functions</a>, <a href="https://www.netlify.com/products/functions/">Netlify Functions</a>, … offer such serverless deployment. These services are usually free or may cost a penny.</p><p>Serverless architecture is a combination of functions code and static hosting. The hosting provides the browser with everything UI needed for rendering while the functions provide the API or the data that should come from business logic, and be hidden from public consumption.</p><h3>Serverless is cheap</h3><p>The cost of running both deployments (hosting and functions) is negligible, both at small and large-scale deployments. After all, the services involved are free and if they will have some cost, it will be a little charge. So yes, serverless is cheap.</p><p>When compared to the cost of running monolithic backend engines, serverless is cheap and cost-effective. You need to make <a href="https://en.wikipedia.org/wiki/Virtual_machine">Virtual Machines (VMs)</a> always available to effectively run such centralized architecture. Cloud Providers charge per hour or minute usage for VMs to be alive. And as these VMs need to be alive and the charge keeps going high and high. This is financially demanding and when compared to the serverless backend solution, serverless wins.</p><h3>Automatic Deployments</h3><p><a href="https://en.wikipedia.org/wiki/CI/CD">Continuous Deployment/Integration (CI/CD)</a> is smooth with serverless. Most platforms that provide these static hosting and cloud/serverless functions tend to offer some automated deployment method, usually from online hosted version control systems like <a href="https://github.com/">GitHub</a>, <a href="https://about.gitlab.com/">GitLab</a>, <a href="https://bitbucket.org/">BitBucket</a>, … So you technically will always have your site auto-updated as you develop it and your site would rarely need maintenance outbreaks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*0oq-nl-QNTmgn2nat0gUDA.png" /></figure><h3>Scaling is assured</h3><p><a href="https://en.wikipedia.org/wiki/Category:Cloud_computing_providers">Cloud Providers</a> and Tech companies that offer deployment services, for both monolithic and serverless architecture always assure scaling. Given their wide range of data centers and internet availability, these companies generally are cool with being servers to immense global traffic. But then in the technology being served, there might be a scaling problem.</p><p>In themselves, static files and functions will always scale. They will always be capable of serving large bandwidth and many requests because they respond once and that’s all. There is still space for more requests or API calls because on average, the static files host and functions processor tends to be more available, given that they serve files and data once per time and stop there.</p><p>This may not always be the case for the monolithic architecture counterpart. As the engine is always running, many variables are kept at runtime. At times, the programming language involved may also induce considerable overhead on the VM’s CPU. The software can crash within itself causing server errors with 500 status codes even on page load. And all this because of the voluminous traffic of requests coming to your website.</p><p>What could be worse? Maybe your cloud subscription may have a fixed number of CPUs/memory (which might not be enough for your monolithic deployment), or you did not opt-in for the available auto-scaling features.</p><p>Of course, <a href="https://aws.amazon.com/elasticbeanstalk">AWS Elastic Beanstalk</a>, <a href="https://cloud.google.com/appengine">Google App Engine</a>, <a href="https://azure.microsoft.com/en-us/services/app-service/">Azure App Services</a>, … that provide these dynamic backends are optimized for them. But then the scaling options of serverless outweigh those of its counterpart.</p><h3>Same backend for mobile and web</h3><p>In the past, developers made separate backend routes for websites and mobile apps. This is because, the API for the websites usually returned HTML, containing frontend UI and data, while the API for the mobile apps returned <a href="https://en.wikipedia.org/wiki/XML">XML</a>, containing only data. This was inefficient as updates to the backend had to be done in two places.</p><p>The advent of serverless helped this matter. The web frontend app takes care of its UI without depending on the backend engine. As the cloud/serverless functions return only necessary app data, mobile apps can now call the cloud/serverless functions just as the web frontend does. So we end up with the same backend for both mobile apps and websites.</p><p>The returned data tends to be in <a href="https://en.wikipedia.org/wiki/JSON">JSON</a> form or <a href="https://en.wikipedia.org/wiki/Associative_array">Map</a> types. Nowadays, both web frontend and mobile apps can understand JSON. JSON is lighter and more readable than XML.</p><h3><strong>Security</strong></h3><p>Serverless is secure. You will tend to use HTTPS to send user authentication-related requests. The static website hosting is not an open backend engine that can take file uploads nor have a vulnerable SQL database. As such, there is no danger of shell files nor SQL injections. Even if shells get uploaded, they technically can’t be executed as static hosts just don’t permit any such executions. Your files will always be available just as you deployed them.</p><h3>Separation of Concerns</h3><p>You can separate authentication, database, file storage, advertisements, and other backend services across various functions. This enables you to update or make changes to a particular service without updating the entire website. Also, if any of these backend services or cloud/serverless functions break, has a vulnerability, or is maliciously attacked, it is not your entire product deployment that will be in the mud.</p><h3>Server-Side Rendering (SSR) is available</h3><p>The name seems to contradict what we’ve been saying but the details will explain. Search Engines like Google index websites out there and return them ranked in order of quality when users search. The process of improving the website’s contents to ensure it ranks high in search results is known as <a href="https://en.wikipedia.org/wiki/Search_engine_optimization">Search Engine Optimization (SEO)</a>.</p><p>Behind the scenes, search engines have robots that crawl the internet and save all that data. They can read HTML file contents and understand website content. These bots, however, don’t execute JavaScript. Static websites built with plain JavaScript and dynamic websites collaborate with SEO properly. The bots can read them.</p><p>But then, static websites built from JavaScript frameworks need browser execution of JavaScript to display data. As the bots don’t execute JavaScript, they don’t find any website information on those websites built with JavaScript frameworks. This could cause a problem since SEO is important for businesses and for the discoverability of websites and blogs.</p><p>This is where Server-Side Rendering comes in. It&#39;s a feature from JavaScript frameworks that provides the executed build but this time as partial HTML with default data. This pre-rendered HTML can be hosted in cloud/serverless functions. As such when the bots try to visit the website, they first see this data instead and hence saving the day.</p><p>But in the browser, the browser first renders the pre-compiled HTML. Then it will gradually complete the loading of the UI asynchronously in the background, just as the framework instructs. <a href="https://angular.io/guide/universal">Angular SSR</a>, <a href="https://reactjs.org/docs/react-dom-server.html">React DOM Server</a>, <a href="https://vuejs.org/guide/scaling-up/ssr.html">Vue SSR</a>, are examples of Server-Side Rendering.</p><h3>… not always serverless though</h3><p>Having understood serverless and its advantages, it is worth noting that serverless doesn’t work everywhere. Some products just can’t permit serverless architecture like controlling subdomains from the same backend engine or within-company sites not available to the internet.</p><p>Also, static hosting is not a good fit for a website where the majority of its content and UI will frequently be updated. This is because there will be a need to update the frontend and redeploy every time such updates are made. Furthermore, building a <a href="https://en.wikipedia.org/wiki/Content_management_system">CMS (Content Management System)</a> like <a href="https://wordpress.org/">WordPress</a> with monolithic backends, like PHP, is better and easier than with static hosting.</p><p>Of course, we don’t expect the serverless type of microservices architecture to be a one-fits-all backend, but we are sure that the architecture fits its purpose well when used.</p><h3>Keep Deploying</h3><p>Serverless is one of the ways you can deploy applications. You build awesome apps and you should make them live. To the showcasing of your abilities, <em>keep deploying </em>what you’ve built.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=67bbbcd7ddd6" width="1" height="1" alt=""><hr><p><a href="https://blog.keepdeploying.com/understanding-serverless-microservices-backend-architecture-67bbbcd7ddd6">Understanding Serverless Microservices Backend Architecture</a> was originally published in <a href="https://blog.keepdeploying.com/">keepdeploying</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[About 2021 DES & DEV Bootcamp]]></title>
            <link>https://medium.com/dscaefunai/about-2021-des-dev-bootcamp-8853deda34ef?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/8853deda34ef</guid>
            <category><![CDATA[design]]></category>
            <category><![CDATA[ae-funai]]></category>
            <category><![CDATA[bootcamp]]></category>
            <category><![CDATA[gdsc]]></category>
            <category><![CDATA[genesystechhub]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Thu, 03 Feb 2022 09:10:11 GMT</pubDate>
            <atom:updated>2022-02-03T09:10:11.631Z</atom:updated>
            <content:encoded><![CDATA[<p>In February and March, we had successfully hosted <a href="https://medium.com/dscaefunai/2021-solution-challenge-9bd29c242ed7">meetups and workshops for the 2021 Solution Challenge</a> in our <a href="https://gdsc.community.dev/federal-university-ndufu-alike-ikwo-funai/">GDSC</a>. April just began and we needed technical events for the month.</p><p><a href="https://twitter.com/mrprewsh">Precious Ngwube</a>, a member of our core team, came up with the idea of a 4-week Javascript series. On planning in a core team meeting, we sought out to 6 weeks instead. The first four weeks were for learning, while the last two were for community building.</p><p><a href="https://twitter.com/officialemeka_c">Emmanuel Chukwuemeka (Officialemeka)</a> was the lead for design in our GDSC. He suggested the addition of UI/UX design as a learning track to the program. We then named the event: <strong>DES &amp; DEV Bootcamp </strong>(… DESign and DEVeloper).</p><p>At that time, I doubled as both <a href="https://goo.gle/dsc-leads">GDSC lead</a> and <a href="https://genesystechhub.com/">Genesys</a> Campus Club lead in <a href="https://funai.edu.ng/">AE-FUNAI</a>. As a result, I organized the series in honor of these two brands (GDSC and Genesys). Hence, promotions of the event included logos of both GDSC and Genesys.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p8iD-TjCMYnn1BC7TeteVA.png" /></figure><h3><strong>Planning</strong></h3><p>We had meetups every Saturday during the six weeks of the Bootcamp. The first four weeks entailed intensive learning. During this time, participants completed small projects individually. They submitted their projects by each meetup. In the last two weeks, participants built projects in teams, which they showcased on the demo day.</p><p>Planning and promotions took us three weeks, making the Bootcamp start by the third Saturday of April. We first chose <a href="https://goo.gl/maps/5Uj4pH8mrnw9SqpF8">Convocation Arena</a> as the event venue. <a href="https://twitter.com/AmaechiChimere1">Victor Amaechi</a>, <a href="https://twitter.com/u_ephraim">Ephraim Leo</a>, and I designed the flyers we used for promotions across the Bootcamp. <a href="https://linkedin.com/in/jennifer-mbuton-593438193/">Jennifer Mbuton</a>, <a href="https://www.facebook.com/emere.michael.14">Emere Michael</a>, and Victor alternated the job of moderator during each Saturday.</p><p>Michael and Victor took care of promoting the Bootcamp. They broadcasted the event online and offline. All promotions included a central call-to-action: the event registration form at <a href="https://bit.ly/desdev21">https://bit.ly/desdev21</a> (it’s, of course, closed by now). I stopped the form from collecting responses by the third week of learning. We recorded 252 responses at this closure. Unarguably, Michael and Victor’s efforts reaped great fruits.</p><p>Officialemeka was in charge of the design track. He curated content and defined deliverables of participants in design. Specifically, he created <a href="https://notion.so/DESDEV-Bootcamp-UI-UX-Design-9c1699af04e747288f0376cd27bbec8b">an notion.so board</a> to guide the learning of design participants. <a href="https://linkedin.com/in/obinna-ukoha-8260011b/">Obinna Ukoha (Obisco)</a> was also a design mentor. He, together with Officialemeka, helped designers during unblocking sessions on the Saturday meetups.</p><p><a href="https://twitter.com/Eunit99">Emmanuel Uchenna (Eunit)</a> was in charge of the web developer track. <a href="https://twitter.com/Ihechikara2">Ihechikara</a>, <a href="https://www.linkedin.com/in/joshua-avoaja-879503142/">Joshua Akachi</a>, and I were the other web development mentors. We assisted web development learners during the Bootcamp. They carried out and submitted tasks assigned by Eunit each week.</p><p>As students, we couldn’t meet with learners every day. We gave participants resources, to learn from during the week. We assisted them with learning in Whatsapp groups and on Saturday meetups. Each Saturday meetup had its separate agenda. Most of the time, it involved unblocking sessions, solving difficulties in participants’ learning journey. The following are what happened during each meetup.</p><h3><strong>Intro Meetup — Sat. 17th April 2021</strong></h3><p>Check out the pictures of the event at <a href="https://gdsc.community.dev/e/mc6aun/">https://gdsc.community.dev/e/mc6aun/</a></p><p>The day’s program involved an introductory session and then a parallel session. I was on stage during the first session. Initially, I described what communities programs were and spoke on <a href="https://g.co/dev/dsc">GDSC</a> and <a href="https://genesystechhub.com/">Genesys</a>. I then explained the Bootcamp’s structure, what we expected of the participants, and what the participants expected from us. You can check out the following slide.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fembed%3Fid%3D1QqIUBNVflzIf1h-yr8Tg7ctXizOzzxf1rLljypaJWhw%26size%3Dl&amp;display_name=Google+Docs&amp;url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F1QqIUBNVflzIf1h-yr8Tg7ctXizOzzxf1rLljypaJWhw%2Fedit%3Fresourcekey%3D0-I6zG_pZkxA9uyFB3E4yfHg&amp;image=https%3A%2F%2Flh3.googleusercontent.com%2FLOI_BMII4e9ayQDFaMObcqyYWhGNXUDC8iVRuy_rqzTrr-7GuuaAG5pcYO0p_uVz-f9NsSnDOoAqNrP6eTTt0N9vilWFxi3V2tXJ0yNsnrhUnPKbhA%3Dw1200-h630-p&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=google" width="700" height="559" frameborder="0" scrolling="no"><a href="https://medium.com/media/5d812a96f19a15bbadfdb80ff8b8c43f/href">https://medium.com/media/5d812a96f19a15bbadfdb80ff8b8c43f/href</a></iframe><p>We separated the audience in two. On the right were web development participants, while on the left were UI/UX design participants. Eunit and Officialemeka addressed each group, respectively. They introduced the objectives and demands of each learning track. They, together with other mentors, assisted the participants in setting up their development environment (for developers), and <a href="https://figma.com/">Figma</a> accounts for UI/UX designers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NOunFPqCQe3m8luvSUAYiw.jpeg" /><figcaption>Emmanuel Chukwu (Officialemeka) mentoring UI/UX design participants during the parallel session</figcaption></figure><p>We were surprised by the number of attendees. We had up to 80 people in attendance. This Bootcamp was the first in-person/physical event we publicized this much. Our promoters did their job well (Thanks to Michael and Micode). Asides from their presence, the commitment of these was also mind-blowing. They promptly followed up with the event. Those who didn’t have laptops paired up with those who had. We were impressed.</p><p>We had some challenges with the projector and with power. The person who delivered the projector to us forgot to bring the power cable of the projector. Consequently, we couldn’t project the slides during the event. Concerning electricity, I thank <a href="https://www.linkedin.com/in/festus-ali/">Festus Ali</a>, <a href="https://www.linkedin.com/in/gilbert-onyinyechi-7a057b1b7/">Onyinyechi Gilbert</a>, and <a href="https://nfcsaefunai.org/">NFCS</a> for providing us with generators. As of the Bootcamp, the school had not yet powered Convocation Arena, so these generators helped us. We provided fuel for them.</p><p><em>Google Forms</em> was also part of the “challenges” story. We created a Google Form for collecting submissions of individual projects across the weeks. However, Google Forms kept on blocking the form, that the form violated their policies. The fields in the form were: name, email address, phone number, learning track, and submission link. We didn’t know which policy we violated.</p><p>This blocking forced me to create a <a href="https://github.com/gdscaefunai/desdevsubmit">custom submission form</a>. We used it to collect participants’ submissions across the Bootcamp. I built the frontend of the form with HTML, CSS, and vanilla JavaScript (without any helper libraries or frameworks). I then made the form to submit to a <em>Google Sheet</em> we had set for that purpose. That way, we could monitor submissions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yjC1avKdrxy2updyPu1oBA.jpeg" /><figcaption>Community Picture</figcaption></figure><p>At the end of the intro meetup, we took a group picture. The participants went home, and we expected to submit what they had built before the weekend. UI/UX design participants had to replicate the Google Search page. Web development participants were to develop a simple portfolio for themselves.</p><h3><strong>Week 1 — Sat. 24th April 2021</strong></h3><p>Check out the pictures of the event at <a href="https://gdsc.community.dev/e/mwc8k8/">https://gdsc.community.dev/e/mwc8k8/</a></p><p>The day’s program had only one activity: grouped learning. Learners clustered around mentors and followed along on how to carry out the task they were to submit. We had new members. Some persons from the previous week didn’t attend. The meetup was still at Convocation Arena. Festus and Gilbert still helped us with generators. We didn’t bother getting a projector, and we didn’t get any again till the demo day.</p><p>Feedback from learners made us understand that clustered learning was helpful. Participants explained that the grouped learning provided some personalized experience, given the community. We took pictures, posted them on social media, and rewarded those with the highest tweets. By the next week, UI/UX designers were to submit the landing of a restaurant app, while web developers were to submit a countdown timer built with JavaScript.</p><h3>GDSC AE-FUNAI on Twitter: &quot;pic.twitter.com/0NtUoswrOy / Twitter&quot;</h3><p>pic.twitter.com/0NtUoswrOy</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TViKiz_xTFKLE_5952mR_A.jpeg" /><figcaption>Community Picture</figcaption></figure><p>Managing the Bootcamp was demanding to the core team members and me. I got more persons into the core team to help various sectors. We had a core team meeting on Monday evening after week 1. There, we discussed and made plans towards [demo day](get-the-link). We also set the day’s program for week 2’s meetup. I motivated everyone to keep volunteering as balancing academics and sacrificing for the Bootcamp wasn’t easy on both mentors and other non-technical core team members.</p><p>We changed the venue of the meetups to [Faculty of Humanities](get-the-link). Wait why? Well, because of electricity; so that we won’t spend on fuel any longer. After all, other organizations in school had started using Convocation Arena on Saturdays for their events, so <em>we meuved</em>.</p><h3><strong>Week 2 — Sat. 1st May 2021</strong></h3><p>Check out the pictures of the event at <a href="https://gdsc.community.dev/e/m6xbzq/">https://gdsc.community.dev/e/m6xbzq/</a></p><p>This Saturday was a little different. Eunit spoke on “Documenting one’s tech journey”, and got participants to write their stories on Medium. Below are some stories from Community members, please read them:</p><ul><li><a href="https://medium.com/dscaefunai/my-journey-to-design-e7e13ad7f0c6"><em>Chinwendu Onyeani</em>: <strong>My Journey to DESIGN</strong></a></li><li><a href="https://medium.com/dscaefunai/being-a-designer-e5ff515188d3"><em>Faith Olohijere: </em><strong>Being A Designer</strong></a></li><li><a href="https://medium.com/dscaefunai/im-enjoying-this-ui-ux-design-journey-7e6f59384f1d"><em>Echetanna Nwankwo: </em><strong>I’m enjoying this UI/UX design journey …</strong></a></li><li><a href="https://medium.com/dscaefunai/my-tech-journey-daa78afd9299"><em>Vinny Vics: </em><strong>My Tech Journey</strong></a></li><li>…</li></ul><p><a href="https://about.me/anudu">Collins Anudu</a> guided the audience on registering for <a href="https://education.github.com/pack">GitHub Student Developer Pack</a>. <a href="https://about.me/anosikejayson">Jason Anosike</a> guided the audience on registering for the <a href="https://www.pluralsight.com/partners/google/africa/gads-2021">Google Africa Developer Scholarship</a>. We equally continued the day with unblocking sessions and clustering mentors and learners together.</p><p>We modified the submission form to collect everyone’s Medium story. We stopped submissions as participants were not following up on the homework pattern we had designed. We took community pictures and ended the meetup.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tYTfImGhhK7jLIItFhMJDw.jpeg" /><figcaption>Community Picture</figcaption></figure><h3>Week 3 — Sat. 8th May 2021</h3><p>Check out the pictures of the event at <a href="https://gdsc.community.dev/e/mpk3hr/">https://gdsc.community.dev/e/mpk3hr/</a></p><p>We continued week 3’s meetup in one of the halls in the Faculty of Humanities building. We had just one agenda for the day: clustered learning/unblocking sessions. Participants by this time had made many friends in the community. Those to meddle/learn with were no longer an issue. People learned more stuff. We had new participants who joined the program given that their friends had invited them, and they’ve been hearing about DES &amp; DEV on campus.</p><p>I want to especially appreciate our mentors at this point. They technically were so involved in helping the learners that they didn’t have time for themselves. This is because participants met the mentors at other times during the week, not just on Saturday meetups. It’s not easy to volunteer! Thank you mentors of DES &amp; DEV.</p><h3>Obumuneme Nwabude on Twitter: &quot;Mentoring and Organizing is beautiful when the participants are following up and making progress. Today&#39;s workshop (Week 3) involved unblocking sessions and clustered learning.Come for our demo day on May 29th where we&#39;ll showcase what we&#39;ve built 😎#weAreGenesys#GDSCAEFUNAI pic.twitter.com/Lbvn4rvkEj / Twitter&quot;</h3><p>Mentoring and Organizing is beautiful when the participants are following up and making progress. Today&#39;s workshop (Week 3) involved unblocking sessions and clustered learning.Come for our demo day on May 29th where we&#39;ll showcase what we&#39;ve built 😎#weAreGenesys#GDSCAEFUNAI pic.twitter.com/Lbvn4rvkEj</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*tVfDzHVcmzyJDu3j4xbPwQ.jpeg" /><figcaption>Community Picture</figcaption></figure><h3>Week 4 — Sat. 15th May 2021</h3><p>Check out the pictures of the event at <a href="https://gdsc.community.dev/e/mpxpeu/">https://gdsc.community.dev/e/mpxpeu/</a></p><p>The 4th Saturday of learning had come. I started feeling a little fulfilled that the Bootcamp had been up for a month and activities were progressing in peace. No violence. No casualties. Well, such peace was till the Bootcamp ended. Thank God.</p><p>We had reduced attendees this Saturday. Churches that use the halls in Humanities Faculty disturbed our meetup as they had some programs that day. Consequently, we held our meetup for only half as much of the time we normally have our event.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pMaQfdV8rpYSnpVeXHvlOQ.jpeg" /><figcaption>Community Picture</figcaption></figure><p>We were entering the two weeks of community building. We attempted grouping participants into teams of Design and Web Developers. The grouping had flaws because some members were absent. We called for unscheduled meetings during the week to get participants ready for demo day.</p><h3>Unscheduled Meetings &amp; Demo Day Rehearsals</h3><p>On the next two Saturdays, 22nd and 29th May, we (community members) met. But this time, our meetings were not publicized and we were focused on what each team was to build and how they were to demo on the demo day.</p><p>We reviewed the teaming, remixed participants, and analyzed the products been built. Teams prepared and rehearsed their presentations. We aided/motivated participants and took more pictures.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_R7bPcl2P2v7pNhx7raldQ.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DjfpGUQKYkgNnm2eN76khw.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P57n22SBmBseKVM4fCupKg.jpeg" /></figure><p>We had more time (an extra week) for the community projects because we postponed the demo day. We postponed the demo day to Saturday 5th June (instead of 29th May) as a result of Biafran insecurities in the South East (of Nigeria) — interstate movements were not permitted. It gave us more time to rehearse and plan better for the demo day in all ramifications.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2I4v-QeONPfvfiX41A39Pg.jpeg" /><figcaption>DES &amp; DEV Bootcamp Demo Day flyer.</figcaption></figure><h3>Bootcamp DEMO Day 🥺😎🥳🎉🎉</h3><ul><li>There was food and drinks.</li><li><a href="https://twitter.com/UkeJerry">Jerry Uke</a> spoke on “Community Advocacy”.</li><li>We had a mini-hackathon.</li><li>Demoed 6 community projects.</li><li>Awarded prizes.</li><li>There was food and drinks.</li><li>…</li></ul><p>Demo Day is worth its own story.</p><p><a href="https://medium.com/dscaefunai/onboarding-dsc-fd34bbf8520e">From when I became GDSC Lead</a>, I had always wanted to organize a demo day. I believe that showcasing what we build helps in maintaining the community spirit. Seeing a demo day come to life gave me some unforgettable joys 🙈.</p><p>I was happy on Demo Day because, in totality, we had achieved more community members in our school, created more awareness for tech, and had organized a great event. I was overjoyed because the Bootcamp had finally come to an end (couldn’t wait for this) as I could now rest from weekly consecutive meetups.</p><h4>Up Next: Demo Day</h4><p>Please read about the Bootcamp DEMO Day at the following:</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8853deda34ef" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dscaefunai/about-2021-des-dev-bootcamp-8853deda34ef">About 2021 DES &amp; DEV Bootcamp</a> was originally published in <a href="https://medium.com/dscaefunai">gdscaefunai</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2021 Solution Challenge]]></title>
            <link>https://medium.com/dscaefunai/2021-solution-challenge-9bd29c242ed7?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/9bd29c242ed7</guid>
            <category><![CDATA[dsc-ae-funai]]></category>
            <category><![CDATA[cd]]></category>
            <category><![CDATA[solution-challenge]]></category>
            <category><![CDATA[developer-student-club]]></category>
            <category><![CDATA[ae-funai]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Fri, 26 Mar 2021 19:21:11 GMT</pubDate>
            <atom:updated>2021-03-26T19:21:11.625Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jxMfouatTaqoDjqXc6c3bw.jpeg" /></figure><p>School had resumed. Lectures had began. Friends met each other 🥰. Everyone felt the long one-year holiday (due to the <a href="https://en.wikipedia.org/wiki/COVID-19_lockdowns">COVID19</a> lockdown and <a href="https://google.com/search?q=ASUU+Strike+2020">ASUU strike</a>. Google had announced that registrations for the <a href="https://goo.gle/solutionchallenge">2021 Solution Challenge</a> were open 🥳.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*YXHCoRnmTdHCaNJeUj_ilw.jpeg" /></figure><h3>Planning</h3><p>I met with the <a href="https://docs.google.com/presentation/d/1AFHzcNXdbb42RHLbWdE0Vzi5_z2ZP4r94WI8y3yYVh4/">core team</a> and we discussed holding an In-Person/Physical Solution Challenge Info Session. The aim was to sensitize for the 2021 Solution Challenge in our community, drive registrations, and follow up participation.</p><p>For the venue, we chose <a href="https://maps.app.goo.gl/UWCFH8MAuWraPCDn8">Mercury Relaxation Centre</a>. It was a new eatery in <em>perm site</em> and was close to <em>Agric Audit</em>. We chose it because it has a wide space outside (ventilated for the meetup). The event held on Saturday, 20th February.</p><p>We scheduled the event to start at 10 am but began by 1 pm. The lateness was due to some members coming late, … we are sorry 😕.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*VBgGvjtRI7AWVTNLyX1Rrw.jpeg" /></figure><h3>Be Techie</h3><p>We began with <a href="https://twitter.com/pngwube">Precious Ngwube (better known as “Nwoke Nnewi”)</a>, a software developer, speaking on <strong>Be Techie</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*peHixFAA0D1BfeJ9QsS-sw.jpeg" /></figure><p>He told his story about how the first time he got to <a href="https://andela.com/">Andela</a>’s office in <a href="https://en.wikipedia.org/Lagos">Lagos</a>. How that experience exposed him to tech communities and volunteering. He spoke on collaboration, open-source internships, digital marketing, and technical writing: all as activities one can do in the tech space. His speech was the best fit for the first physical meetup we had in our community, since its inception. The content of his talk is highlighted in the slide below.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fembed%3Fid%3D1PGTPIqIQu3nLsK_X5nlsrlosOcd64JscRUpyg8AuvuE%26size%3Dl&amp;display_name=Google+Docs&amp;url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F1PGTPIqIQu3nLsK_X5nlsrlosOcd64JscRUpyg8AuvuE%2Fedit%3Fresourcekey%3D0-VgomzJx9ciguWl_VTYrscQ&amp;image=https%3A%2F%2Flh3.googleusercontent.com%2FFSZNjtkCd_2wQ7MAJ91S9jB1a3-qp-vJT6huBZtWZrAxoaNevS3wKwSsvwJ8Lv5lp9iYfKa0a_PFEg7joV5NlrENt8bazC48Moypl4Wx9fuZmrsvsQ%3Dw1200-h630-p&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=google" width="700" height="559" frameborder="0" scrolling="no"><a href="https://medium.com/media/098a38c5ef66e256b007d24291376992/href">https://medium.com/media/098a38c5ef66e256b007d24291376992/href</a></iframe><p>I can’t exclude the fact that Nwoke Nnewi’s talk was motivated by <a href="https://twitter.com/aniediudo">Aniedi Udo</a> TEDx talk in Port Harcourt, where he spoke on “Bridging the gap between industry and academia”. Thanks, sir Aniedi. You can find Aniedi’s TEDx talk on YouTube at <a href="https://youtu.be/bneP1GxUiUE">https://youtu.be/bneP1GxUiUE</a></p><h3>Barter App Giveaway</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*HFW6qwk_9TT5VLhFl8eCcA.jpeg" /></figure><p><a href="https://twitter.com/AmaechiChimere1">Victor Amaechi</a> was the <a href="https://flutterwave.com/">Flutterwave</a> ambassador for our school, for the <a href="https://barter.flutterwave.com/">Barter App</a>. In light of partnership and community spirit, Victor had a short session, where he explained about Barter, got the community members to install the app, and shared gifts (money) with them through the app.</p><h3>Speaking on Solution Challenge</h3><figure><img alt="Obumuneme Nwabude speaking on 2021 Solution Challenge" src="https://cdn-images-1.medium.com/max/1024/1*VvZLzc6kffDkfFgt9boWNA.jpeg" /></figure><p><a href="https://goo.gle/solutionchallenge">2021’s Solution Challenge</a>, aimed at solving (with software), any or a combination of the <a href="https://sdgs.un.org/goals">17 United Nations Sustainable Development Goals</a> (UNSDGs). It was a global competition where teams with a maximum of 4 members were to submit their solutions. Participants were all <a href="https://g.co/dev/dsc">DSC</a> members, irrespective of the institution the student was attending.</p><p>The competition expected that the solution must have been tested. That it already had an iteration/modification based on user feedback. And that the code be available (for grading) through any cloud-hosted <a href="https://en.wikipedia.org/wiki/Version_control">version control system</a> (like <a href="https://github.com/">GitHub</a>, <a href="https://gitlab.com/">GitLab</a>, or <a href="https://bitbucket.org/">BitBucket</a>).</p><p>The 2021 Solution Challenge involved using at least one or more of the many <a href="https://developers.google.com/products">Google Technologies</a> to build solutions. The submission deadline for the Solution Challenge was 31st March. After that date, submissions were to be graded by Google according to a preset judging system.</p><p>The top 50 teams were to emerge as winners and were to be announced by May. These winners were to continue building and from them, the top 10 finalist teams were to be announced. The best 3 teams were to be selected from these ten as final winners of the 2021 Solution Challenge. Rewards for winners included mentorship from Google, <a href="https://pluralsight.com/">Pluralsight</a> subscription, swags, Chromebooks, etc…</p><p>I spoke on the Solution Challenge and got the attending members to register at <a href="https://goo.gle/solutionchallenge">goo.gle/solutionchallenge</a> (the registration website). In turn, they received their <a href="https://google.dev/">google.dev</a> badge as registrants of the 2021 Solution Challenge. Below is the slide I used, it contains more info about the 2021 Solution Challenge.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fembed%3Fid%3D1ro5aKG5U1nDjUMGA-4K0Kpytg2ZhjSOPKFN60VZx_TI%26size%3Dl&amp;display_name=Google+Docs&amp;url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F1ro5aKG5U1nDjUMGA-4K0Kpytg2ZhjSOPKFN60VZx_TI%2Fedit%3Fusp%3Ddrivesdk&amp;image=https%3A%2F%2Flh6.googleusercontent.com%2FySr0MSTAKKezJRyFl-pByLqGUlLCfkJSqAKzk--Iu5cyqU4l80X_KGmmvevlCvHZp6wbnV4on2kz_sA%3Dw1200-h630-p&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=google" width="700" height="559" frameborder="0" scrolling="no"><a href="https://medium.com/media/0da538a9ca2b64a88e4582222b51c53b/href">https://medium.com/media/0da538a9ca2b64a88e4582222b51c53b/href</a></iframe><p>I like the 2021 Solution Challenge because we were expected to submit at least a <a href="https://en.m.wikipedia.org/wiki/Minimum_viable_product">Minimum Viable Product</a> of whatever solution we chose. With this, we did not need to make the solution perfect. The solution just needed to have the minimum features that will make it usable. As such, one could keep building on the solution even after submitting it.</p><p>I also like the Solution Challenge because it focused on the <a href="https://sdgs.un.org/goals">UNSDGs</a>. So while getting community members to use their technical skills for great rewards, the challenge made them solve our locality problems. After all, that’s what DSC is out for 😉😎.</p><h3>The meetup was all fun.</h3><p>We had varying sessions of fastest finger giveaways for airtime (recharge card pins). We networked with each other and took many pictures. We had a social media competition that involved having the highest reactions on your tweet/post about the event. <a href="https://twitter.com/treasureihedibo">Treasure</a> won the social media competition and went home with her prize.</p><h3>Treasure🎀 on Twitter: &quot;#DeveloperStudentClubs #DSC#DSCAEFUNAI pic.twitter.com/PeWmySPQMA / Twitter&quot;</h3><p>DeveloperStudentClubs #DSC#DSCAEFUNAI pic.twitter.com/PeWmySPQMA</p><p>We also had a <a href="https://kahoot.com/">Kahoot</a> game where members played competitively on simple quiz questions about DSC and the event. The Kahoot entertained everybody. You needed to see the smiles and jumps when people ranked highest per round of the ten questions 😍. Out of the 20 attending members, <a href="https://twitter.com/AmaechiChimere1">Victor</a> came third, <a href="https://twitter.com/pngwube">Precious</a> second, and <a href="https://twitter.com/u_ephraim">Leo</a> first. They all went home with their prices 🥳.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9yUTS0NpedyPgpbKt9scPw.jpeg" /></figure><h3>ecorun.com.ng</h3><p>The meetup drew to an end. We were to go and start building for Solution Challenge. Just before we closed, <a href="https://twitter.com/AvoajaJoshua">Joshua Akachi</a> and <a href="https://twitter.com/Chukwue60327177">Victor Chukwuebuka</a> marveled us all with a demo of a product they have built for the use of everyone. In a nutshell, <a href="https://ecorun.com.ng/"><strong>Ecorun</strong></a> is an e-commerce and social media in one place. Do sign up there today!</p><p><a href="https://ecorun.com.ng/">Ecorun</a></p><figure><img alt="Builders of ecorun.com.ng" src="https://cdn-images-1.medium.com/max/864/1*63WSpO2-oeHN1i6uEZmXrw.jpeg" /><figcaption>Builders of ecorun.com.ng</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6YcEtP-9DBCcfsykrg8DHw.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YTWZ6iEDOQb_lgoP9DX0Zw.jpeg" /></figure><p>Know more about what happened during the 2021 Solution Challenge Meetup at <a href="https://dsc.community.dev/e/m8tk74/">https://dsc.community.dev/e/m8tk74/</a></p><h3>Follow-up Workshop</h3><p>To help the community members in building for the Solution Challenge, we organized a workshop where we told them more about the Solution Challenge. We compared everyone’s skills and teamed up members. We followed them up later towards building for the Solution Challenge. We made sure that only those who consented and were willing to participate were put in teams. The workshop held on Wednesday, 3rd March from 5 to 6.30 pm (after lectures/classes).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D0xXj3N29KvEbJUGucPraw.jpeg" /></figure><p>Know more about what happened during the workshop at <a href="https://dsc.community.dev/e/m872ak/">https://dsc.community.dev/e/m872ak/</a></p><h3>My take-home</h3><p>Leadership is beautiful when people are supporting you. It’s one thing to have a good core team, it’s another to have them helping out in various tasks. Across the events, the core team managed different activities so well. As such, we recorded no failures nor disappointments. Thanks, guys!</p><h3>Next steps</h3><p>There was academic pressure on all of us. Courtesy of the long time we spent at home, the school had to rush activities to catch up with the academic calendar. We would have lost a session if not for the rush. Lectures were compressed. Exams were being written in March (at the peak of Solution Challenge). The follow-up was effective but we didn’t get to complete/submit our solutions, because we could not withstand the academic pressure.</p><p>Notwithstanding, we have started planning towards having a Web Developer Bootcamp on campus in April and May. RSVP (indicate your attendance) for the event at <a href="https://dsc.community.dev/e/mc6aun/">https://dsc.community.dev/e/mc6aun/</a></p><p>#DeveloperStudentClubs #DSC #SolutionChallenge #DSCAEFUNAI #AE-FUNAI</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9bd29c242ed7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dscaefunai/2021-solution-challenge-9bd29c242ed7">2021 Solution Challenge</a> was originally published in <a href="https://medium.com/dscaefunai">gdscaefunai</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My Entire First TEDx Perception]]></title>
            <link>https://obumnwabude.medium.com/my-entire-first-tedx-perception-2f0ec5830702?source=rss-66ac74c8b338------2</link>
            <guid isPermaLink="false">https://medium.com/p/2f0ec5830702</guid>
            <category><![CDATA[tedxabakiliki]]></category>
            <category><![CDATA[tedx]]></category>
            <category><![CDATA[first-tedx]]></category>
            <category><![CDATA[tedxikenegbu]]></category>
            <category><![CDATA[ted]]></category>
            <dc:creator><![CDATA[Obum]]></dc:creator>
            <pubDate>Sat, 12 Dec 2020 17:18:34 GMT</pubDate>
            <atom:updated>2021-08-09T16:03:21.811Z</atom:updated>
            <content:encoded><![CDATA[<h3>How I first perceived TEDx</h3><p>On Tuesday, 1st October (independence day), 2019, we, trainers on <a href="http://konverge.co/">“Google Digital Skills for Africa”</a> for <a href="https://knvgmedia.com/">Konverge Media</a>, in <a href="https://wikipedia.org/wiki/Ebonyi_State">Ebonyi state</a> carried out our own #DigitalNigeria training at <a href="https://google.com/search?q=clevero+hotel+abakaliki">Clevero Hotel, Abakiliki</a>.</p><p><a href="https://www.instagram.com/p/B3FIAfyAPg1/">https://instagram.com/p/B3FIAfyAPg1/</a></p><h3>Missing my would-have-been-first TEDx</h3><p>When we were done with the event, while parking up, boss <a href="https://twitter.com/odibeliworld25">Emeka Odibeli</a> who just led the #DigitalNigeria invited me for <a href="https://www.ted.com/tedx/events/35690">TEDx Abakiliki</a> that held on Saturday, 5th October, just four days later. <a href="https://youtu.be/_OEJ-lrUnhk">He was a speaker at TEDx Abakiliki 2019</a>. I was reluctant and unwilling to attend. This was for two reasons. First, I wanted to rest that weekend. I was previously involved in many Google Digital Skills for Africa trainings and a break in events did me good. Second, I did not know about <a href="https://ted.com/">TED</a> at that time and hence could not make it a priority. Boss Emeka noticed my unwelcoming face and told me: &quot;.. even if you don’t want to attend, attend because I will be speaking that day.&quot; Yet, <em>I fall hands</em>. My first reason above won the battle.</p><p>Come Saturday, TEDx Abakiliki 2019, was live in town while I was in school (I obviously did not attend). I shared the event’s flyers and pictures on my Whatsapp status. <a href="https://twitter.com/AnchimbomNdich2">Anchi</a> wanted more details about the event. He knows what TEDs are and was marveled to see me share/attend one. His joyful surprise turned to dismay and madness when he discovered that I was not attending and was doing nothing in my room. Anchi made me feel how bad it was for me to have missed TEDx Abakiliki 2019. That transport from school to town was just 200 naira and the event was FREE. That many persons, himself inclusive, will want to attend TEDs but can not and there was I.</p><p>Anchi later explained to me what TEDs are. He referred me to <a href="https://ted.com/">ted.com</a> and <a href="https://youtube.com/c/TED">Youtube</a>. I checked out the resources and coupled with Anchi’s reaction, it dawned on me what I had lost. I had missed featuring in many photos I would have appeared in. I had missed taking pictures I would have taken. I had missed the experience. I had missed meeting people. I had missed the story I would have told. Above all, I had missed the first-hand ideas shared by that day’s speakers. I from then resolved to attend TED/TEDx events whenever/wherever I can.</p><h3>Getting to know TEDx Ikenegbu</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZuCCShK1AbOQfk7DgoT0Pg.jpeg" /><figcaption>TEDx Ikenegbu’s flyer</figcaption></figure><p>Early March this year, someone shared <a href="https://tedxikenegbu.org/">TEDx Ikenegbu</a>’s flyer in a Whatsapp group that I am a member of. I visited the <a href="https://tedxikenegbu.org/">tedxikenegbu.org</a> site as on the flyer and visited all its social media links from the site. (The online presence of entities make it easy to know about them). What attracted me and made me hungry to attend this TEDx was a particular speaker: <a href="https://twitter.com/sirjudennam">Sir Jude Nnam</a>. He is an <em>ancestor</em> of music (He is just so good at everything about music) and is someone I will like to listen to more often. The event’s ticket cost 3000 naira and the event was to hold on Saturday, 4th April.</p><p>I do not live in <a href="https://wikipedia.org/wiki/Owerri">Owerri</a> where one could find physical adverts for the event. The event site and online info were not enough for me. I wanted to feel the TEDx vibe, wanted more information, and wanted to be receiving updates on the event. This want made me contact and keep in touch with <a href="https://twitter.com/mundus01">Martin Uwakwe</a>, <a href="https://www.ted.com/participate/organize-a-local-tedx-event/apply-for-a-tedx-license">licensee</a> of TEDx Ikenegbu. Through him, I got to know about the shutdown of the event, due to COVID19. I had not bought my ticket by then.</p><h3>Post COVID19</h3><p>The COVID19 pandemic later eased out and still through Martin, around November, I got to know about TEDx Ikenegbu’s new date. The TEDx event was now to hold on Saturday, 12th December. All the speakers were retained. I purchased my ticket three weeks earlier and could not wait for the event to hold. I had high expectations, I mean all the stories Anchi had told me and all the recordings on Youtube. Not just that, it was the first TEDx in Owerri. I was expecting Sir Jude Nnam.</p><h3>Preparing to attend</h3><p>In the spirit of sharing ideas, I got in touch with my friends that live in Owerri to inform them about the event, and possibly get them to attend. Only two persons out of the twenty I reached out to, knew about the TEDx event. Others were like me when Anchi first shouted at me 🤣. In other words, they did not know what was TED. I was their Anchi and taught them about it. However, none of my friends in Owerri attended 💔. Maybe because it was not a priority to them too, and the event came with a ticket cost, unlike Abakiliki’s that was free. I basically attended alone.</p><figure><img alt="Timetable of TEDx Ikenegbu 2020" src="https://cdn-images-1.medium.com/max/760/1*u1_s6Gl5v3zc52RkKzECVw.jpeg" /><figcaption>Timetable of TEDx Ikenegbu 2020</figcaption></figure><p>I was living in <a href="https://wikipedia.org/wiki/Awka">Awka</a> at that time, and it was two hours journey to Owerri. The event was to start at 9 am and end by 3.30 pm. I had high expectations and did not want to come late for the TEDx event or miss any beginning parts. I sought means to sleep in Owerri on Friday, to be sure I was at <a href="https://google.com/search?q=benchmark+hotels+owerri">Benchmark hotels</a> (event’s venue) on time, but they did not work. I ended up coming to Owerri very early from Awka that Saturday morning. I had prepared so well the previous night that by 5.30 am, on Saturday, I had left home for the park.</p><h3>I was disappointed</h3><p>You needed to see me rush, thinking that the event must have began 🤦‍♂️. I arrived at the venue exactly at 8.55 am and was shocked that many things were still being prepared. The hall was empty, attendees and speakers had not yet come. The organizers were busy putting things in order. Lighting, sound, and the hall’s decoration were being set. Nobody stopped/welcomed me at the entrance at this time 💔. I was angry.</p><p>I greeted Martin and tried having a chat but could not because he was busy. Many were reaching out to him for important things. I angrily left the hotel and strolled its streets. Later found a restaurant and had something to eat. I delayed outside to try to calm down but it did not work. I returned to the venue an hour later.</p><p>This time around, I was stopped by the ushers: they wanted to register me. The registration process entailed identifying that one had purchased the event’s ticket and giving the attendee event “properties”. Given that I registered online, I could not provide a physical ticket. The ushers searched my name on the laptop they had and approved my registration. I was given a jotter, pen, and an attendee tag. The tag had my name printed on it.</p><p>My name was wrongly spelled (making me angrier). I was asked to wait and was kept standing without being attended to for a long time (increasing the anger) 💔💔💔. I later entered and got a seat. This first bad impression of TEDx killed the vibe I had. It made me not to make new friends there, besides the ones I knew/invited did not come.</p><p>Parts of the hall’s setup were complete. The hall was a tenth filled with attendees and Sir Jude Nnam had come. I chatted up Anchi and <a href="https://twitter.com/uokezie1">Okezie</a> and shared my dismay with them. They consoled me and explained that the event’s speakers will meet my expectations. But <em>omo</em>, the music in the hall was so loud, I could not bear it, I had to walk out again. I snapped pictures on the TEDx Ikenegbu board.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/810/1*Ep6vttX08qUtAWKbbeRekw.jpeg" /><figcaption>Obumuneme Nwabude attending TEDx Ikenegbu</figcaption></figure><p>I stayed outside till the event began around 11 am (out of anger). I then imagined if I had slept in Owerri to meet up with time just to face such disappointments, the anger would have been more than me. Besides, I had sacrificed other endeavors to attend this event and the event started late. Another source of grief was the fact that, if the event began late, it will end late. I was to go back to Awka that very Saturday and will not like to go home when places are dark. I got better consolation after speaking with <strong>Prisca Naya</strong>. She was among the team organizing and she really calmed me down. She corrected my name on my name tag 🤩.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yWbIAHZEQsCtvV4xWqa9iQ.jpeg" /><figcaption>Obumuneme Nwabude TEDx Ikenegbu name tag</figcaption></figure><h3>TEDx Ikenegbu 2020 was awesome</h3><p><a href="https://facebook.com/uzone247">Uzodinma Akalonu</a>, the MC, did his job well. He kept using the TEDx event’s theme: “A New World”, in almost all he said. I liked the way he introduced and appreciated each speaker. There was this <em>sticking-to-the-theme</em> (“a new world”) by all speakers, irrespective of the diversity of discipline from which they spoke. Each speaker was being awarded a TEDx Ikenegbu branded pillow after their speech.</p><p>It was unfortunate that there was a power outage at some point. It lasted for three minutes and was in between a speaker’s speech. We had a break after seven speakers spoke. Water was shared while food came later. I sat on stage while it was clear, and was snapped there by <strong>Miriam</strong> (whom I sat next to). Thank you, Miriam! I also had a little convo with Sir Jude Nnam, during this break, and believe me, that felt good 😍.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UAWOSlJyVwIxmmVDRW09hQ.jpeg" /><figcaption>Obumuneme Nwabude attending TEDx Ikenegbu</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/810/1*fjgkRu6PuqWGKJwh29d05Q.jpeg" /><figcaption>Obumuneme Nwabude with Sir Jude Nnam</figcaption></figure><p>Still, during the break, people wrote and signed on the big backdrop, outside the hall, set aside for that purpose. They wrote about what a new world meant to them. They equally took pictures there. I got a marker and expressed myself. I wrote: “NEXT TIME, START EARLY”. 🙄</p><p>The TEDx event was good. The organizers and team members put in much effort to make the event work. Notwithstanding the late-starting, every other protocol was properly set (except the food vendor that delivered late too). Listening to the speakers appeased me as Okezie mentioned that it would. Just that how I first felt is part of my story. It seems only I felt that way. Maybe because I might have over expected. Besides, this was the first TEDx Ikenegbu. Dear organizers and team members, you did a great job. Kudos!</p><p>After the break, there was a <a href="https://kahoot.com/">Kahoot</a> game on questions about <a href="https://wikipedia.org/wiki/Imo_State">Imo state</a> and details on the TEDx event we were attending. I took part and was nowhere near to the leaderboard’s top. I sat at the hall’s left periphery and could not see the slide which was projected to the left side of the stage. It made no difference. After all, I do not know much about Imo state. About sixty persons participated and it was really competitive. The winner, <strong>Emma</strong> was awarded a TEDx Ikenegbu branded pillow.</p><h3>The Speakers</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B9bh5N045rRCB68_Aay-eQ.jpeg" /><figcaption>TEDx Ikenegbu’s flyer with speakers</figcaption></figure><p>This is the best part of the story. It is why we all came. Great speakers from here and there. The claps after each were much. The <a href="https://tedxikenegbu.org/blog/speaker/chika-jones/">first speaker, Chika Jones</a> did a good opening with his poem performances. In fact, below is each speaker, and an <strong>attempt</strong> to mention in one sentence what I understood from each. They said a lot of unique things and told a lot of stories, such that, no summary will do justice to what they shared.</p><p>1. <em>Performance poet</em>, <a href="https://tedxikenegbu.org/blog/speaker/chika-jones/"><strong>Chika Jones</strong></a>: From now on, let our conversations be with purpose.<br>2. <em>Lawyer</em>, <a href="https://tedxikenegbu.org/blog/speaker/victoria-ibezim-ohaeri/"><strong>Victoria Ibezim</strong></a>: Empower the woman, and she will build your nation.<br>3. <em>Filmmaker</em>, <a href="https://tedxikenegbu.org/blog/speaker/ekene-som-mekwunye/"><strong>Ekene Mekwunye</strong></a>: Let us, in our films and stories, show a nation we want.<br>4. <em>Life coach</em>, <a href="https://tedxikenegbu.org/blog/speaker/ritchie-felix/"><strong>Ritchie Felix</strong></a>: Prepare yourself for a more digital and connected economy.<br>5. <em>Gynecologist</em>, <a href="https://tedxikenegbu.org/blog/speaker/dr-chito-nwana/"><strong>Chito Nwana</strong></a>: Speak up, share your story, and embrace mental health.<br>6. <em>Breast cancer survivor</em>, <a href="https://wikipedia.org/wiki/Betty_Anyanwu-Akeredolu"><strong>Betty Anyanwu-Akerodolu</strong></a>: With the right thing done at the right time, breast cancer will not kill its patients.<br>7. <em>Writer</em>, <a href="https://linkedin.com/in/ebubechukwu-ibecheozor-758435140"><strong>Ebubechukwu Ibecheozor</strong></a>: Fear, when harnessed properly, can make us do the right thing.<br>(after the break and Kahoot game)<br>8. <em>Financial coach</em>, <strong>Hector Okechukwu</strong>: Long-term vision, business education, social capital, and better cultures will help reduce the number of failing businesses in Africa.<br>9. <em>Clinical prosthetist</em>, <a href="https://twitter.com/anukamvictor"><strong>Victor Anukam</strong></a>: When next you see a <strong>differently-abled</strong> (instead of disabled) person, think about their capabilities.<br>10. <em>Software Developer</em>, <a href="https://tedxikenegbu.org/blog/speaker/george-iwu/"><strong>George Iwu</strong></a>: Given that digital devices and artificial intelligence will soon take over most work, harness those human skills that will be in need in the nearest future.<br>11. <em>Music “Ancestor”</em>, <a href="https://twitter.com/sirjudennam"><strong>Sir Jude Nnam</strong></a>: All things shall perish under the sky, music alone shall live never to die.<br>12. <em>Gender equality activist</em>, <a href="https://twitter.com/Solomon_Buchi"><strong>Solomon Buchi</strong></a>: Dear patriarchy, rest in peace.</p><p>Forgive me!!! It is improper to have reduced fifteen-minutes mind-blowing talks to one sentence each. The talks were very rich. Besides, I have been writing from my own perspective. To make up for my offense, I wrote in greater detail about TEDx Ikenegbu, together with the videos of each speaker. There, I told the story of the event and not my own story. Please read it at <a href="https://obumnwabude.medium.com/tedx-ikenegbu-6a40d7ec7c02">https://obumnwabude.medium.com/tedx-ikenegbu-6a40d7ec7c02</a></p><h3>Sir Jude Nnam</h3><p>He is commonly called “ancestor”. He has great prowess in anything music. Remember, he is the speaker I wanted to listen to. I gave more focus during his time on stage than during others. It was good as he was last but one speaker. It made me pay attention to those who spoke before him. Otherwise, I might have lost focus in the TEDx event, given that my desire would have been satiated much earlier.</p><p>He was on stage with a piano and he sang thrice. He started his session by singing: “All things shall perish under the sky, music alone live never to die”. His talk was on: “Music composition in 10 minutes.” He explained a fact. That we in Africa, we all actively participate in music. That everybody has music potential. He also taught that we can use music to fight stress. There are many other things he shared.</p><blockquote>“Be open to every genre of music”</blockquote><blockquote>“Be able to play an instrument”</blockquote><blockquote>“Write music from your heart”</blockquote><blockquote>“Music theory will help you write down the notes you compose”</blockquote><blockquote>“Find a group where you can express your music”</blockquote><blockquote>“PRACTICE! PRACTICE!! PRACTICE!!!”</blockquote><p>He engaged the audience in singing and harmonizing a very short piece. He later told his story of how he composed “My Story” on a sickbed in 2003 with a hanging leg. The song is at <a href="https://www.youtu.be/aLhMgFL14nI">https://www.youtu.be/aLhMgFL14nI</a>. Thanks, sir for the inspiration. What I took home was I need to give music time, which is something I would do. After all, for a new world, it is clear that we will all need music 😎. When leaving for my way back to Awka, he himself bade me bye 🥺.</p><h3><strong>What really is TED?</strong></h3><p><a href="https://ted.com/">TED is from Technology Entertainment and Design</a>. Today, it covers almost all topics. It is devoted to spreading ideas. TEDx events are <strong>independently organized</strong>. TED events that help share ideas in communities around the world. I missed <a href="https://www.ted.com/tedx/events/35690">TEDx Abakiliki in 2019</a>, and I’m delighted to have attended my first TEDx event: <a href="https://tedxikenegbu.org/">TEDx Ikenegbu 2020</a>, which has done justice to the spirit of TED and sharing ideas. I remain ever hungry to attend TED/TEDx events whenever/wherever I can.</p><p>Thank you!</p><p>Know exactly what happened during TEDx Ikenegbu: A New World at <a href="https://obumnwabude.medium.com/tedx-ikenegbu-6a40d7ec7c02">https://obumnwabude.medium.com/tedx-ikenegbu-6a40d7ec7c02</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2f0ec5830702" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>