[{"content":"After the privacy related changes to WhatsApp, a lot of people are fleeing this popular chat application for other more privacy-aware choices. This development, made me consider Signal, again, as a chat service, but my decision to install it and try to move my contacts was not a privacy related one.\nIn Greece where me and most of my contacts live, WhatsApp isn\u0026rsquo;t as popular as Facebook\u0026rsquo;s Messenger, an app that lacks end-to-end encryption and as a result is considerably worse privacy-wise.\nEnd-to-end encryption is a technical measure, implemented to make eavesdropping on your chat conversations impossible. It encrypts your messages from the moment they leave your device, till the moment they are received by your friend\u0026rsquo;s phone. In this way, nobody can use your conversations to sell your information to advertisers and no rogue government can read your messages to enforce laws. But for the average person living in a western democracy, who still (knowingly or unknowingly) uses Facebook and other ad-supported, privacy-invasive social networks, this feature is not as important as it seems.\nMessenger, for me, is the best chat app in the world, because it has the only feature I need from a chat app. My contacts!\nIf I want to contact somebody, I know that he will be there receiving my messages. This feature is enough for me to give up the promise of enhanced privacy.\nIt\u0026rsquo;s something different that should be more important.\nBut why did I install Signal? Although we feel that we can choose our chat service, the direct opposite happens. The best feature mainstream chat apps have, our contacts\u0026rsquo; usage of them, is the reason we are hostages to these applications. If we want to move to another chat application, we need to persuade all of our contacts to move. This is the great power Facebook is using irresponsibly.\nThis power is what takes away from us the right to choose who and why is using our data.\nBut more importantly, this power hinders innovation and progress. If a better chat application is released, then most people wouldn\u0026rsquo;t care to use it. That happens because it wouldn\u0026rsquo;t have the most important feature, their friends. Eventually, if this application is really better than the current offerings, the best features would be copied by the mainstream apps and the vicious cycle will continue.\nTrying to go against this great power, I installed Signal. I chose Signal because it is in a way the direct opposite of what Facebook\u0026rsquo;s apps are. It is open-source, people can view and contribute to its code and it\u0026rsquo;s backed by a non-profit. It\u0026rsquo;s an app that implements changes based on what its community wants, it\u0026rsquo;s an app that can care about your opinion and your choices.\nI immediately invited my closest contacts to join and\u0026hellip; people joined, then agreed that this app is nice and afterwards nobody contacted me a second time through Signal. Everybody still used Messenger.\nAt the end, I completely understood their choice, or better, their lack of choice. If they wanted to keep in touch with their other friends, they still needed to use Messenger, and who wants to complicate their lives with multiple apps?\nAt the end, what can we do? When the internet started proliferating, choice was one of its biggest advantages. We could choose the software we use, we could choose the services we access. But now, the massive social networks, with the power they have accumulated by our participation, have narrowed down our choices to the bare minimum.\nThe only way to change the situation is to force those social networks to use their powers more responsibly. Europe\u0026rsquo;s GDPR law with its right to data portability is a step in the right direction, but it\u0026rsquo;s not enough. I believe we need more to regain our right to choose what services we use.\nAfter portability, we need to discuss interoperability, which in this case is the ability to contact people who are using different chat applications than the one you are using. The way email and SMS already work.\nAlthough this is still far-fetched, we need to finally start addressing it.\nFurther reads What is ActivityPub? Signal: What is it and why is everyone talking about it? How do you feel about the right to choice? I would love to discuss more about it in the comments.\n","permalink":"https://chrispanag.com/posts/i-didnt-install-signal-for-privacy/","summary":"\u003cp\u003eAfter the privacy related changes to WhatsApp, a lot of people are fleeing this popular chat application for other more privacy-aware choices. This development, made me consider Signal, again, as a chat service, but my decision to install it and try to move my contacts was \u003cstrong\u003enot a privacy related one\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eIn Greece where me and most of my contacts live, WhatsApp isn\u0026rsquo;t as popular as Facebook\u0026rsquo;s Messenger, an app that lacks end-to-end encryption and as a result is considerably worse privacy-wise.\u003c/p\u003e","title":"I didn't install Signal for privacy"},{"content":"You want to create a blog to start sharing some thoughts, ideas or projects. But when you want to post something, you don\u0026rsquo;t want to think a lot about the underlying software, and you definitely don\u0026rsquo;t want to mess with lots of settings, buttons, designs and properties.\nIf you are a major procrastinator like me, every little configurable setting or element, can be a reason to \u0026ldquo;think it over again\u0026rdquo;, \u0026ldquo;start writing some other day\u0026rdquo;, \u0026ldquo;need more time\u0026rdquo; and more excuses for not finishing or even starting your blog post.\nThat\u0026rsquo;s why, when I decided to start posting on a blog, I needed something that integrates well with my current workflow.\nAs a developer, I use git/GitHub for storing and managing my projects and I write almost everything I create on VSCode. I\u0026rsquo;m also fluent with Markdown, which I use for most of my projects to document and write directions. For me, visual editors, such as the one used by WordPress, are just clutter that gets in the way of writing my thoughts.\nSo what I needed, was an easy-to-use static site generator that takes Markdown and spits out HTML pages. One of those was hugo, and after some fiddling around with it, I found out that this is the one. This, coupled with a nice theme lead to the creation of my website.\nNow, when I want to write a new post on my blog, I fire up VSCode, open the site\u0026rsquo;s repository, create a file and just start writing, as I do with every other project. When the post is finished, I just commit and push on GitHub and my website is automatically built and deployed by Netlify. No fiddling with visual editors, no complicated options and properties. I just focus on the content.\nSome additional benefits The website is ranked 100/100 on Google\u0026rsquo;s Page Speed Insights. This means my website is treated more favorably by search engines.\nNo upkeep: security checks and server maintenance. I don\u0026rsquo;t need to update anything, or configure anything other than what matters the most, my website.\nConclusion/Disclaimer Having said these, I don\u0026rsquo;t object that for some people/organizations, WordPress is the right choice. As a matter of fact, I have suggested WordPress to many clients, especially organizations that need the extra features, extensibility and customizations. But for a developer, like me, who just wants to blog and share his views and ideas, I believe a static website is the best way to go. More importantly it\u0026rsquo;s the best way to persist writing, because it integrates so well with a developer\u0026rsquo;s day-to-day workflow, reducing the friction to create content.\n","permalink":"https://chrispanag.com/posts/why-i-didnt-use-wordpress-for-my-blog/","summary":"\u003cp\u003eYou want to create a blog to start sharing some thoughts, ideas or projects. But when you want to post something, you don\u0026rsquo;t want to think a lot about the underlying software, and you definitely don\u0026rsquo;t want to mess with lots of settings, buttons, designs and properties.\u003c/p\u003e\n\u003cp\u003eIf you are a major procrastinator like me, every little configurable setting or element, can be a reason to \u0026ldquo;think it over again\u0026rdquo;, \u0026ldquo;start writing some other day\u0026rdquo;, \u0026ldquo;need more time\u0026rdquo; and more excuses for not finishing or even starting your blog post.\u003c/p\u003e","title":"Why I didn't use WordPress for my blog"},{"content":" TLDR; I created a small npm package that acts as a wrapper around node-fetch, and returns the same promise for the same request, until it resolves. You can visit the repo of this package here. Below, I explain my motivation, and how I tackled the issue.\nSo here\u0026rsquo;s the scenario:\nYou have a system that interfaces with a really slow third-party API. User Bob, needs some data, so your system performs a request to the third-party API, and waits for a response. In the meantime, user Alice needs the same data and the system performs the same request to the API on behalf of her. Both users are now waiting for two requests that the only difference they have, is the execution time.\nIf a request to this API has an average response time of 1 second, both users will wait 1 second. Also, you would need to occupy resources in your system and the third-party API for more than 1 second, and for 2 seconds at most!\nThe solution What if you could have both users, Bob and Alice, wait for the same request? Then, although Bob will still wait for the request for 1 second, Alice will use Bob\u0026rsquo;s request, and wait less time for the response.\nTo achieve that, we\u0026rsquo;ll need a promise-cache subsystem. This subsystem will consist of a data structure to store our requests\u0026rsquo; promises and of a way to retrieve them/delete them when they are not needed.\nThe data structure We need a data structure to store our promises inside. This data structure needs to be able to store and retrieve a new promise in one operation (O(1)). So, the best choice would be a key/value store. JavaScript offers two such structures, the basic object and the Map() instance. The most preferable data structure for our use-case among the two is the Map().\nSo, let\u0026rsquo;s create it:\nconst promiseCache: Map\u0026lt;string, Promise\u0026lt;Response\u0026gt;\u0026gt; = new Map(); The retrieval/storage Now, let\u0026rsquo;s create a function that wraps around the request function and retrieves the same promise for the same request, if it exists. If it doesn\u0026rsquo;t, it performs a new request and stores it in the cache.\nfunction memoizedRequest(url: string) { const key = url; if (promiseCache.has(key)) { return promiseCache.get(key); } const promise = request(url); promiseCache.set(key, promise); return promise; } With this, we have achieved the basic function of our promise-cache subsystem. When our system performs a request using the memoizedRequest function, and the request has already happened, it returns the same promise.\nBut, we haven\u0026rsquo;t yet implemented the mechanism for the deletion of the promise from the cache when the promise resolves (when the request returns results)\nThe deletion - cache invalidation For this, we\u0026rsquo;ll create a function that awaits for the promise to resolve and then delete the promise from the cache.\nasync function promiseInvalidator(key: string, promise: Promise\u0026lt;any\u0026gt;) { await promise; promiseCache.delete(key); return promise; } And then we\u0026rsquo;ll modify our memoizedRequest function to include this invalidation function:\nfunction memoizedRequest(url: string) { const key = url; if (promiseCache.has(key)) { return promiseCache.get(key); } const promise = promiseInvalidator(key, request(url)); promiseCache.set(key, promise); return promise; } But what happens with more complicated requests? Not all requests can be differentiated by just the url they are performed on. There are many other parameters that make a request different (eg: headers, body etc).\nFor that, we\u0026rsquo;ll need to refine our promise-cache\u0026rsquo;s key and add an options object on our function:\nfunction memoizedRequest(url: string, options: RequestOptions) { const key = url + JSON.stringify(options); if (promiseCache.has(key)) { return promiseCache.get(key); } const promise = promiseInvalidator(key, request(url)); promiseCache.set(key, promise); return promise; } Now, only the requests that use exactly the same options will return the same promise until they resolve.\nWith this, we implemented all the basic functionality of our package. But we haven\u0026rsquo;t taken into account the possibility of a request failure. Let\u0026rsquo;s add this on our code, by making the promiseInvalidator function to always remove the promise from the cache either when it resolves, or when it rejects.\nasync function promiseInvalidator(key: string, promise: Promise\u0026lt;any\u0026gt;) { try { await promise; } finally { promiseCache.delete(key); } return promise; } More improvements This implementation has a small drawback, that can prove serious on a production system. All the requests\u0026rsquo; data, are stored within the key of our data store, highly increasing the memory requirements of our application, especially when our requests contain a lot of data. The solution to this is to use a hash function on our key, to assign a unique value to each different request, without needing to include all the actual data of the request.\nconst key = hasher(url + JSON.stringify(options)); Caveats This solution, isn\u0026rsquo;t applicable to any situation. To use this solution, you need to ensure that the API you are interfacing with, is not providing different responses for two different requests in the amount of time it will take for those requests to resolve.\nThe package If you don\u0026rsquo;t want to code this for yourself, I created a simple npm package that does all of the above, as a wrapper to node-fetch (or any other fetch-like function you choose).\nimport memoizedNodeFetch from \u0026#39;memoized-node-fetch\u0026#39;; const fetch = memoizedNodeFetch(); (async () =\u0026gt; { const fetch1 = fetch(\u0026#39;https://jsonplaceholder.typicode.com/todos/1\u0026#39;); const fetch2 = fetch(\u0026#39;https://jsonplaceholder.typicode.com/todos/1\u0026#39;); // This should return true because both requests return the same promise. console.log(fetch1 === fetch2); const res1 = await fetch1; const res2 = await fetch2; console.log(await res1.json()); console.log(await res2.json()); })(); You can see all of the above work, on its GitHub repository here:\nhttps://github.com/chrispanag/memoized-node-fetch\nPS. 1: Although this can be used in the front-end, I can\u0026rsquo;t find a very useful use-case for it, especially when you have other packages such as react-query/swr, that although they perform a different function than the above, can sometimes remove the need for it.\nPS. 2: Special thanks to the other two contributors of this repository (ferrybig and Bonjur) for their invaluable input and suggestions!\n","permalink":"https://chrispanag.com/posts/improve-speed-interfacing-with-slow-api/","summary":"\u003cblockquote\u003e\n\u003cp\u003eTLDR; I created a small npm package that acts as a wrapper around node-fetch, and returns the same promise for the same request, until it resolves. You can visit the repo of this package \u003ca href=\"https://github.com/chrispanag/memoized-node-fetch\"\u003ehere\u003c/a\u003e. Below, I explain my motivation, and how I tackled the issue.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003cstrong\u003eSo here\u0026rsquo;s the scenario:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eYou have a system that interfaces with a really slow third-party API. User Bob, needs some data, so your system performs a request to the third-party API, and waits for a response. In the meantime, user Alice needs the same data and the system performs the same request to the API on behalf of her. Both users are now waiting for two requests that the only difference they have, is the execution time.\u003c/p\u003e","title":"A trick to improve speed when you are interfacing with a slow API"},{"content":"I\u0026rsquo;m a builder. I\u0026rsquo;ve spent my career building products and the systems behind them: platform infrastructure at scale at BeReal, and before that web3 products and chatbot platforms used by millions.\nNow I own the technical direction at Prelude, where I lead the engineering team and the architecture behind its phone verification and anti-fraud systems. I\u0026rsquo;m responsible for what we ship and how it holds up at scale, and I still write code alongside the team.\nI work mostly with Go and NodeJS. Originally from Athens, Greece, I now live in Paris, France. In my free time I play Greek rembetiko music on the guitar and administer the rembetiko.gr community.\nFrom time to time I share thoughts, projects and ideas on my blog.\nTimeline Sep 2014 I was admitted to the School of Electrical \u0026amp; Computer Engineering of the National Technical University of Athens.\nMar 2016 Built \u0026ldquo;When is my bus coming\u0026rdquo; (\u0026ldquo;Πότε έρχεται το λεωφορείο μου\u0026rdquo;), a Facebook chatbot that informed users of the estimated time of arrival of buses in Athens, Greece. At its peak, it was serving over 20,000 weekly users. (press references: iEfimerida, noupou.gr)\nSep 2017 - Jul 2021 Working at Enneas, a software house based in Athens, Greece. There, I designed their chatbot infrastructure from scratch and worked with large Greek organizations such as Cosmote and OPAP to ship it into production.\nAug 2021 - Feb 2023 Working at Capsule Social as Lead Backend Engineer. We released Blogchain, a decentralized, censorship-resistant blogging platform where articles are stored on IPFS. (press references: TechCrunch 2021, NearWeek, ProductHunt)\nFeb 2022 Moved to Paris, France.\nMar 2023 Built Paroles.gr, a web app built on NestJS, React, PostgreSQL and TypeScript that allows users to easily search the lyrics of Greek songs.\nMar 2023 - Aug 2024 Working at BeReal as Senior Platform Engineer. I designed and built backend services in Go and NodeJS that powered BeReal\u0026rsquo;s core features, including the relationship graph that handled hundreds of millions of relationships during the daily 2-minute notification, and the post service that handled millions of reads and writes per second. Several of these services ran at over 1M req/s, and I cut their running cost. I also took part in the on-call rotation as an incident commander during major incidents.\nSep 2024 Started working at Prelude as a Senior Software Engineer. I designed and built Prelude\u0026rsquo;s Auth product and its v2 API, launched its email verification system, worked on anti-fraud against SMS pumping, and improved OTP phone verification conversion rates.\nSep 2025 Promoted to Engineering Manager at Prelude. I lead the team behind its verification and anti-fraud products and own their technical direction, while staying hands-on with the code.\n","permalink":"https://chrispanag.com/about/","summary":"About","title":"About"},{"content":"Me on the web Places I\u0026rsquo;ve appeared online and in the press.\nMedia coverage NEARCON 2022 @ Lisbon, Portugal - Representing Capsule Social Not Your Parent\u0026rsquo;s Facebook: Building Social Media in Web3\nA panel discussion on the next generation of social media built on decentralized technologies, and how we applied those ideas on Blogchain.\nReady Layer One: Blogchain decentralized blogging platform built on NEAR for web3 | podcast\nWith my colleague Jack Dishman, I discussed Blogchain and how decentralization can address the problems in traditional social media.\nENNEAS Μια Ελληνική εταιρεία που παίζει δυνατά στο VR/AR και τα chatbots\nJoint interview with my former colleague George Patseas, on how we built a sustainable software agency in Athens, Greece.\n\u0026ldquo;When is my bus coming\u0026rdquo; chatbot coverage (in Greek) «Πότε έρχεται το λεωφορείο;»: 22χρονος έφτιαξε εφαρμογή που θα λατρέψετε (iEfimerida.gr)\nInterview on the Greek news site iEfimerida.gr for my Facebook Chatbot \u0026ldquo;When is my bus coming\u0026rdquo;.\nΧρήστος Παναγιωτακόπουλος, ο νότιος που έφτιαξε το “Πότε έρχεται το λεωφορείο;” (noupou.gr)\nInterview on the local news site noupou.gr for my Facebook Chatbot \u0026ldquo;When is my bus coming\u0026rdquo;.\n«Πότε έρχεται το λεωφορείο μου»: Ο 22χρονος που έβαλε τέλος στο μαρτύριο αναμονής μας στις στάσεις (flash.gr)\nInterview on the Greek news site flash.gr for my Facebook Chatbot \u0026ldquo;When is my bus coming\u0026rdquo;.\nΕφαρμογή για δρομολόγια λεωφορείων στέλνει στους επιβάτες καρδούλες και ερωτόλογα!\nArticle on the Greek news site newspost.gr for my Facebook Chatbot \u0026ldquo;When is my bus coming\u0026rdquo;.\nΗ ελληνική εφαρμογή που μας λύνει τα χέρια - Ώρα Ελλάδος 5.30 15/4/2019 | OPEN TV\nTV interview on the Greek nationwide TV channel \u0026ldquo;Open Beyond\u0026rdquo; for my Facebook Chatbot \u0026ldquo;When is my bus coming\u0026rdquo;.\nVarious Profiles Rembetiko Forum youtube.com ","permalink":"https://chrispanag.com/chrispanag-on-the-web/","summary":"Christos on the web","title":"Christos on the web"}]