<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Randall</title>
    <description>The latest articles on DEV Community by Randall (@mistval).</description>
    <link>https://dev.to/mistval</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F413348%2F81e25d08-d557-4f4f-bc83-d704b110b128.jpeg</url>
      <title>DEV Community: Randall</title>
      <link>https://dev.to/mistval</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mistval"/>
    <language>en</language>
    <item>
      <title>How I Host a Bot in 50,000 Discord Servers For Free</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Sat, 12 Jul 2025 15:35:54 +0000</pubDate>
      <link>https://dev.to/mistval/how-i-host-a-bot-in-45000-discord-servers-for-free-5bk9</link>
      <guid>https://dev.to/mistval/how-i-host-a-bot-in-45000-discord-servers-for-free-5bk9</guid>
      <description>&lt;p&gt;When I talk to other developers about &lt;a href="https://github.com/mistval/kotoba" rel="noopener noreferrer"&gt;my Discord bot&lt;/a&gt; they tend to be surprised that I operate it for free using widely available free tiers on cloud platforms, despite it being in nearly 50,000 servers.&lt;/p&gt;

&lt;p&gt;So I thought I would write an article detailing how that works. I'll reveal where I host it, early technical decisions that made the bot lean, optimizations I've made, and what I could do better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's hosted 🖥
&lt;/h2&gt;

&lt;p&gt;Of course in order to host a bot for free, one needs, drum roll please 🥁, free hosting. &lt;/p&gt;

&lt;p&gt;The bot is hosted in &lt;a href="https://www.oracle.com/cloud/free/" rel="noopener noreferrer"&gt;Oracle Cloud&lt;/a&gt;. Their free tier allows you to create a VPS with up to 24 GB of RAM and 4 vCPUs, which is pretty wild compared to most other free tiers at similar providers, which offer more like 1 GB. Note that only ARM cores are available for this.&lt;/p&gt;

&lt;p&gt;I'm currently using 18 GB of RAM for the bot server and 3 vCPUs. Not only is this sufficient, it's actually pretty overkill, and I expect I could scale the bot to at least 150k servers without any changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8v7dh3keeuhksyzjglp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8v7dh3keeuhksyzjglp.png" alt=" " width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although ARM cores might sound like a negative, the benchmarks I ran before moving the bot here actually showed the Oracle ARM cores outperforming Intel and AMD cores at Digital Ocean and Vultr (even their "high frequency" and "high performance" offerings), at least on the SQLite and image rendering benchmarks I used.&lt;/p&gt;

&lt;p&gt;The ARM cores also look very cost effective even if you're paying. I've been pretty impressed with Oracle's offering honestly. Downtime has been minimal as well, I only remember one extended downtime of around 4 hours in the 2 years that the bot has been there.&lt;/p&gt;

&lt;p&gt;One caveat is if you browse Reddit and such, you'll find some stories of Oracle randomly shutting free tier users down. I actually upgraded to a paid account and I pay for a small amount (like $2/month) of object storage for personal file backups using &lt;a href="https://duplicati.com/" rel="noopener noreferrer"&gt;Duplicati&lt;/a&gt;, so that might get me onto the nice list. In the event, I haven't had any problems personally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Run Functions ☁️
&lt;/h2&gt;

&lt;p&gt;I don't need to do this anymore like I used to, since the free Oracle instance is so overkill, but &lt;a href="https://github.com/mistval/kotoba/tree/master/cloud_functions" rel="noopener noreferrer"&gt;a couple of the bot's features&lt;/a&gt; are deployed as &lt;a href="https://cloud.google.com/functions?hl=en" rel="noopener noreferrer"&gt;Google Cloud Run functions&lt;/a&gt;  (it's Google's answer to AWS Lambda).&lt;/p&gt;

&lt;p&gt;Both of the features deployed this way use a few hundred megabytes of RAM to hold dictionaries in memory. By deploying those features as Cloud Run functions, the main bot server doesn't need to pony up the RAM for that. That all gets spun up on a separate server somewhere else in the magical Google cloud, and the main bot server just makes an HTTP request to the Cloud Run function.&lt;/p&gt;

&lt;p&gt;So I basically use Cloud Run functions as extra free RAM for these memory-intensive features (and free CPU cycles too, although that's less critical since I've always had more CPU headroom). These features can feel more sluggish sometimes due to &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html#cold-start-latency" rel="noopener noreferrer"&gt;the phenomenon of cold starts&lt;/a&gt; but that's not a deal breaker.&lt;/p&gt;

&lt;p&gt;Google Cloud has a very generous &lt;a href="https://cloud.google.com/free?hl=en" rel="noopener noreferrer"&gt;free tier&lt;/a&gt; that easily covers all of my usage of Cloud Run functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging and Error Reporting ⚠️
&lt;/h2&gt;

&lt;p&gt;Logs are sent to &lt;a href="https://cloud.google.com/logging?hl=en" rel="noopener noreferrer"&gt;Google Cloud Logging&lt;/a&gt;, and error level logs automatically flow to Google Cloud Error Reporting from there. Once again, this is all covered under the free tier, with room to spare.&lt;/p&gt;

&lt;p&gt;Additionally, a separate bot in my support server allows moderators to trigger &lt;a href="https://www.pagerduty.com" rel="noopener noreferrer"&gt;PagerDuty&lt;/a&gt;, which will call my cell phone and wake me up from my beauty sleep if any exceptionally serious situation arises. Fortunately, this has never needed to be used. As you may have guessed, this is covered under PagerDuty's free tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language and Runtime &amp;lt;/&amp;gt;
&lt;/h2&gt;

&lt;p&gt;The bot is built with JavaScript and runs with &lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;. While JavaScript is not a particularly fast language in general, a lot of the bot's heavy lifting plays nicely to the strengths of Node.js. A lot of it happens in highly optimized native code within the &lt;a href="https://v8.dev/" rel="noopener noreferrer"&gt;V8 engine&lt;/a&gt; powering Node.js.&lt;/p&gt;

&lt;p&gt;For example JSON parsing (which the bot needs to perform for every event it receives from Discord) is implemented in C++ in the V8 engine, and is very fast. The cost of then throwing the parsed object over the fence into JavaScript land is relatively minimal, and the work done on it by JavaScript code is pretty minimal as well in most cases (we'll get more into that later).&lt;/p&gt;

&lt;p&gt;Node.js probably isn't the optimal choice here, and a bot written in C++ or Rust would likely perform better (depending on the bot library), but Node.js is likely going to beat other popular choices including Python, Java, and C#, and may even give Go a good run for its money. (Sorry, I don't have benchmarks, that's just my educated guess)&lt;/p&gt;

&lt;h2&gt;
  
  
  Library 📕
&lt;/h2&gt;

&lt;p&gt;Choosing a &lt;em&gt;bot library&lt;/em&gt; is just as important, maybe even more important, than choosing a language.&lt;/p&gt;

&lt;p&gt;Background: The Discord API consists of a websocket API (for receiving events in real time) and a REST API (for performing actions). Neither are trivial to use, especially the former. Fortunately, there are community-maintained libraries for most popular programming languages to simplify interactions with the Discord APIs.&lt;/p&gt;

&lt;p&gt;Bot developers almost always choose one of these libraries rather than trying to reinvent the wheel. When I made this choice way back in 2016, there were two main options for Node.js: &lt;a href="https://www.npmjs.com/package/discord.js" rel="noopener noreferrer"&gt;discord.js&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/eris" rel="noopener noreferrer"&gt;Eris&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;discord.js was known as being more feature-rich, having great documentation, and having a welcoming community, while Eris was known for being much faster and leaner, having a somewhat more gatekeepy community, and being used by most of the popular Node.js bots who needed its vertical scalability.&lt;/p&gt;

&lt;p&gt;At the time, I didn't have any ambitions of scaling my bot to tens of thousands of servers, but a lot of the bots I knew and respected used Eris, and I wasn't intimidated by its reputation since I wasn't a beginner. So I ended up choosing Eris, which was definitely the right choice in hindsight. &lt;/p&gt;

&lt;p&gt;Nowadays, discord.js has improved its performance to an extent, while Eris has fragmented due to the main maintainers apparently moving on. My bot is using &lt;a href="https://www.npmjs.com/package/@projectdysnomia/dysnomia" rel="noopener noreferrer"&gt;Dysnomia&lt;/a&gt; now which is a fork of Eris that aims to carry the torch onward, but that's a pretty niche setup by this point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Library Optimizations ➕
&lt;/h2&gt;

&lt;p&gt;The nature of the Discord API requires keeping a lot of information in a cache. For example, after your bot connects to the websocket API and an event happens involving user &lt;code&gt;A&lt;/code&gt;, Discord will send your bot miscellaneous information about user &lt;code&gt;A&lt;/code&gt; such as their username, avatar, account creation date, and more. Later, when another event involving user &lt;code&gt;A&lt;/code&gt; occurs, Discord might not send you that extra information, under the assumption that you cached it previously.&lt;/p&gt;

&lt;p&gt;This cached data is managed by whichever bot library you chose, and is generally kept in the bot process's memory. This cached data can easily get into the multi-gigabyte range for bots in thousands or tens of thousands of servers.&lt;/p&gt;

&lt;p&gt;How a bot's library manages this cache data is a major factor in how well the bot scales. Eris (and by extension Dysnomia) is relatively efficient at this compared to other bot libraries. On the other hand, its cache customization options aren't very comprehensive. One option it &lt;em&gt;does&lt;/em&gt; provide is allowing you to customize the message cache size, and this is a pretty significant one. I set this to zero in my bot, because my bot never needs to access previously-sent messages. If I remember correctly, the default is to cache the most recent 100 messages in each channel. For a bot in 50,000 servers, that can be millions of messages and multiple gigabytes of memory.&lt;/p&gt;

&lt;p&gt;Another important optimization is to only enable &lt;a href="https://discord.com/developers/docs/events/gateway#gateway-intents" rel="noopener noreferrer"&gt;Gateway Intents&lt;/a&gt; that you actually need. If at all possible, avoid the &lt;code&gt;PRESENCE_UPDATE&lt;/code&gt; intent like the plague. Having that intent enabled can massively increase the number of events your bot receives (you'll receive an event any time anybody's online status changes in any server). Enabling &lt;code&gt;PRESENCE_UPDATE&lt;/code&gt; actually requires approval from Discord now if your bot is in more than 100 servers, so this is a decision most developers don't need to grapple with - Discord will force it off for you unless you really need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hot Path 🔥
&lt;/h2&gt;

&lt;p&gt;The bot receives an event for every message sent in any server it's present in. That averages to around 100-200 events per second.&lt;/p&gt;

&lt;p&gt;At least 99.9% of those messages aren't intended for the bot, so the bot just has to look at the message, decide "this isn't for me", and do nothing. The faster we can do that, the better. Here's some of the key code involved in that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;processInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;serverId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;guild&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;guild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;prefixes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persistence_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrefixesForServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serverId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;msgContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;msgContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msgContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;spaceIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msgContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;commandText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spaceIndex&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;commandText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msgContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;commandText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msgContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spaceIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;commandText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;commandText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;prefixes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefixedAlias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commandText&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;prefixedAlias&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeCommand_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spaceIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code actually isn't very micro-optimized. Looking at it now, I can imagine how we could do better (mainly by avoiding the creation of new strings). But here's the important bit that we're getting right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;prefixes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persistence_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrefixesForServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serverId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fetching the custom command prefixes for the current Discord server. If you don't know what that means, consider that by default all the bot's command are prefixed/namespaced with &lt;code&gt;k!&lt;/code&gt;. For example: &lt;code&gt;k!help&lt;/code&gt;, &lt;code&gt;k!about&lt;/code&gt;, etc. But what if there's another bot that uses the same prefix, and it also has a &lt;code&gt;k!help&lt;/code&gt; command? Fortunately, the bot allows server admins to change the &lt;code&gt;k!&lt;/code&gt; prefix to something else to disambiguate (or if they just prefer using some other prefix).&lt;/p&gt;

&lt;p&gt;This is a per-server setting that's saved in the database, but this code here to fetch it is synchronous, and that's the key optimization. All command prefixes are cached in process memory to avoid needing any inter-process communication in this hot path. Making hundreds of calls per second to a database or cache instance, while possible, would add significant load and would concretely impact scalability.&lt;/p&gt;

&lt;p&gt;Not all bots can avoid database queries in their hot paths. If you have a bot that grants users XP for every message they send, then you have to make a database query, there's no way around it (although you could batch many XP increments together and flush them to the database in groups).&lt;/p&gt;

&lt;h2&gt;
  
  
  What Could I Do Better? 👀
&lt;/h2&gt;

&lt;p&gt;The main choice I made that doesn't align so well with keeping the bot lean is using MongoDB as the database. Here's the output from &lt;code&gt;top&lt;/code&gt; on the server as we speak, sorted by memory usage:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxbg9fmj8117cwoyzlev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxbg9fmj8117cwoyzlev.png" alt=" " width="603" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mongo is using a bit over 3 gigabytes of resident memory, an amount that is similar to the bot process itself. Now to be fair, I could reduce that by setting &lt;code&gt;--wiredTigerCacheSizeGB&lt;/code&gt; a lot lower and any performance degradation would probably be pretty minimal. Since the bot has over 9 gigabytes of available memory, and over 3 gigabytes completely free, there's just no need.&lt;/p&gt;

&lt;p&gt;I could have used PostgreSQL instead and it likely would have given me a bit more RAM headroom and a lot more CPU headroom, but the real resource-miser move would have been to use SQLite, which would have been sufficient for this bot. SQLite is often misunderstood as a toy database, and it's further often assumed that that alleged toy-ness also means it has poor performance. While it's a nuanced topic, that's a broadly unfair assessment as SQLite avoids overhead from...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inter-process communication (this one's big)&lt;/li&gt;
&lt;li&gt;Access control&lt;/li&gt;
&lt;li&gt;Row-level locking&lt;/li&gt;
&lt;li&gt;...more&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SQLite doesn't have a lot of these big-boy features, which gives it a performance boost for &lt;em&gt;low-concurrency&lt;/em&gt; access patterns (which is the case for my bot).&lt;/p&gt;

