<?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: Michael Bello</title>
    <description>The latest articles on DEV Community by Michael Bello (@mykelayo).</description>
    <link>https://dev.to/mykelayo</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%2F3785202%2Fd95534e0-f525-491a-ae11-b45559ed1722.jpeg</url>
      <title>DEV Community: Michael Bello</title>
      <link>https://dev.to/mykelayo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mykelayo"/>
    <language>en</language>
    <item>
      <title>How I built self-destructing notes with AES-256-GCM and Upstash Redis</title>
      <dc:creator>Michael Bello</dc:creator>
      <pubDate>Mon, 23 Feb 2026 10:04:55 +0000</pubDate>
      <link>https://dev.to/mykelayo/how-i-built-self-destructing-notes-with-aes-256-gcm-and-upstash-redis-12l1</link>
      <guid>https://dev.to/mykelayo/how-i-built-self-destructing-notes-with-aes-256-gcm-and-upstash-redis-12l1</guid>
      <description>&lt;p&gt;Every developer has sent credentials over Slack. The other person copies the key and it sits in chat history forever, in Slack's logs, backups, search indexes.&lt;/p&gt;

&lt;p&gt;I built notenotfound to fix this. Here's how the encryption and burn-after-reading works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encryption (AES-256-GCM)&lt;/strong&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;function&lt;/span&gt; &lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plaintext&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;key&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ENCRYPTION_KEY&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&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;iv&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// random 96-bit IV per note&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCipheriv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aes-256-gcm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&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;enc&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;final&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;tag&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAuthTag&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// 128-bit auth tag — tamper-proof&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enc&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;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&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;GCM mode means the auth tag detects any tampering. I chose SHA-256 for key derivation because scryptSync hit Netlify's serverless CPU limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The burn-after-reading&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two steps: (1) confirm=false checks note exists, returns metadata, note untouched. (2) confirm=true decrypts first, THEN deletes, then returns content. If decrypt fails, note stays in Redis. User gets an error and can retry — note never silently disappears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The name&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;notenotfound, because that's exactly what happens to your note after it's read.&lt;/p&gt;

&lt;p&gt;Live: notenotfound.netlify.app | Code: github.com/mykelayo/notenotfound&lt;/p&gt;

</description>
      <category>node</category>
      <category>privacy</category>
      <category>security</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built a free cron expression decoder, here's how it works under the hood</title>
      <dc:creator>Michael Bello</dc:creator>
      <pubDate>Sun, 22 Feb 2026 16:05:15 +0000</pubDate>
      <link>https://dev.to/mykelayo/i-built-a-free-cron-expression-decoder-heres-how-it-works-under-the-hood-2ef1</link>
      <guid>https://dev.to/mykelayo/i-built-a-free-cron-expression-decoder-heres-how-it-works-under-the-hood-2ef1</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every developer has been here: you're staring at &lt;code&gt;0 */4 * * 1-5&lt;/code&gt; in a config file &lt;br&gt;
and you have no idea what it means without running it mentally or Googling it.&lt;/p&gt;

&lt;p&gt;I got tired of that. So I built &lt;strong&gt;Cron.Explain&lt;/strong&gt;, a free tool that decodes any cron expression into plain English instantly.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://timely-flan-0ca3c1.netlify.app" rel="noopener noreferrer"&gt;https://timely-flan-0ca3c1.netlify.app&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Paste any cron expression and you get three things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Plain English explanation&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;0 9 * * 1-5&lt;/code&gt; → &lt;em&gt;"At 9:00 AM, on Monday through Friday"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Field-by-field breakdown&lt;/strong&gt;&lt;br&gt;
Each of the 5 fields explained individually — minute, hour, day of month, month, day of week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Next 5 run times&lt;/strong&gt;&lt;br&gt;
The actual dates and times your job will fire next, calculated in your local timezone.&lt;/p&gt;
&lt;h2&gt;
  
  
  The API
&lt;/h2&gt;

&lt;p&gt;It also ships a free REST API — no key required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://timely-flan-0ca3c1.netlify.app/api/explain &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"cron": "0 9 * * 1-5"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expression"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 9 * * 1-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"explanation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"At 9:00 AM, on Monday through Friday"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nextRuns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"2026-02-23T09:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;The interesting part: I didn't use any external cron parsing libraries. &lt;br&gt;
The entire parser is hand-rolled in vanilla JavaScript.&lt;/p&gt;

&lt;p&gt;The core is a &lt;code&gt;matchesField(value, n, min)&lt;/code&gt; function that handles all the special cron characters: &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;*/n&lt;/code&gt;, &lt;code&gt;n-m&lt;/code&gt;, &lt;code&gt;n,m&lt;/code&gt;, and &lt;code&gt;n/m&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The next-run calculator works by starting from the current minute and incrementing forward, checking each minute against all 5 fields until it finds 5 matches. It caps at 500,000 iterations to handle edge cases like rare monthly schedules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;React + Vite (frontend)&lt;/li&gt;
&lt;li&gt;Netlify Functions (serverless API)&lt;/li&gt;
&lt;li&gt;Zero dependencies for the parser&lt;/li&gt;
&lt;li&gt;IBM Plex Mono font because it felt right for a dev tool&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'm thinking about adding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cron job monitoring (ping a URL, alert if it hasn't run)&lt;/li&gt;
&lt;li&gt;Reverse builder (describe a schedule in English, get the cron)&lt;/li&gt;
&lt;li&gt;More languages in the API docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is open source on GitHub. Would love feedback, especially on cron expressions that break the parser — there are definitely edge cases I haven't covered.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live tool:&lt;/strong&gt; &lt;a href="https://timely-flan-0ca3c1.netlify.app" rel="noopener noreferrer"&gt;https://timely-flan-0ca3c1.netlify.app&lt;/a&gt;&lt;br&gt;
📖 &lt;strong&gt;API docs:&lt;/strong&gt; &lt;a href="https://timely-flan-0ca3c1.netlify.app/docs" rel="noopener noreferrer"&gt;https://timely-flan-0ca3c1.netlify.app/docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