&lt;p&gt;That said, I'm comfortable with the decision to use Mongo in hindsight. I used it as a learning opportunity to learn a new database, it fits on the instance, I like the official tools (Mongo Compass), and it gets me where I need to go. But it would be on the chopping block if I needed to claw back more memory and CPU headroom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Welp that's how I do it! It's a combination of utilizing the great free hosting options available, choosing fast dependencies, and making a few targeted optimizations. Feel free to comment if you have any questions or comments.&lt;/p&gt;

</description>
      <category>discord</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Reflections on Generative AI</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Fri, 04 Jul 2025 16:40:12 +0000</pubDate>
      <link>https://dev.to/mistval/reflections-on-generative-ai-4fon</link>
      <guid>https://dev.to/mistval/reflections-on-generative-ai-4fon</guid>
      <description>&lt;p&gt;AI. It's my turn to talk about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 We're in the Future
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvlqby3iwqqkujx8qi7x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvlqby3iwqqkujx8qi7x.jpg" alt=" " width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First I have to point out how &lt;em&gt;cool&lt;/em&gt; of a world we now live in. Generative AI may seem normal by now, almost mundane. But when I step back and try to look at it from a fresh perspective, it's the most spectacular and unexpected technology of my lifetime. In early 2022, if you had showed me Claude Sonnet 4 iteratively implementing a feature while writing tests in Cursor and asked me to predict when such technology would be invented, I would have guessed the 2040s at the earliest. Yet here we are just a few years later in 2025.&lt;/p&gt;

&lt;p&gt;A lot of Sci-Fi AIs suddenly look plausible with current technology. HAL 9000 from 2001: A Space Odyssey, the terminal AIs in Book of the Long Sun, Auntie Dot from Halo: Reach, to name a few. These AIs are all flawed and are not AGI (as OpenAI defines it at least) but they are able to understand what is said to them, speak intelligently, synthesize information, and make decisive decisions in general contexts, not unlike our quaint little ChatGPT 4o can today.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧮 The Accelerationists and the Luddites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby7pewjppwl2lzyyetm8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby7pewjppwl2lzyyetm8.jpg" alt=" " width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where does it go from here? Visions are polarized, especially in the industries generative AI is being adopted in. Some get hyped about the impending singularity whenever a new model is released. Others shrug it off as being of little consequence.&lt;/p&gt;

&lt;p&gt;I suspect many in both camps are coming from places of fear, but with different, almost opposite sources. The "Luddites" are afraid of being replaced by AI and marginalized as a result. While many of the Accelerationists already feel marginalized, or are haunted by an 'is this all there is?' kind of ennui, or fear humanity as a whole is careening towards a cliff. For them, the tantalizing Sci-Fi future that AI seems to herald is the cure.&lt;/p&gt;

&lt;p&gt;When we see viewpoints get so polarized, historically that seems to be a great hint about how it's really going to unfold. Ultimately the truth is going to lie somewhere in between the two poles, likely pretty close to the center. The software industry will be disrupted (I think this is irrefutable by this point). But are we going to be uploading our minds to computers and lording over our own star systems next year? Probably not.&lt;/p&gt;

&lt;p&gt;Generative AI will continue to disrupt industries. We'll get better and better at applying it. But as we do so its limitations will become more and more clear, leaving us far short of any utopian (or dystopian) vision.&lt;/p&gt;

&lt;p&gt;This doesn't preclude those possibilities eventually, as technology only improves, but rather than a sudden radical upheaval we're more likely to experience years that don't look much different from the previous, despite significant changes on the decade timescale. This is how it's always been.&lt;/p&gt;

&lt;h2&gt;
  
  
  💀 My Death as an Artist
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekvzz3l7651vtffyh2vr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekvzz3l7651vtffyh2vr.jpg" alt=" " width="500" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Outsiders imagine programming as a pretty rote and mechanical task. Someone decides what the computer should do, and the programmer sits down and "types it in". But if you're reading this, you know that's not how it works.&lt;/p&gt;

&lt;p&gt;We code with our own voices, and no two programmers make all of the same decisions. We get better at it (and sometimes worse) over time, but there's no universal ideal that we converge on. We remain unique, like painters or authors, and we take pride in our work.&lt;/p&gt;

&lt;p&gt;But when AI writes code, I don't feel like it's mine. Sure, I'll make tweaks by hand and prompt the AI to make changes, but I don't rewrite it into my own voice, there would be no point using AI then.&lt;/p&gt;

&lt;p&gt;So I find that I don't &lt;em&gt;care&lt;/em&gt; about code that AI generates for me. I still care about the architectural choices and the functionality, but I become detached from the microscopic details of the code and any artistry that might have been there. And I worry this detachment might hurt the product itself, that's something I’m still trying to navigate effectively.&lt;/p&gt;

&lt;p&gt;This is why I haven't been using AI much for personal projects. I use Copilot autocomplete, but I rarely prompt AIs to write large pieces of code for me. I might be sacrificing efficiency, but I find it more fun this way. I get into the flow and care about the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  🦋 The Struggle as Metamorphosis
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkifcysk77q6hq487b7oy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkifcysk77q6hq487b7oy.jpg" alt=" " width="500" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's going to happen to beginner programmers in this age of AI? There's a lot of trepidation about this, and it's well-deserved. I'm not talking about the idea of AI replacing programmers, but rather how beginner programmers will (or won't) emerge from their cocoons one day as expert programmers.&lt;/p&gt;

&lt;p&gt;I vividly remember the first program I ever wrote. It was a script for Garry's Mod in 2006. It was about 50 lines long. It was attached to an AK-47, and if you picked up that AK-47, it froze you in place until you admitted in chat to being a n00b. It was trivial, but it took me about a week of banging my head on it late into the night struggling to interpret Lua documentation like ancient hieroglyphs. For that time, I was obsessed with it and thought about little else. When I was done, I knew the basics about variables, functions, and events.&lt;/p&gt;

&lt;p&gt;But now? ChatGPT could spit that out for me in 30 seconds. Nobody needs to struggle to build something like that anymore. I could have it working in no time, but I'd hardly learn a thing from the experience.&lt;/p&gt;

&lt;p&gt;The fact that AI is capable of this doesn't just deprive beginners of valuable learning experiences. That part has a clear, if painful, solution for anyone disciplined enough (just resist the temptation and don't use AI during your formative stages).&lt;/p&gt;

&lt;p&gt;No. More insidiously, it's also deeply demoralizing.&lt;/p&gt;

&lt;p&gt;For as long as I can remember, I have dreamt of being a respected electronic music producer. It's my secret life goal. I've never dedicated the time to it that a life goal deserves, but I have dabbled with it and know basic DAW (Digital Audio Workstation) usage.&lt;/p&gt;

&lt;p&gt;Until recently, this basic knowledge made me a better electronic music producer than the 99.9% of humanity that has never touched a DAW before. But that's no longer the case. I now have thousands of hours to go until I'm half as good as the AI music generators that everyone now has access to. Realizing this, I now struggle more than ever to put in effort. Beginner programmers are experiencing the same thing.&lt;/p&gt;

&lt;p&gt;Where does this lead economically? The world still needs programmers with deep technical expertise, but the pipeline from beginner to expert is being squeezed. On one extreme, it's possible that AI will win the race. That is, AI replaces all or almost all programmers in the 30-40 years between now and when the current generation of programmers retires. 30-40 years is a long time, after all. On the other extreme, maybe we end up in a Dune-like world largely starved of the talent still needed to maintain the machines built by previous generations. As usual, the reality is likely somewhere in between, perhaps with "vibe coding" as the main development style, supported by a caste of (perhaps mostly older) programmers who uniquely retain the ability to go deeper.&lt;/p&gt;

&lt;p&gt;And where does it lead us as artists? I fear that, like the act of using AI to write code that's not my own, the watering down of the learning experience may be another factor that could drain life from the experience of programming, especially for those too young to have experienced it.&lt;/p&gt;

&lt;h2&gt;
  
  
  ☁️ The Realization of the Dream
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpjvhxj1x9605bzxeaom.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpjvhxj1x9605bzxeaom.jpg" alt=" " width="500" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's another interpretation we can take of my experience with music. Sure, I might now be farther than ever from micromanaging those sine waves and drum loops into a unique and personal thing of beauty. And yet paradoxically, I'm closer than ever to making &lt;em&gt;something&lt;/em&gt;. I can, and have, dreamt something up, asked an AI to build it for me, and received a pretty good result. This is true of music and digital art as well (including the cover image of this very article). No, it doesn't feel like it's truly mine, nor is it exactly what I had envisioned, but at least it's something - something I'm currently incapable of building otherwise.&lt;/p&gt;

&lt;p&gt;Moreover, I expect this experience to become &lt;em&gt;more&lt;/em&gt; personal over time, not less, as the tools will come with more customizability to help us achieve exactly the sound/look that we had imagined.&lt;/p&gt;

&lt;p&gt;I expect this will happen with programming as well. The tools will do better at doing what we ask and realizing our dreams exactly. For me as an experienced programmer, that makes it feel less personal (since I'm not writing the code myself). But for a non-programmer, it's more personal (because they can get exactly what they envisioned without compromise).&lt;/p&gt;

&lt;p&gt;This is the bright side of applying AI to any art (including programming). It can help anyone become a builder, and realize dreams that may otherwise have been out of reach, or at least locked behind thousands of hours of study and practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I do have some more thoughts but I like that this is ending on a positive note, and it's long enough already, so I will wrap it up here.&lt;/p&gt;

&lt;p&gt;The impacts of AI on our society are already far-reaching and will become even more so. We'll all be winners in some ways and losers in others. I am struggling with a sense of loss in the AI age, but also optimistic about the opportunities it comes with. We're not just changing how we build things, but &lt;em&gt;what&lt;/em&gt; we &lt;em&gt;can&lt;/em&gt; build, and that's exciting.&lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>discuss</category>
      <category>beginners</category>
    </item>
    <item>
      <title>🏖️ Going Against the Grain</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Thu, 25 Apr 2024 19:32:05 +0000</pubDate>
      <link>https://dev.to/mistval/going-against-the-grain-elb</link>
      <guid>https://dev.to/mistval/going-against-the-grain-elb</guid>
      <description>&lt;p&gt;I was reflecting recently on habits and practices I have that go against the common wisdom of the software development industry. Do you have any?&lt;/p&gt;

&lt;p&gt;Here are some of mine:&lt;/p&gt;

&lt;h2&gt;
  
  
  I don't ask a lot of questions
&lt;/h2&gt;

&lt;p&gt;I often hear that one should ask as many questions as possible, especially when onboarding onto a new project. That has never been my style though. Instead, I like to answer my own questions by digging through code, stepping through it in a debugger, experimenting, reading old commit messages, things like that. I definitely do ask questions, but it's somewhat of a last resort for me when I either can't find the answer myself, or really need to know the answer ASAP.&lt;/p&gt;

&lt;p&gt;When I answer my own question like this, I don't just get the answer to the question I'm asking, I also stumble upon the answers to a lot of other questions I haven't even asked yet. That doesn't really happen when I just ask someone my question and they tell me the answer.&lt;/p&gt;

&lt;p&gt;I credit this habit for why I often seem to get a reputation for knowing a lot about code I didn't even write. Also it helps me operate when there's nobody I can ask about something (often happens in freelancing).&lt;/p&gt;

&lt;h2&gt;
  
  
  I write lots of integration tests, few unit tests
&lt;/h2&gt;

&lt;p&gt;Usually I hear that one should write a lot of unit tests, some component tests, and a few integration tests. I generally do the opposite, and write many integration tests and few unit tests. Here are some reasons why I think this works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Integration tests are the best at telling you if your application actually works. The goal of software engineering is never really "write a function that does X", it's always "write an interface via which some external actor can do X", and that's what integration tests test.&lt;/li&gt;
&lt;li&gt;Integration tests can often survive refactoring and other overhauls intact. Let's consider an HTTP API server. You're going to want to have a stable API with request and response schemas that stay the same regardless of any internal refactoring you do. If a test targets that stable API, then it can remain unchanged and valid even if you completely rewrite all the code under the hood. In contrast big refactors often necessitate big changes to unit tests and especially component tests, which makes the refactor costlier and also introduces the possibility of breaking a test and having it pass when it shouldn't.&lt;/li&gt;
&lt;li&gt;Other developers and QA engineers can write integration tests without having whitebox insight into the internal code. If you can setup a good set of utilities for writing API-level tests and shoot some example tests and API documentation over to QA, they can start adding more tests while knowing little to nothing about the internals. In addition, since integration tests are less likely to need changes, you're less likely to need to update tests written by someone else (which is often pretty difficult) when you make changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I definitely write unit and component tests but I reserve them for certain conditions like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pure functions that implement complex and well-defined algorithms. We often need such functions to be rock solid and have many tests that run quickly. And if the expected behavior is well-defined, we're less likely to change it in a way that requires changing/rewriting tests.&lt;/li&gt;
&lt;li&gt;If it's difficult to test certain behavior from higher level tests (sometimes this is true just due to the type of application you're building - for instance, UI tests can be very cumbersome to write).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  I don't like frameworks
&lt;/h2&gt;

&lt;p&gt;While I respect the structure that frameworks can impart on a project (especially a big project) and the magic they provide, I find that the magic is sometimes more of a curse than a blessing.&lt;/p&gt;

&lt;p&gt;The more magical a framework is, the more times I experience the magic of "I don't understand why this doesn't work..." and spend a day waving my trial-and-error wand at it until I reach the nirvana of "now it works but I still don't understand why..." and move onto the next thing.&lt;/p&gt;

&lt;p&gt;When I keep things simple and work with lower-level tools, I stumble upon puzzles much less often and can work through them much more quickly. Also I have more flexibility to build exactly what I need, without bumping into the railings that opinionated frameworks often present.&lt;/p&gt;

&lt;p&gt;That said, it's a balancing act to be sure. I don't build applications in assembly just because "it's the least magical". Sometimes opinionated frameworks can do things that would be really hard or time consuming to do otherwise, and for bigger teams they can help everyone stay on the same page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about you?
&lt;/h2&gt;

&lt;p&gt;How do you rebel against the common wisdom?&lt;/p&gt;

&lt;p&gt;By the way, I don't claim to be objectively correct on any of those practices, it's just how I work, for better or for worse!&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>softwareengineering</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>🏁🐘Winning Race Conditions With PostgreSQL</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Sat, 17 Feb 2024 17:13:16 +0000</pubDate>
      <link>https://dev.to/mistval/winning-race-conditions-with-postgresql-54gn</link>
      <guid>https://dev.to/mistval/winning-race-conditions-with-postgresql-54gn</guid>
      <description>&lt;p&gt;Race conditions suck! They can be extremely difficult to debug, and often only occur naturally in production at the most critical moments (times of high traffic). Fortunately, many databases (such as PostgreSQL) give us powerful tools to manage and avoid race conditions.&lt;/p&gt;

&lt;p&gt;In this article, we will do a deep dive into how to avoid race conditions while using PostgreSQL, focusing especially on enforcing uniqueness constraints. We'll start with some simpler cases, then move on to more complex ones, so feel free to skip down depending on your level of experience.&lt;/p&gt;

&lt;p&gt;This article assumes that you are familiar with race conditions, why they happen, and why they suck. (if you aren't familiar, don't worry, you will learn the hard way soon enough! We all do.) The later cases also assume at least an intermediate knowledge of relational databases.&lt;/p&gt;

&lt;p&gt;This article will use &lt;a href="https://www.npmjs.com/package/pg" rel="noopener noreferrer"&gt;the Node.js pg library&lt;/a&gt; for examples.&lt;/p&gt;

&lt;p&gt;Let's just dive right in and look at some different cases where our code and data are vulnerable to race conditions, and find out how to fix them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 1: Simple Uniqueness
&lt;/h2&gt;

&lt;p&gt;Most applications do not allow two users to share the same email address. If a user tries to register with an email already registered by another account, registration should fail.&lt;/p&gt;

&lt;p&gt;One might write such a uniqueness check in JavaScript like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hashedPassword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingUserResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT 1 FROM "user" WHERE email = ?;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingUserResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User already exists&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT INTO "user" VALUES (?, ?);&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hashedPassword&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this code is vulnerable to race conditions. If two users try to sign up &lt;em&gt;at the exact same time&lt;/em&gt; with the same email, then it's possible for a sequence of events like this to happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User #1 SELECT operation&lt;/li&gt;
&lt;li&gt;User #2 SELECT operation&lt;/li&gt;
&lt;li&gt;User #1 INSERT operation&lt;/li&gt;
&lt;li&gt;User #2 INSERT operation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since the &lt;code&gt;SELECT&lt;/code&gt; operations happen for both users before the &lt;code&gt;INSERT&lt;/code&gt; happens for either one, both users will survive the duplicate-check and proceed to the &lt;code&gt;INSERT&lt;/code&gt; operation. The nature of &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous" rel="noopener noreferrer"&gt;asynchronous programming&lt;/a&gt; allows such sequences of events.&lt;/p&gt;

&lt;p&gt;Fortunately this problem is really easy to fix, we just need to add a &lt;code&gt;UNIQUE&lt;/code&gt; constraint to the &lt;code&gt;email&lt;/code&gt; column in the &lt;code&gt;user&lt;/code&gt; table. We can do this when creating the table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"user"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"email"&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"hashedPassword"&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or later, by using &lt;code&gt;ALTER TABLE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By setting a &lt;code&gt;UNIQUE&lt;/code&gt; constraint on the &lt;code&gt;email&lt;/code&gt; column, we are telling the database that no two rows may have the same value for &lt;code&gt;email&lt;/code&gt;, and the database itself will enforce this for us. Even if the two &lt;code&gt;INSERT&lt;/code&gt;s happen at exactly the same time, concurrency features in the database guarantee that only one will succeed and the other will error.&lt;/p&gt;

&lt;h1&gt;
  
  
  Case 2: Compound Uniqueness
&lt;/h1&gt;

&lt;p&gt;Let's say we're running a multi-user blogging application, like Dev.to, and we want to allow users to create one highlighted post per week. Our &lt;code&gt;posts&lt;/code&gt; table might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"userId"&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"createdAt"&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITHOUT&lt;/span&gt; &lt;span class="n"&gt;TIMEZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"highlighted"&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"postContent"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could write code similar to the first example, with a uniqueness check like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingWeeklyHighlightedPostResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`
    SELECT 1
    FROM "posts"
    WHERE
      "userId" = ?
      AND
      "highlighted" IS TRUE
      AND
      /* "createdAt" is greater than the first moment
       * of the current week */
      "createdAt" &amp;gt;= DATE_TRUNC('week', NOW());
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingWeeklyHighlightedPostResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You already have a highlighted post this week&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However this would have the same issue. If a user submits two highlighted posts at the same time, both of them might pass this uniqueness check. Maybe the user accidentally double-clicked the submit button, or maybe they're trying to exploit this to get more ad revenue 🤷&lt;/p&gt;

&lt;p&gt;Like last time, can we create a &lt;code&gt;UNIQUE&lt;/code&gt; constraint to help us? Yes! Even though we store an exact timestamp rather than the week that a post was created in, PostgreSQL's support of &lt;a href="https://www.postgresql.org/docs/current/indexes-expressional.html" rel="noopener noreferrer"&gt;indexes on expressions&lt;/a&gt; gives us what we need. Further, we need to use the &lt;a href="https://www.postgresql.org/docs/current/indexes-partial.html" rel="noopener noreferrer"&gt;partial index&lt;/a&gt; feature to only enforce this constraint on highlighted posts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="nv"&gt;"one_highlighted_post_per_week_constraint"&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'week'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"highlighted"&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this UNIQUE index, the database will not not allow two rows to both have &lt;code&gt;"highlighted" IS TRUE&lt;/code&gt; and to have the same combination of &lt;code&gt;userId&lt;/code&gt;, &lt;code&gt;DATE_TRUNC('week', "createdAt")&lt;/code&gt;. In other words, a user may only have one highlighted post per week.&lt;/p&gt;

&lt;p&gt;For any rows where &lt;code&gt;"highlighted" IS FALSE&lt;/code&gt;, they are exempt from this constraint and we can insert as many of them as we want.&lt;/p&gt;

&lt;h1&gt;
  
  
  Case 3: Compound Multiple Uniqueness
&lt;/h1&gt;

&lt;p&gt;Same case as above, but rather than one highlighted post per week, we want to allow users to make &lt;em&gt;three&lt;/em&gt; highlighted posts per week. Can we do this with a UNIQUE constraint like we did above?&lt;/p&gt;

&lt;p&gt;Once again, yes, but the solution here may be a bit more convoluted. First, we create the same &lt;code&gt;posts&lt;/code&gt; table, but instead of a BOOLEAN &lt;code&gt;highlighted&lt;/code&gt; column we add an INT &lt;code&gt;weeklyHighlightedPostNumber&lt;/code&gt; column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"userId"&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"createdAt"&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITHOUT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"weeklyHighlightedPostNumber"&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"postContent"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a post is highlighted, then its &lt;code&gt;"weeklyHighlightedPostNumber"&lt;/code&gt; will be an integer. If the post is not highlighted, &lt;code&gt;"weeklyHighlightedPostNumber"&lt;/code&gt; will be NULL.&lt;/p&gt;

&lt;p&gt;Now we add a constraint that forces &lt;code&gt;weeklyHighlightedPostNumber&lt;/code&gt; to be a number between &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;3&lt;/code&gt;, if it's not NULL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;num_weekly_posts_constraint&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"weeklyHighlightedPostNumber"&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;OR&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"weeklyHighlightedPostNumber"&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can add a &lt;code&gt;UNIQUE&lt;/code&gt; constraint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="nv"&gt;"three_highlighted_posts_per_week_constraint"&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'week'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nv"&gt;"weeklyHighlightedPostNumber"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"weeklyHighlightedPostNumber"&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will enforce that for any highlighted posts (rows with non-NULL &lt;code&gt;"weeklyHighlightedPostNumber"&lt;/code&gt;), they may not have the same combination of &lt;code&gt;"userId"&lt;/code&gt;, &lt;code&gt;DATE_TRUNC('week', "createdAt")&lt;/code&gt;, and &lt;code&gt;"weeklyHighlightedPostNumber"&lt;/code&gt;. Since the earlier constraint requires &lt;code&gt;"weeklyHighlightedPostNumber"&lt;/code&gt; to be between 1 and 3, this limits us to 3 highlighted posts per week per user.&lt;/p&gt;

&lt;p&gt;This does mean that when inserting a post, you need to figure out the next available post number. We can do this with a bit of SQL in our INSERT operation. This solution also handles gaps (for example if you have three highlighted posts and delete the second one). It does start getting a little hairy, but check it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createHighlightedPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;insertResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`
      WITH next_highlighted_post_num AS MATERIALIZED (
        SELECT series_num
        FROM GENERATE_SERIES(1, 3) AS series_num
        WHERE NOT EXISTS (
          SELECT *
          FROM posts
          WHERE
            posts."userId" = $1
            AND
            DATE_TRUNC('week', NOW()) &amp;lt;= posts."createdAt"
            AND
            posts."weeklyHighlightedPostNumber" = series_num
        )
        LIMIT 1
      )
      INSERT INTO posts
      SELECT $1, NOW(), series_num, $2
      FROM next_highlighted_post_num;    
      `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;insertResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowCount&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not create highlighted post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course you could instead just write a simple &lt;code&gt;SELECT&lt;/code&gt; query to get all currently existing &lt;code&gt;"weeklyHighlightedPostNumber"&lt;/code&gt;s for the given user and current week, and write JavaScript code to choose the new row's &lt;code&gt;"weeklyHighlightedPostNumber"&lt;/code&gt; to &lt;code&gt;INSERT&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Case 4: Compound Referential Uniqueness
&lt;/h1&gt;

&lt;p&gt;This case will be similar to case #2, but instead of "one highlighted post per week", we'll modify the condition slightly to "must wait 7 days before making another highlighted post".&lt;/p&gt;

&lt;p&gt;In case #2, if a user makes a highlighted post on a Wednesday, they're next able to make one at the start of the next week (Monday).&lt;/p&gt;

&lt;p&gt;But here in case #4, if a user makes a highlighted post on a Wednesday, they have to wait until the same time next Wednesday before they can make another.&lt;/p&gt;

&lt;p&gt;This would require any constraint to reference the user's previous highlighted post creation date, which is something that typical &lt;code&gt;UNIQUE&lt;/code&gt; constraints simply cannot do. To solve this we'll need to bust out some extra features: transactions and &lt;a href="https://www.postgresql.org/docs/current/explicit-locking.html" rel="noopener noreferrer"&gt;advisory locks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This will be the final and trickiest case that we look at.&lt;/p&gt;

&lt;p&gt;We will use the same table schema as in case #2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"userId"&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"createdAt"&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITHOUT&lt;/span&gt; &lt;span class="n"&gt;TIMEZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"highlighted"&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"postContent"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is a solution in JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createHighlightedPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postInsertLockNamespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BEGIN TRANSACTION;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT PG_ADVISORY_XACT_LOCK(?, ?);&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;postInsertLockNamespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingWeeklyHighlightedPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`
        SELECT 1
        FROM posts
        WHERE
          "userId" = ?
          AND
          highlighted IS TRUE
          AND
          "createdAt" &amp;gt;= NOW() - INTERVAL '1 week'
      `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingWeeklyHighlightedPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Already have a highlighted post in the previous seven days&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT INTO "posts" VALUES (?, ?, ?, ?);&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMMIT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ROLLBACK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this to work, anything which is going to modify (&lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt;) records in the &lt;code&gt;posts&lt;/code&gt; table should first &lt;code&gt;SELECT PG_ADVISORY_XACT_LOCK(5000, userId);&lt;/code&gt;. This takes a lock, and any other transaction that tries to take a lock &lt;em&gt;with the same arguments&lt;/em&gt; will have to wait for it to be released. If we're careful to always take this lock before modifying &lt;code&gt;posts&lt;/code&gt;, we can trust that after we take the lock, nothing else will modify posts for that user until after we release the lock. This means after we do the &lt;code&gt;SELECT&lt;/code&gt; statement to get &lt;code&gt;existingWeeklyHighlightedPosts&lt;/code&gt;, we know that the result of that will remain correct until after our &lt;code&gt;INSERT&lt;/code&gt; completes and the transaction commits. This effectively prevents a user from submitting a highlighted post if they have already submitted one in the past seven days, even if they are spamming us with parallel post requests.&lt;/p&gt;

&lt;p&gt;However, it can be hard to make sure that your code is well-behaved and always takes the lock before modifying &lt;code&gt;posts&lt;/code&gt;, especially if other developers are working on the same codebase (or some psycho (totally not yourself of course) logs in with pgAdmin and runs random queries!). If anyone does not properly take the lock before inserting, then this approach breaks.&lt;/p&gt;

&lt;p&gt;To help a little bit, we can create &lt;a href="https://www.postgresqltutorial.com/postgresql-triggers/" rel="noopener noreferrer"&gt;a trigger&lt;/a&gt; that automatically takes the lock whenever you &lt;code&gt;INSERT&lt;/code&gt; or &lt;code&gt;UPDATE&lt;/code&gt; or &lt;code&gt;DELETE&lt;/code&gt; rows in this table. That would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="nv"&gt;"take_post_modify_lock_function"&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;PLPGSQL&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"userId"&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="n"&gt;PERFORM&lt;/span&gt; &lt;span class="n"&gt;PG_ADVISORY_XACT_LOCK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"userId"&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="n"&gt;PERFORM&lt;/span&gt; &lt;span class="n"&gt;PG_ADVISORY_XACT_LOCK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"take_post_modify_lock_trigger"&lt;/span&gt;
&lt;span class="k"&gt;BEFORE&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="nv"&gt;"take_post_modify_lock_function"&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This trigger will &lt;em&gt;force&lt;/em&gt; any modification of &lt;code&gt;posts&lt;/code&gt; to take the lock, so you don't need to remember to do it in code when you modify posts.&lt;/p&gt;

&lt;p&gt;You do still need to do it in code before &lt;code&gt;SELECT&lt;/code&gt;ing to verify your constraint condition though. There is no such thing as &lt;code&gt;SELECT&lt;/code&gt; triggers, and even if there were, taking the lock would be wasteful for queries that don't really need to take it (like when you're fetching a list of posts to display on the homepage). If you don't take the lock before &lt;code&gt;SELECT&lt;/code&gt;ing, then someone else might get the lock and do an &lt;code&gt;INSERT&lt;/code&gt; before you do the &lt;code&gt;INSERT&lt;/code&gt; you're planning to do based on the results of the &lt;code&gt;SELECT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;‡ The &lt;code&gt;5000&lt;/code&gt; chosen above for the first argument to &lt;code&gt;PG_ADVISORY_XACT_LOCK()&lt;/code&gt; is arbitrary. We could have chosen any other number. What's important is that it should be different from any other types of locks you create, so that locks with different meanings don't overlap. For example if we had another table &lt;code&gt;comments&lt;/code&gt; and wanted to do a similar thing with locks, we could use &lt;code&gt;5001&lt;/code&gt; for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  SERIALIZABLE Transaction Isolation Level
&lt;/h3&gt;

&lt;p&gt;There's actually some secret alternative magic sauce that can make this all work without any explicit locking, and that's the &lt;a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-SERIALIZABLE" rel="noopener noreferrer"&gt;SERIALIZABLE transaction isolation level&lt;/a&gt;. Most SQL databases have this, but it's often a bit different between them. For example PostgreSQL's version provides much stronger guarantees than MySQL's version (which can be a little deceptive).&lt;/p&gt;

&lt;p&gt;When you use the SERIALIZABLE transaction isolation level and you do a SELECT inside of a transaction, PostgreSQL "remembers" what you SELECTed, and if any data changes before the transaction finishes in a way that would cause your SELECT query to have returned a different result, then your transaction will get a "serialization failure" and have to ROLLBACK.&lt;/p&gt;

&lt;p&gt;I cannot stress enough that this is an extremely powerful feature and if you simply switch it on (as the default: &lt;code&gt;ALTER DATABASE &amp;lt;DATABASE NAME&amp;gt; SET DEFAULT_TRANSACTION_ISOLATION TO SERIALIZABLE;&lt;/code&gt;) and consistently use transactions, then you can write your code without having to think about explicit locking. However there are a number of guidelines and pitfalls you have to be aware of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Use explicit transactions (&lt;code&gt;BEGIN TRANSACTION;&lt;/code&gt; statement) for any operations you do that execute more than one SQL statement (such as the &lt;code&gt;createHighlightedPost()&lt;/code&gt; JavaScript function we looked at above).&lt;/li&gt;
&lt;li&gt;Avoid running any transactions with lower isolation levels, and be very careful if you do, since it can be harder to reason about how transactions with different isolation levels interact.&lt;/li&gt;
&lt;li&gt;Be ready to retry transactions, since you may encounter serialization failures pretty regularly, which is normal and expected.&lt;/li&gt;
&lt;li&gt;Keep transactions as short as possible, since having more transactions running at any given time will increase the chance of serialization failures.&lt;/li&gt;
&lt;li&gt;Understand that the predicate locking used by SERIALIZABLE transactions does introduce some non-trivial overhead.&lt;/li&gt;
&lt;li&gt;Be aware that certain patterns don't work well under SERIALIZABLE transaction isolation level. For example using &lt;code&gt;SELECT ... FOR UPDATE SKIP LOCKED&lt;/code&gt; to implement a highly concurrent queue will not work (you'll just get bombarded with serialization failures).&lt;/li&gt;
&lt;li&gt;Make sure you have indexes that support your queries, since transactions that have to do full table scans can greatly increase the amount of serialization failures.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Sometimes having to worry about race conditions is a "good problem to have", since it suggests that you have a lot of users using your system. Hopefully they're paying you!&lt;/p&gt;

&lt;p&gt;But as the user base grows, race conditions can cause more and more mysterious and crippling faults, and can sometimes even be caused on purpose by exploiters.&lt;/p&gt;

&lt;p&gt;I hope this article gave you some new techniques to deal with these cases, and if you know of any other good ones, please share in the comments!&lt;/p&gt;

&lt;p&gt;While writing this, I had a billion ideas for additional cases, footnotes, and asterisks, but couldn't include it all without making quite a mess. If you have any questions or are interested in additional cases, feel free to comment!&lt;/p&gt;

</description>
      <category>sql</category>
      <category>javascript</category>
      <category>softwareengineering</category>
      <category>database</category>
    </item>
    <item>
      <title>🤖Discord Bot Programmed ... in Discord!</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Fri, 28 Apr 2023 12:30:00 +0000</pubDate>
      <link>https://dev.to/mistval/discord-bot-programmed-in-discord-22c</link>
      <guid>https://dev.to/mistval/discord-bot-programmed-in-discord-22c</guid>
      <description>&lt;p&gt;Meet my bot &lt;strong&gt;Kiryu&lt;/strong&gt;. His specialty is downloading JavaScript code from a Discord channel and executing it. To have Kiryu automatically add a role to new members who join my server, I write this message in the code channel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzuw3ggps32jl7igd0zbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzuw3ggps32jl7igd0zbu.png" alt=" " width="648" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kiryu sees the message, extracts the JavaScript code, and &lt;code&gt;eval()&lt;/code&gt;s it. Now whenever a new member joins, he will give them a role.&lt;/p&gt;

&lt;p&gt;Here's another one. This one makes Kiryu automatically ban anyone who sends a message including a banned word:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2atwdidu7r4i9lh3nrx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2atwdidu7r4i9lh3nrx.png" alt=" " width="642" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kiryu re-evaluates code when he starts, when a new message is created in the code channel, when a code message is updated, and when a code message is deleted, so his code always stays in sync with the code I have written in the code channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;Check out Kiryu's core code &lt;a href="https://github.com/mistval/kiryu" rel="noopener noreferrer"&gt;here&lt;/a&gt;. It's just one source file, less than 200 lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run Your Own Instance
&lt;/h2&gt;

&lt;p&gt;If you want to try running your own instance, I pushed an image to Docker Hub. You can easily launch an instance using Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; kiryu &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PROGRAMMER_IDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'YOUR USER ID HERE'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'YOUR BOT TOKEN HERE'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;CODE_CHANNEL_IDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'YOUR CODE CHANNEL ID HERE'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;LOG_CHANNEL_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'YOUR LOG CHANNEL ID HERE'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  mistval/kiryu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't know how to create a bot account, check out the &lt;a href="https://www.freecodecamp.org/news/create-a-discord-bot-with-python/" rel="noopener noreferrer"&gt;start of this great article&lt;/a&gt; and follow the instructions up until you get a bot token, which you can then substitute into the command above, along with your user ID, the ID of the channel you want to write code in, and the channel where you want errors to be reported to.&lt;/p&gt;

&lt;p&gt;Once the bot is online, try writing this in your code channel, surrounded by &lt;code&gt;js&lt;/code&gt; code tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;messageHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhw0rn5scw864k759gbr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhw0rn5scw864k759gbr.png" alt=" " width="447" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bot should react with a green checkmark. Then if you say &lt;code&gt;hello&lt;/code&gt; (in a different channel), the bot should respond with &lt;code&gt;world&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Events
&lt;/h2&gt;

&lt;p&gt;As you may have noticed, Kiryu has a built-in &lt;code&gt;messageHandlers&lt;/code&gt; array as a convenience. Any function added to that array will be called for every new message that isn't a code message and isn't from a bot. Errors thrown from inside of message handler functions will be automatically reported to the log channel.&lt;/p&gt;

&lt;p&gt;For other types of events, you can subscribe to them directly on the &lt;code&gt;bot&lt;/code&gt; object, as shown in the very first image in this article when I subscribe to the &lt;code&gt;guildMemberAdd&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;You can see the available events in the &lt;a href="https://abal.moe/Eris/docs/0.16.1/Client#event-callCreate" rel="noopener noreferrer"&gt;eris&lt;/a&gt; documentation here. Eris is a Discord bot library for Node.js. It's no longer well-maintained and it has never been as popular as the &lt;code&gt;discord.js&lt;/code&gt; library, it's just what I'm personally used to, since I like it for performance reasons.&lt;/p&gt;

&lt;p&gt;One gotcha here is that you have to be careful of double-subscribing to events, since the code in the code message may be evaluated multiple times. That's why I run &lt;code&gt;bot.removeAllListeners('guildMemberAdd');&lt;/code&gt; before subscribing to the event in the earlier example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Module Loader
&lt;/h2&gt;

&lt;p&gt;Kiryu can automatically download and install modules from npm. For one of Kiryu's fancier features, I use the &lt;code&gt;@pagerduty/pdjs&lt;/code&gt; module. It's a library for interfacing with the PagerDuty API. Using Kiryu, moderators in my server have a way to open a PagerDuty incident, which will call my cell phone and wake me up from my beauty sleep. I have this feature because I have another bot in 30,000+ servers, and if something goes really wrong with it, I want to know ASAP!&lt;/p&gt;

&lt;p&gt;The code for that feature is a bit longer, so I won't show it here, but here's a short example of using the &lt;a href="https://www.npmjs.com/package/hello-world-npm" rel="noopener noreferrer"&gt;hello-world-npm&lt;/a&gt; module to re-implement the above hello world command:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujwtgopd48ucg3na30ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujwtgopd48ucg3na30ok.png" alt=" " width="510" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If Kiryu doesn't have that module downloaded already, he'll download it and exit. When he restarts (which he will do automatically, if you use the Docker command above) he will have the module available. (I couldn't find a way to load a new module into a running process)&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Examples
&lt;/h2&gt;

&lt;p&gt;If you want to see all the code that I'm running on Kiryu, feel free to check &lt;a href="https://discord.gg/V4s8n275QE" rel="noopener noreferrer"&gt;my server&lt;/a&gt; where I have the aforementioned PagerDuty command plus a few more, plus SQLite integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;I would be remiss if I didn't mention that downloading and &lt;code&gt;eval()&lt;/code&gt;ing code from a Discord server isn't exactly an inherently secure thing to do. Although Kiryu is written to only evaluate code in messages sent by users whose IDs you specify in the &lt;code&gt;PROGRAMMER_IDS&lt;/code&gt; environment variable, bugs in Kiryu or in Discord itself could lead to malicious code execution on the host machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So why am I coding a bot like this? Well, just because it's fun, and it's a cool thing to show off in my server. I wouldn't really recommend doing this for a major production bot, but it gets the job done for little chores in my server, and looks cool while doing it! If you're interested in trying Kiryu, or this technique more generally, I'd love to hear what you think in the comments!&lt;/p&gt;

</description>
      <category>discord</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>🏺Database Architecture - History Over State</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Sun, 02 Apr 2023 01:14:41 +0000</pubDate>
      <link>https://dev.to/mistval/database-architecture-history-over-state-3m8o</link>
      <guid>https://dev.to/mistval/database-architecture-history-over-state-3m8o</guid>
      <description>&lt;p&gt;What should a database store? There are two broad ways of thinking about it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A database should store state&lt;/li&gt;
&lt;li&gt;A database should store history&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Engineers tend to gravitate towards treating their database as a store of &lt;strong&gt;state&lt;/strong&gt;. It is the more obvious approach, and it tends to be a bit simpler.&lt;/p&gt;

&lt;p&gt;But it turns out that treating your database as a store of &lt;em&gt;history&lt;/em&gt; instead can make your applications much more flexible and resilient. Let us take a look...&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the difference?
&lt;/h2&gt;

&lt;p&gt;Let us pretend we have a simple web game and we want to display a leaderboard to our users. Each match has one or more players, and each player earns zero or more points. We want to show a ranking of players by how many points they have earned in total:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Username&lt;/th&gt;
&lt;th&gt;Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;John&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ife&lt;/td&gt;
&lt;td&gt;125&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amir&lt;/td&gt;
&lt;td&gt;98&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jane&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  If you treat the database as a state store
&lt;/h3&gt;

&lt;p&gt;The engineer who treats their database as a state store simply makes a table to store the state of the leaderboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;points_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They start updating this new table whenever a player earns points, and they run a simple query on it to get the top 20 players:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  If you treat the database as a history store
&lt;/h3&gt;

&lt;p&gt;The engineer who treats their database as a history store approaches this much differently. They do not store a leaderboard state. Rather, they store a match history. They create two tables like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"match"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;match_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITHOUT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITHOUT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;map_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;game_mode&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;match_player&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;match_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="nv"&gt;"match"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a match happens, the relevant information is inserted into the &lt;code&gt;match&lt;/code&gt; and &lt;code&gt;match_player&lt;/code&gt; tables. Now, to calculate the leaderboard, the engineer queries the database like so to aggregate all of the match history and dynamically generate a leaderboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;match_player&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than thinking "I need to store a leaderboard in the database", this engineer thinks "I need to store as much history and useful information in the database as I can. Oh and as an aside, that data needs to be at least sufficient to generate a leaderboard".&lt;/p&gt;

&lt;p&gt;Perhaps you noticed that their leaderboard query does not even use the &lt;code&gt;match&lt;/code&gt; table (it only uses &lt;code&gt;match_player&lt;/code&gt;). Despite not needing &lt;code&gt;game_mode&lt;/code&gt; and the other data there immediately, this engineer decided that it is an important part of the application's history, and therefore should be stored.&lt;/p&gt;

&lt;p&gt;To make querying a little easier, this engineer might decide to create a &lt;code&gt;leaderboard&lt;/code&gt; &lt;a href="https://www.postgresql.org/docs/current/sql-createview.html" rel="noopener noreferrer"&gt;view&lt;/a&gt;, which is basically a saved query that can be queried like a table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;match_player&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  In come the bugs and new features
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The bug -&lt;/strong&gt; One day you discover that a bug in your game is being exploited, and some users have been able to score an unfair number of points. You look back at your git history and determine that the bug was introduced on April 4. You look back in your logs and find some match IDs where the bug was certainly exploited, and you also determine that if anyone was able to score more than 50 points in a single match, they must have been exploiting.&lt;/p&gt;

&lt;p&gt;What do you do? If you only have a stateful &lt;code&gt;leaderboard&lt;/code&gt; table, the situation is more difficult. Somehow you have to try to determine which users have exploited the bug. Then what? Delete them from the leaderboard entirely? Deduct some of their points? How many?&lt;/p&gt;

&lt;p&gt;For the engineer who stored match history, the situation is easier. They can delete the bugged matches, and any that happened after April 4 where a player scored more than 50 points. Problem solved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The new feature -&lt;/strong&gt; One day you decide you want to create monthly leaderboards. If you went with the state-store architecture, you need to create at least one entirely new table, and you will not be able to show leaderboards for months that have already passed. If you went with the history-store architecture, you already have all of the data you need retroactively, and you just need to write a new query.&lt;/p&gt;

&lt;p&gt;We can never know what the future will hold, but as we can see, the engineer who treats their database as a history store is much more equipped to face whatever requirements come their way!&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;One might point out that constantly aggregating over an entire table of match history to compute a leaderboard is not efficient. While this approach scales further than you might think, at some point it really does become too much. What should we do then?&lt;/p&gt;

&lt;p&gt;Fortunately, we do not need to ditch the history-centric architecture. There are at least a couple of big guns we can bring out to greatly improve performance:&lt;/p&gt;

&lt;h3&gt;
  
  
  Materialized Views
&lt;/h3&gt;

&lt;p&gt;Some databases, such as PostgreSQL, have first-class support for a concept referred to as &lt;a href="https://www.postgresql.org/docs/current/rules-materializedviews.html" rel="noopener noreferrer"&gt;materialized views&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A materialized view is a table whose contents are computed based on a query. If you are familiar with regular (non-materialized) views, the main difference is that a materialized view is stored on disk. The contents of a materialized view are only re-computed when requested, via running the &lt;code&gt;REFRESH MATERIALIZED VIEW&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;In the leaderboard example, the engineer who went with the history-store architecture can create a materialized view exactly like they created the regular view, just by adding the &lt;code&gt;MATERIALIZED&lt;/code&gt; keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;match_player&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And they can query this &lt;code&gt;leaderboard&lt;/code&gt; materialized view exactly like they would query a table. Even if the &lt;code&gt;match_player&lt;/code&gt; table has a huge number of records, fetching the top 20 scorers will be fast, because the rows in the materialized view are pre-computed and stored on disk.&lt;/p&gt;

&lt;p&gt;To recompute the leaderboard every hour, you could use &lt;code&gt;pg_cron&lt;/code&gt; to set up a cron job in the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;pg_cron&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'leaderboard-refresh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'0 * * * *'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'REFRESH MATERIALIZED VIEW CONCURRENTLY leaderboard'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Your PostgreSQL installation might already have &lt;code&gt;pg_cron&lt;/code&gt; available, but if not, you would need to &lt;a href="https://github.com/citusdata/pg_cron" rel="noopener noreferrer"&gt;install it&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Then your leaderboard will automatically update every hour.&lt;/p&gt;

&lt;p&gt;An obvious shortcoming here is that the leaderboard data can be up to one hour stale. Another is that, even if we only refresh the view once per hour, there is still some point at which that might become unsustainable in terms of performance (it would likely take many billions of matches before we get there, but this might be more relevant if you want to update the leaderboard say, every five seconds).&lt;/p&gt;

&lt;p&gt;Despite these shortcomings, a materialized view is often a great solution. But if you need completely up-to-date data, or you have an un-utterably huge amount of data to aggregate over, the next technique might serve you better...&lt;/p&gt;

&lt;h3&gt;
  
  
  Incremental View Maintenance (IVM)
&lt;/h3&gt;

&lt;p&gt;Thinking about it logically, when a player wins a match, do we really need to re-compute the entire leaderboard? Couldn't we just find that user's leaderboard record, and update their points?&lt;/p&gt;

&lt;p&gt;Yes indeed we can! This approach is sometimes referred to as &lt;a href="https://wiki.postgresql.org/wiki/Incremental_View_Maintenance" rel="noopener noreferrer"&gt;Incremental View Maintenance&lt;/a&gt;. It has been proposed as a first-class feature in PostgreSQL, but is not yet shipped. However we can implement it ourselves.&lt;/p&gt;

&lt;p&gt;The naive way to do it is to create a &lt;code&gt;leaderboard&lt;/code&gt; table, then whenever you insert a new record into the &lt;code&gt;match_player&lt;/code&gt; table, you also have code to do an update on the &lt;code&gt;leaderboard&lt;/code&gt; table to update the participants' points.&lt;/p&gt;

&lt;p&gt;This can be an okay approach, but it requires vigilance to make sure your code always updates the &lt;code&gt;leaderboard&lt;/code&gt; table when it updates the &lt;code&gt;match_player&lt;/code&gt; table. A better way is to use triggers. You can write a trigger function which runs automatically whenever you update the &lt;code&gt;match_player&lt;/code&gt; table. Whether you add new match records, delete records, or update records, your trigger function can update the &lt;code&gt;leaderboard&lt;/code&gt; table appropriately. Best of all, the trigger function lives in the database itself, and the database takes care of running it whenever the &lt;code&gt;match_player&lt;/code&gt; table changes. A trigger function for this scenario could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;update_leaderboard_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INSERT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="c1"&gt;-- If a new match_player record is being inserted, we&lt;/span&gt;
    &lt;span class="c1"&gt;-- do an upsert on the leaderboard table to increase&lt;/span&gt;
    &lt;span class="c1"&gt;-- the points of the leaderboard record corresponding&lt;/span&gt;
    &lt;span class="c1"&gt;-- to that username in the new match record.&lt;/span&gt;
    &lt;span class="c1"&gt;-- IMPORTANT: The leaderboard table must have a unique&lt;/span&gt;
    &lt;span class="c1"&gt;-- index on the username column&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;ELSEIF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DELETE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="c1"&gt;-- If a match_player record is being deleted, we update&lt;/span&gt;
    &lt;span class="c1"&gt;-- the leaderboard table and decrease the leaderboard&lt;/span&gt;
    &lt;span class="c1"&gt;-- points of the user whose match record was deleted.&lt;/span&gt;
    &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;ELSEIF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="c1"&gt;-- If a match_player record is being updated, we calculate&lt;/span&gt;
    &lt;span class="c1"&gt;-- the difference in points between the new record&lt;/span&gt;
    &lt;span class="c1"&gt;-- and the old record and add that to the points of&lt;/span&gt;
    &lt;span class="c1"&gt;-- the user whose match record was updated (the difference&lt;/span&gt;
    &lt;span class="c1"&gt;-- might be negative, in which case the user's position&lt;/span&gt;
    &lt;span class="c1"&gt;-- on the leaderboard would go down)&lt;/span&gt;
    &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;update_leaderboard_trigger&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;match_player&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;update_leaderboard_function&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That might look big and scary if you haven't worked with triggers before, but what it does is pretty simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you &lt;code&gt;INSERT&lt;/code&gt; into the &lt;code&gt;match_player&lt;/code&gt; table, it adds points to the &lt;code&gt;leaderboard&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;If you &lt;code&gt;DELETE&lt;/code&gt; from the &lt;code&gt;match_player&lt;/code&gt; table, it deducts points from the &lt;code&gt;leaderboard&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;If you &lt;code&gt;UPDATE&lt;/code&gt; the &lt;code&gt;match_player&lt;/code&gt; table, it calculates the change in points (since the function has access to both the &lt;code&gt;NEW&lt;/code&gt; and &lt;code&gt;OLD&lt;/code&gt; version of the row) and applies the difference to the &lt;code&gt;leaderboard&lt;/code&gt; table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So this trigger automatically keeps your leaderboard up to date in real time! (as long as you do not &lt;code&gt;TRUNCATE&lt;/code&gt; the &lt;code&gt;match_player&lt;/code&gt; table, at least).&lt;/p&gt;

&lt;p&gt;By using materialized views and/or IVM, you get more or less the best of both worlds. You get performance that's nearly as good as the simpler state-store architecture, while having the flexibility of the history-store architecture. These approaches do kind of let some statefulness creep back in, but it can be worth it for the performance gains, and the history still remains the ultimate source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Should a database store state, or should it store history and compute state from history? It turns out that the history-store architecture holds a lot of really compelling advantages. Personally, it is how I always design database schemas now, although I certainly do make compromises in some scenarios.&lt;/p&gt;

&lt;p&gt;What do you think? Are you familiar with both of these architectures? Is there a third, fourth, or fifth that we should be talking about?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I took a few shortcuts to cut down on a line of code here and there, such as not always specifying primary keys, and using username as a unique ID. These should not be considered good practices.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>postgres</category>
      <category>programming</category>
    </item>
    <item>
      <title>🙏Please, specify your units! 📏</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Sat, 10 Sep 2022 01:20:26 +0000</pubDate>
      <link>https://dev.to/mistval/please-specify-your-units-5ekl</link>
      <guid>https://dev.to/mistval/please-specify-your-units-5ekl</guid>
      <description>&lt;p&gt;What sucks about this code?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mset&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myKey2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;obj2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not clear at all clear if the &lt;code&gt;ttl&lt;/code&gt; is 10,000 &lt;em&gt;milliseconds&lt;/em&gt;, &lt;em&gt;seconds&lt;/em&gt;, or something else. Just glancing at this code (and knowing that JavaScript &lt;code&gt;setTimeout()&lt;/code&gt; uses milliseconds), you might be tempted to think it's in milliseconds. Wrong! It's in seconds!&lt;/p&gt;

&lt;p&gt;This snippet is example code from the README of &lt;a href="https://www.npmjs.com/package/node-cache" rel="noopener noreferrer"&gt;node-cache&lt;/a&gt;. This type of unclear code is really prevalent, so I'm not trying to pick on them in particular. Fortunately, the README does say that it's in seconds, but you have to &lt;a href="https://www.dictionary.com/browse/rtfm" rel="noopener noreferrer"&gt;RTFM&lt;/a&gt; to be sure, and who's got time for that?&lt;/p&gt;

&lt;p&gt;Wouldn't it be nice if the &lt;code&gt;ttl&lt;/code&gt; property were named &lt;code&gt;ttlSeconds&lt;/code&gt;? Then it's instantly clear what it means to whoever is reading the code.&lt;/p&gt;

&lt;p&gt;Or maybe there's an even better solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Datetimes
&lt;/h2&gt;

&lt;p&gt;It's also pretty common for datetimes to be represented as an integer number of milliseconds (or seconds) since the Unix epoch ( 00:00:00 UTC on 1 January 1970). For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myBirthday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;700876800000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sucks too. You might recognize this as very likely being a number of milliseconds since the Unix epoch, but you have no idea what date this represents.&lt;/p&gt;

&lt;p&gt;How about this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myBirthday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1992-03-18T00:00:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now whoever's reading this code knows exactly what date it represents, and code that accesses &lt;code&gt;myBirthday&lt;/code&gt; does too. Surrounding code doesn't need tribal knowledge about whether this &lt;code&gt;number&lt;/code&gt; is a number of seconds or milliseconds. It's a &lt;code&gt;Date&lt;/code&gt;, and has a very specific meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  TimeSpans
&lt;/h2&gt;

&lt;p&gt;Jumping back to &lt;code&gt;TTL&lt;/code&gt; (time to live) again. A TTL is not a specific date and time, but a length of time (one second, one year, etc), so we can't use &lt;code&gt;Date&lt;/code&gt; to represent it.&lt;/p&gt;

&lt;p&gt;Naming your TTL property &lt;code&gt;ttlSeconds&lt;/code&gt; for clarity is a fine solution in a lot of cases, but what if there were a datatype similar to &lt;code&gt;Date&lt;/code&gt; for this?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.timespan?view=net-6.0" rel="noopener noreferrer"&gt;Some languages&lt;/a&gt; actually have such features built in. JavaScript doesn't, but we can use a package like &lt;a href="https://npm.runkit.com/timespan" rel="noopener noreferrer"&gt;timespan&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;timespan&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timespan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timespan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;totalMilliseconds&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 50000&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;totalSeconds&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 50&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;totalMinutes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 0.8333333333333334&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can specify your TTL in whatever units you want, and the code you pass your TTL to can read it in whatever units it wants. You don't have to agree on units at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.simscale.com/blog/nasa-mars-climate-orbiter-metric/" rel="noopener noreferrer"&gt;If only NASA used constructs like this&lt;/a&gt;, they may have been able to save a few bucks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Just a short one I wanted to get off my chest here. Many bugs have their roots in mix-ups about what units are being used. Fortunately we have some tools at our disposal to mitigate these sort of risks. I wish I saw them used more often!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>codequality</category>
    </item>
    <item>
      <title>⚡ When Faster Isn't Better</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Sat, 06 Aug 2022 00:21:00 +0000</pubDate>
      <link>https://dev.to/mistval/when-faster-isnt-better-4f19</link>
      <guid>https://dev.to/mistval/when-faster-isnt-better-4f19</guid>
      <description>&lt;p&gt;Lately I keep hearing about &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;Bun&lt;/a&gt;, a new JavaScript runtime meant to compete with Node.js and Deno. &lt;em&gt;It's fast&lt;/em&gt;, they're saying, and indeed two of the three bullet points on its homepage are different ways of saying "it's fast".&lt;/p&gt;

&lt;p&gt;Fast is sexy, developers love fast things, everyone does. But this kind of fast isn't likely to do you much good, and we're going to talk about why.&lt;/p&gt;

&lt;p&gt;To be clear, I'm not trying to pick on Bun in particular. I actually know very little about it beyond what's on its homepage and what people say about it. It might be great. Regardless, the claims it makes and the way people talk about it make it a great starting point for this discussion, which is about math more than anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  "This Kind of Fast"
&lt;/h2&gt;

&lt;p&gt;The graphics on Bun's homepage show it performing approximately 3x faster than Node.js on three &lt;em&gt;CPU bound&lt;/em&gt; tasks which are typical of &lt;em&gt;web server applications&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;1: Rendering and serving (React) webpages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3h4fsrn2go3o0kgii9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3h4fsrn2go3o0kgii9s.png" alt="Chart showing page render throughput for Bun, Node, and Deno" width="412" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2: Querying a SQLite database&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gybh53lel4vyrem4tut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gybh53lel4vyrem4tut.png" alt="Chart showing SQLite query throughput for Bun, Node, and Deno" width="445" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3: Hashing a string&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffw7t5u0ooa9p1g0wt0sm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffw7t5u0ooa9p1g0wt0sm.png" alt="Chart showing string hashing throughput for Bun, Node, and Deno" width="454" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Billion Dollar Server
&lt;/h2&gt;

&lt;p&gt;While the graphics above show the humble Node.js server rendering and serving about 16,000 pages per second, that's for an unrealistically simple case: a page that just says "Hello World" and is presumably served to a client running on the same machine.&lt;/p&gt;

&lt;p&gt;The humble Node.js server won't get anywhere near that throughput on real-world workloads. As a rough estimate, let's suppose that a Next.js application running on a single-core Node.js server can serve approximately 100 pages per second. And let's suppose that Fast Engine's 3x performance (again, I'm not picking on Bun, I swear!) holds true here, and it can serve 300 pages per second.&lt;/p&gt;

&lt;p&gt;Another way to look at this is: Fast Engine can serve 300 pages per second running on just one server. If you're using Node.js, you would have to buy an extra two servers to achieve that same throughput.&lt;/p&gt;

&lt;p&gt;Here's the thing. Three hundred pages per second is about &lt;em&gt;twenty five million&lt;/em&gt; per day. That's about &lt;em&gt;seven hundred and fifty million&lt;/em&gt; per month.&lt;/p&gt;

&lt;p&gt;Let's slap Google AdSense on this baby:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy307fyrxdzmstqhby6w1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy307fyrxdzmstqhby6w1.png" alt="Google Adsense calculator showing a mountain of money made from 10 million monthly page views" width="800" height="829"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Turns out their calculator only goes up to ten million page views per month, which would net us about $700,000 per year.&lt;/p&gt;

&lt;p&gt;Meanwhile an extra two single-core servers will cost you &lt;a href="https://www.vultr.com/pricing/" rel="noopener noreferrer"&gt;around $10-$20&lt;/a&gt; per month.&lt;/p&gt;

&lt;p&gt;So Fast Engine is saving you ten bucks per month on your multi-million dollar gravy train. See the problem? You have better things to worry about than ten dollars, for example, what kind of private jet you're going to buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 93.333 Millisecond Request
&lt;/h2&gt;

&lt;p&gt;Okay, but if Fast Engine's throughput advantages don't really matter, what about its improved response time? Fast Engine finishes rendering the page faster, so visitors see it faster, and are more satisfied, right?&lt;/p&gt;

&lt;p&gt;Actually, they won't notice or care. Roughly speaking, if the Node.js server can serve 100 pages per second, that implies that it spends about 10 milliseconds of CPU time on each page render. Meanwhile Fast Engine, serving 300 pages per second, spends about 3.33 milliseconds of CPU time on each page.&lt;/p&gt;

&lt;p&gt;But the &lt;em&gt;request duration&lt;/em&gt; from the user's browser to the server and back is dominated by the latency between them and the server, and potentially also the latency between the server and other APIs and databases. Consequently, running the same code on Fast Engine might reduce the total request duration from, say, 100ms to 93.333ms. Again, it's an improvement on paper, but no one will notice or care.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $10,000 Developer
&lt;/h2&gt;

&lt;p&gt;Let's say you're not convinced, and you decide to go with Fast Engine anyway, and hire a developer to build your server for you.&lt;/p&gt;

&lt;p&gt;You might find that this project, which would take, say, three months to build on Node.js, will take four months to build on Fast Engine, due to Fast Engine being less compatible with popular modules, having a smaller community to get help from, etc.&lt;/p&gt;

&lt;p&gt;In the US, employing a developer full-time for a month is likely to cost you at least ten thousand US dollars in total expenses. So while you may have saved ten dollars per month in server costs &lt;em&gt;someday down the line when your service gets popular&lt;/em&gt;, and you may have shaved a few milliseconds off of request duration, you paid $10,000 for that, and got your product to market a month later. Those are not good trade-offs.&lt;/p&gt;

&lt;p&gt;To be clear, it's not necessarily the case that the "faster" engine will take longer to write code for. That might not be true. The point is that before getting starry-eyed about fast technologies, there are other factors to consider, and in most cases you will save a &lt;em&gt;lot&lt;/em&gt; more money if you prioritize developer efficiency over software execution efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Number-Crunching Desktop
&lt;/h2&gt;

&lt;p&gt;Time to start putting in a few asterisks. So far, we've been talking about web server applications, where the workloads tend to be IO-bound and horizontal scaling is relatively cheap, easy, and infinite.&lt;/p&gt;

&lt;p&gt;These arguments don't necessarily apply well to other domains. For example in a graphics engine, rendering 3x as many frames per second would be a tremendous improvement. That would triple the frame rate!&lt;/p&gt;

&lt;p&gt;Absolutely, screaming fast code has an important place in the world. But that place is not JavaScript code running on web servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Snappy Lambda
&lt;/h2&gt;

&lt;p&gt;One final thing I want to discuss is serverless compute platforms such as AWS Lambda and Google Cloud Functions.&lt;/p&gt;

&lt;p&gt;If there's anywhere we need fast JavaScript servers, this is it, due to the problem of &lt;a href="https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/" rel="noopener noreferrer"&gt;cold starts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A cold start occurs when the serverless platform needs to spin up a new instance of your code in order to service increased traffic. This can involve unzipping dependencies, booting the runtime, establishing database sessions, and a variety of other relatively slow operations.&lt;/p&gt;

&lt;p&gt;All of this can result in requests to your server sporadically being noticeably slow. If Fast Engine can significantly reduce how long cold starts take, that would be nice.&lt;/p&gt;

&lt;p&gt;Still, the potential benefits here are fairly limited, as: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"According to an analysis of production Lambda workloads, cold starts typically occur in under 1% of invocations. The duration of a cold start varies from under 100 ms to over 1 second." - &lt;em&gt;Amazon&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Furthermore, a lot of the slow operations involved in cold starts have little to nothing to do with the engine that your code is running on, and would not be improved by a faster engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In a vacuum, &lt;code&gt;3x performance&lt;/code&gt; sounds great, and understandably sends a tingle of joy up the spine of any developer. But in truth it's pretty meaningless without a more careful examination of the use case and an understanding of exactly which sections of it are getting faster and which aren't.&lt;/p&gt;

&lt;p&gt;Sometimes the slow thing is still plenty fast, and the fast thing isn't as much faster as it first appears. Sometimes it doesn't really matter much either way.&lt;/p&gt;

&lt;p&gt;The average JavaScript web server is one such case. That being said, every situation is unique and every tool has its place.&lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note #1 - The SQLite benchmark is more CPU bound than one might imagine. SQLite has internal caching, which lives in the same process as the client. Running the same query over and over will read data from the cache, though its size is limited.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>serverless</category>
      <category>webdev</category>
    </item>
    <item>
      <title>🎩The Magic of || and &amp;&amp; in JavaScript</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Sat, 16 Jul 2022 20:34:18 +0000</pubDate>
      <link>https://dev.to/mistval/the-magic-of-and-in-javascript-49k4</link>
      <guid>https://dev.to/mistval/the-magic-of-and-in-javascript-49k4</guid>
      <description>&lt;p&gt;In JavaScript, it's common to see code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might know what this code does, but maybe you don't know &lt;em&gt;why&lt;/em&gt;. A couple of simple but very clever core language mechanics enable us to write code like this. Despite the clickbait title, there is (sadly) no magic involved. Let's take a look at what's really going on here.&lt;/p&gt;

&lt;p&gt;In case you don't know what this code does: the former snippet provides a default value for the &lt;code&gt;color&lt;/code&gt; variable. If &lt;code&gt;options.color&lt;/code&gt; is a &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Truthy" rel="noopener noreferrer"&gt;truthy&lt;/a&gt; value (such as a non-empty string), then &lt;code&gt;color&lt;/code&gt; gets set to that value. But if &lt;code&gt;options.color&lt;/code&gt; is a &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Falsy" rel="noopener noreferrer"&gt;falsy&lt;/a&gt; value, such as undefined, then &lt;code&gt;color&lt;/code&gt; gets set to the default value of &lt;code&gt;'red'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The latter snippet guards against &lt;code&gt;data&lt;/code&gt; being undefined (or null). If &lt;code&gt;data&lt;/code&gt; is undefined, then trying to reference &lt;code&gt;data.name&lt;/code&gt; directly will result in an error being thrown. But if we use the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; trick, there will be no error in such a case. &lt;code&gt;name&lt;/code&gt; will just be set to undefined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boolean Expressions and Short-Circuit Evaluation
&lt;/h2&gt;

&lt;p&gt;To understand how this works, it's important to first understand &lt;a href="https://en.wikipedia.org/wiki/Short-circuit_evaluation" rel="noopener noreferrer"&gt;short-circuit evaluation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In a language like JavaScript, short-circuit evaluation means that when a JavaScript engine evaluates a boolean expression (such as &lt;code&gt;a || b&lt;/code&gt;), it will only evaluate as much of the expression as it needs to, in order to find the answer.&lt;/p&gt;

&lt;p&gt;In the expression &lt;code&gt;a || b&lt;/code&gt; (meaning &lt;code&gt;a&lt;/code&gt; OR &lt;code&gt;b&lt;/code&gt;), think logically about what happens if &lt;code&gt;a&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;. If &lt;code&gt;a&lt;/code&gt; is true, then it doesn't even matter what &lt;code&gt;b&lt;/code&gt; is, right? &lt;code&gt;b&lt;/code&gt; could be &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, or anything else, and the result of &lt;code&gt;a || b&lt;/code&gt; will be true regardless. If &lt;code&gt;a&lt;/code&gt; is true, then &lt;code&gt;a&lt;/code&gt; OR &lt;code&gt;b&lt;/code&gt; is also true, period.&lt;/p&gt;

&lt;p&gt;JavaScript engines are smart enough to know this too, and in practice it means that if &lt;code&gt;a&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; (or any other truthy value), the engine won't even bother looking at &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To see this in action, you could try running the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While it's illegal to try to reference a property on &lt;code&gt;null&lt;/code&gt;, this code does not error, because the engine never evaluates the right-hand-side of the expression! It doesn't need to, because the left-hand-side is sufficient to answer the question on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boolean Operators Don't Return Boolean Values!
&lt;/h2&gt;

&lt;p&gt;The second piece of the puzzle is what's returned by boolean operators, such as &lt;code&gt;||&lt;/code&gt; and &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;. Often, developers assume that these operators return boolean values, but that's actually not the case.&lt;/p&gt;

&lt;p&gt;Try running this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does NOT return &lt;code&gt;true&lt;/code&gt;. It returns &lt;code&gt;'a'&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;And this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returns &lt;code&gt;'b'&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;What's going on here? This is the part that I find really clever. Boolean expressions do not return boolean values, they actually return &lt;em&gt;the last thing that was evaluated&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to short-circuit evaluation, when evaluating the expression &lt;code&gt;'a' || 'b'&lt;/code&gt;, the engine only evaluates the left-hand-side of the expression. That means that &lt;code&gt;'a'&lt;/code&gt; is the last thing that it evaluates in this expression. Therefore, the return value of the expression is &lt;code&gt;'a'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the expression &lt;code&gt;'a' &amp;amp;&amp;amp; 'b'&lt;/code&gt;, the left-hand-side of the expression is truthy, but because this is an &lt;code&gt;AND&lt;/code&gt; operation, the engine still has to look at the right-hand-side to check if it's truthy or not. If it is, the expression is true. If it's not, then the expression is false. That means in this case, the final thing that's evaluated in this expression is &lt;code&gt;'b'&lt;/code&gt;. That's why &lt;code&gt;'b'&lt;/code&gt; is the return value of the expression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it Together
&lt;/h2&gt;

&lt;p&gt;That's the theory, but we can boil it down into simpler-sounding rules that might make it easier to use in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;||&lt;/code&gt; operator returns the thing on left-hand-side, if that thing is truthy. If it's not, it returns the thing on the right-hand-side.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; operator returns the thing on the left-hand-side, if that thing is &lt;em&gt;falsy&lt;/em&gt;. If it's not, it returns the thing on the right-hand-side.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's look at the examples from the top of the article again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applying rule #1, if &lt;code&gt;options.color&lt;/code&gt; is truthy, the expression returns &lt;code&gt;options.color&lt;/code&gt;. Otherwise, it returns &lt;code&gt;'red'&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applying rule #2, if &lt;code&gt;data&lt;/code&gt; is falsy, the expression returns &lt;code&gt;data&lt;/code&gt;. Otherwise, it returns &lt;code&gt;data.name&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Fancy
&lt;/h2&gt;

&lt;p&gt;You can get pretty fancy with this. For example, if you want to look at a property deep inside of an object, and provide a default value if it's not there, you could write code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name Unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;data&lt;/code&gt; is truthy, it will return the thing on the right. The thing on the right is yet another boolean expression, where if &lt;code&gt;data.profile&lt;/code&gt; is truthy, it too will return the thing on the right. If any of this is falsy, then &lt;code&gt;(data &amp;amp;&amp;amp; data.profile &amp;amp;&amp;amp; data.profile.name)&lt;/code&gt; will return the last thing it evaluated. Since that thing would be falsy, &lt;code&gt;||&lt;/code&gt; will return the thing on the right-hand-side, &lt;code&gt;'Name Unknown'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Does that sound a bit convoluted? I hope it does, and I wouldn't recommend writing code like this. It can get pretty hard to understand. But if you do understand it, then congratulations, you understand the magic of JavaScript boolean expressions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional Chaining and Nullish-Coalescing
&lt;/h3&gt;

&lt;p&gt;As an aside, while code like the above used to be somewhat common in JavaScript, the new-ish optional chaining and nullish-coalescing features give us a simpler way to do the same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name Unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won't dive into that, but it deserves mention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;JavaScript engines only evaluate the parts of a boolean expression that they need to, and boolean expressions return the last thing that was evaluated. That's it. It's a simple mechanism, but it enables some pretty magical-looking constructs. Hopefully this article helped unwind the mystery, and if you have any questions or comments please post them below!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Don't parse CSVs by hand!</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Wed, 11 May 2022 22:52:53 +0000</pubDate>
      <link>https://dev.to/mistval/dont-parse-csvs-by-hand-5944</link>
      <guid>https://dev.to/mistval/dont-parse-csvs-by-hand-5944</guid>
      <description>&lt;p&gt;I've seen custom JavaScript code to parse CSV data a number of times. It looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csvData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;csvData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So simple, and it may be fine if you're parsing a single static CSV file whose content you know. But if you're parsing unknown CSV files, don't do this, there are many ways it can break.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can Go Wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Escaped cells
&lt;/h3&gt;

&lt;p&gt;Columns in a CSV file may contain commas. Such columns are typically escaped with quotes. The raw data for a row with such a column might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name,Best Line
Hamlet,"To be, or not to be"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Though the second line has two commas, this produces a table with two columns, not three:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Best Line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hamlet&lt;/td&gt;
&lt;td&gt;To be, or not to be&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This breaks the simple CSV parser implementation which just splits on &lt;code&gt;,&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Line Endings
&lt;/h3&gt;

&lt;p&gt;The simple CSV parser implementation assumes LF line endings (&lt;code&gt;\n&lt;/code&gt;), but text files, including CSV files, created on Windows will often have CRLF line endings (&lt;code&gt;\r\n&lt;/code&gt;). Further, there may be line ending characters within escaped cells. Needless to say, these cases break the simple CSV parser implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do instead
&lt;/h2&gt;

&lt;p&gt;Bring in a library that's built for this. I like to use &lt;a href="https://www.npmjs.com/package/csv-parse" rel="noopener noreferrer"&gt;csv-parse&lt;/a&gt;. It's easy to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;csvParse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;csv-parse/sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csvData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;csvParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I know no one likes bringing in libraries to solve what seems to be a simple task, but parsing arbitrary CSVs correctly is actually a lot harder than it looks at first glance. Save yourself the pain, use someone else's battle-hardened parser.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Discord Webhook Powered Contact Form</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Thu, 27 Jan 2022 02:17:55 +0000</pubDate>
      <link>https://dev.to/mistval/discord-webhook-powered-contact-form-3lk6</link>
      <guid>https://dev.to/mistval/discord-webhook-powered-contact-form-3lk6</guid>
      <description>&lt;p&gt;Recently more and more people have been discovering the power of personal Discord servers. You can use them to store files, write notes, mess around with bots, and more.&lt;/p&gt;

&lt;p&gt;In this article, I will show you how to hook up a contact form on your website to send mail to your Discord server. It's free, it's easy, and it does not even require a backend server.&lt;/p&gt;

&lt;p&gt;Before getting started, you should know basic HTML and JavaScript, and you should have a Discord account and a private Discord server (use the plus button in the bottom left of the Discord desktop client to create one).&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Webhook
&lt;/h2&gt;

&lt;p&gt;First, we need to create a webhook in Discord. Decide which channel in your private server you want to receive mail in, and click the settings button. I'm going to use the &lt;code&gt;#general&lt;/code&gt; channel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnd762oid216cff506gbn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnd762oid216cff506gbn.png" alt="Location of the settings button" width="273" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the settings window, go to the &lt;code&gt;Integrations&lt;/code&gt; section, and click &lt;code&gt;Create Webhook&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcn6op8lo895y08a7pmv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcn6op8lo895y08a7pmv.png" alt="Location of the Create Webhook button" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the webhook has been created, give it a name (I chose &lt;code&gt;Contacts&lt;/code&gt;), and click &lt;code&gt;Copy Webhook URL&lt;/code&gt;. This will copy the webhook URL to your clipboard. We'll need it in a little bit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Contact Form
&lt;/h2&gt;

&lt;p&gt;This article is going to focus on how to call the webhook via JavaScript, so I'm going to gloss over the HTML part a bit. If you want to follow along, you can copy and paste this code into a file called &lt;code&gt;contact.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Bootstrap CSS --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"&lt;/span&gt; &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container mt-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;onsubmit=&lt;/span&gt;&lt;span class="s"&gt;"sendContact(event)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"emailInput"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter your email address&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"emailInput"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"messageInput"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter your message&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"messageInput"&lt;/span&gt; &lt;span class="na"&gt;rows=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's just really basic HTML boilerplate plus bootstrap to make things look slightly non-gross.&lt;/p&gt;

&lt;p&gt;If you open the &lt;code&gt;contact.html&lt;/code&gt; file in your browser, this is what you will see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pcsbiveivna7kne3keo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pcsbiveivna7kne3keo.png" alt="Basic HTML contact form" width="789" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you click the &lt;code&gt;Submit&lt;/code&gt; button, it will call the &lt;code&gt;sendContact&lt;/code&gt; function, which does nothing!&lt;/p&gt;

&lt;p&gt;So let's make it do something. Let's start writing code in the &lt;code&gt;sendContact()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;First, no surprises, let's prevent the default form submit action, and let's get the email address and message that the user input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;senderEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emailInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;senderMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messageInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next let's craft the body that we're going to send to the webhook. The body should be a Discord message object, which is clearly documented in the &lt;a href="https://discord.com/developers/docs/resources/channel#message-object" rel="noopener noreferrer"&gt;Discord API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In our case, we just want a message with a title and two sub-sections: &lt;code&gt;Sender&lt;/code&gt; and &lt;code&gt;Message&lt;/code&gt;. That's going to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webhookBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;embeds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact Form Submitted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sender&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;senderEmail&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;senderMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we just use &lt;code&gt;fetch&lt;/code&gt; to send the webhook. Remember that webhook URL you copied earlier? You'll need it here. Paste it in as the value of the &lt;code&gt;webhookUrl&lt;/code&gt; variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webhookUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR URL HERE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webhookUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webhookBody&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let's show an alert and tell the user whether the request was successful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I have received your message!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;There was an error! Try again later!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Refresh the page, type in an email and message, and click Submit.&lt;/p&gt;

&lt;p&gt;If you did everything right, you should hear a satisfying little &lt;em&gt;ting&lt;/em&gt; sound from your Discord client telling you that there has been a new message in your server. Check it out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F134vg9ofg2gg89cum9ej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F134vg9ofg2gg89cum9ej.png" alt="The content of the contact form sent to my Discord server" width="415" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With just a little bit of frontend code we now officially have our contact form sending mail to our private Discord server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;Here's the full code that I used for this demo. Remember to replace &lt;code&gt;YOUR URL HERE&lt;/code&gt; with your webhook URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Bootstrap CSS --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"&lt;/span&gt; &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container mt-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;onsubmit=&lt;/span&gt;&lt;span class="s"&gt;"sendContact(event)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"emailInput"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter your email address&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"emailInput"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"messageInput"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter your message&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"messageInput"&lt;/span&gt; &lt;span class="na"&gt;rows=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;senderEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emailInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;senderMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messageInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webhookBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;embeds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact Form Submitted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sender&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;senderEmail&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;senderMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webhookUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR URL HERE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webhookUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webhookBody&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I have received your message!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;There was an error! Try again later!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Word of Caution
&lt;/h2&gt;

&lt;p&gt;It's important to understand that putting your webhook link into your frontend code means that a malicious actor could take it and use it in a script to spam you or even send you nasty images.&lt;/p&gt;

&lt;p&gt;Fortunately, that's about the worst they could do. The link only allows sending messages in your server, and does not allow reading messages, taking any sort of administrative action, or really anything else.&lt;/p&gt;

&lt;p&gt;So while embedding the webhook link into your small, personal website is likely going to be fine, I would not do this for anything major, or if I had a lot of tech-savvy enemies. I also would not do this in a Discord channel that many other people have read access to.&lt;/p&gt;

&lt;p&gt;If you are concerned about these risks but you still want to use a Discord webhook for your contact form, you would need some sort of backend to be a middleman for the requests. In fact, I use that approach for my own personal site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blocked TLDs
&lt;/h2&gt;

&lt;p&gt;It has come to my attention that this method does not work for &lt;code&gt;*.tk&lt;/code&gt; domains. Discord seems to selectively not return the needed CORS headers for TLDs that it does not like. Besides getting a new domain, your only option involves proxying the request through a backend server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Getting contact forms to work well (for free) can actually be harder than it sounds. I used to use Gmail's SMTP server via my personal website's backend for this, but it would frequently stop working for "security reasons" until I went into my account and reminded Google that it's legit traffic. I ended up swapping in a Discord webhook instead and haven't looked back. It's super convenient and easy to set up, and has worked very reliably.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>discord</category>
      <category>webdev</category>
    </item>
    <item>
      <title>👻The Nightmarishly Dangerous Default Behavior of setInterval()</title>
      <dc:creator>Randall</dc:creator>
      <pubDate>Sat, 08 Jan 2022 18:07:14 +0000</pubDate>
      <link>https://dev.to/mistval/the-nightmarishly-dangerous-default-behavior-of-setinterval-1bng</link>
      <guid>https://dev.to/mistval/the-nightmarishly-dangerous-default-behavior-of-setinterval-1bng</guid>
      <description>&lt;p&gt;So you want to send an HTTP request every &lt;code&gt;delay&lt;/code&gt; number of milliseconds. Easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;doRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what if &lt;code&gt;delay&lt;/code&gt; is undefined?&lt;/p&gt;

&lt;p&gt;Here's where it gets scary. It turns out that if &lt;code&gt;delay&lt;/code&gt; is undefined (or null, NaN, etc), it will default to 1 millisecond! Enjoy your DoS attack!&lt;/p&gt;

&lt;p&gt;I've seen this issue affect production systems on two occasions now, and both instances caused significant damage and required emergency maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Safer setInterval()
&lt;/h2&gt;

&lt;p&gt;If you're concerned about this possibility, you can replace setInterval with safer code. Execute this code as early as possible during initialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oldSetInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2147483647&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid interval delay: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;oldSetInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This replaces the &lt;code&gt;setInterval&lt;/code&gt; function globally with a wrapper that will throw an error if the &lt;code&gt;delay&lt;/code&gt; argument is not a number or is outside of the valid range. No one likes errors, but it's usually better than accidentally making 1,000 requests per second!&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Implementations of setInterval()
&lt;/h2&gt;

&lt;p&gt;I've been talking about Node.js so far, and this behavior is clearly documented in the &lt;a href="https://nodejs.org/api/timers.html#setintervalcallback-delay-args" rel="noopener noreferrer"&gt;Node.js documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;setInterval()&lt;/code&gt; is actually not part of the ECMAScript standard, and some of its details may vary across different JavaScript environments.&lt;/p&gt;

&lt;p&gt;Here's some code you can execute to see what the default delay is in your environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`MS elapsed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks like in Firefox, the default delay is 16 ms, while in Chrome it's 4. While the Node.js documentation states that it's 1 ms, my testing suggests that it's very inconsistent in Node.js, much more so than in browsers. I'm not sure why.&lt;/p&gt;

&lt;h2&gt;
  
  
  setTimeout()
&lt;/h2&gt;

&lt;p&gt;To be clear, yes, &lt;code&gt;setTimeout()&lt;/code&gt; does have exactly the same issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I don't know who thought it was a good idea to default the &lt;code&gt;delay&lt;/code&gt; to a low number. Throwing an error when &lt;code&gt;delay&lt;/code&gt; is invalid sounds like a much better idea to me. But whatever, it is what it is! We just have to understand it and be careful.&lt;/p&gt;

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