<?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</title>
    <description>The most recent home feed on DEV Community.</description>
    <link>https://dev.to</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed"/>
    <language>en</language>
    <item>
      <title>I Built a Pneumonia Detection AI on My MacBook — Here's Exactly How It Works</title>
      <dc:creator>nandhu_sauce</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:37:31 +0000</pubDate>
      <link>https://dev.to/saucynandhu/i-built-a-pneumonia-detection-ai-on-my-macbook-heres-exactly-how-it-works-47nn</link>
      <guid>https://dev.to/saucynandhu/i-built-a-pneumonia-detection-ai-on-my-macbook-heres-exactly-how-it-works-47nn</guid>
      <description>&lt;p&gt;I just finished building a deep learning system that identifies pneumonia from chest X-rays with 96% accuracy and an AUC-ROC of 0.99. I ran the entire training process on my MacBook Pro M5 using the GPU acceleration provided by Apple's Metal Performance Shaders (MPS). This project wasn't about complex math; it was about using transfer learning to turn a consumer laptop into a medical diagnostic tool. Understanding how to handle messy data and verify what an AI is actually "seeing" is more important than having a massive server room.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I ACTUALLY BUILT
&lt;/h2&gt;

&lt;p&gt;At its core, I built a computer program that acts like a specialized set of eyes for doctors. You feed it a digital chest X-ray image, and in less than a second, it tells you whether it sees signs of pneumonia or a normal, healthy lung. It doesn't just guess; it provides a confidence percentage for its decision. The system is designed to catch cases that might be subtle to the human eye, acting as a second pair of eyes to reduce diagnostic errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  THE DATASET PROBLEM NOBODY MENTIONS
&lt;/h2&gt;

&lt;p&gt;When I first looked at the data, I found a massive problem: class imbalance. The training set had 1,341 "Normal" images but a whopping 3,875 "Pneumonia" images. If I had trained the model as-is, it would have quickly learned that "guessing pneumonia every time" results in 74% accuracy without actually learning a single thing about lungs. This is a common trap in AI where a high accuracy score hides a completely useless model.&lt;/p&gt;

&lt;p&gt;To fix this, I used two specific techniques to level the playing field. First, I implemented a &lt;code&gt;WeightedRandomSampler&lt;/code&gt;, which you can think of as "photocopying rare examples." It ensures that during training, the model sees the "Normal" images more frequently so it doesn't forget what a healthy lung looks like. Second, I added class weights to the loss function—essentially a "bigger penalty for missing rare cases." If the model misclassifies a "Normal" lung as pneumonia, the error signal it receives is mathematically amplified, forcing it to pay closer attention to those specific patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHY I DIDN'T TRAIN FROM SCRATCH
&lt;/h2&gt;

&lt;p&gt;I didn't start with a blank slate. Instead, I used a technique called transfer learning with an architecture known as ResNet-18. Think of it like this: a radiologist who already knows what edges, textures, and shapes look like doesn't need to relearn basic vision from scratch. They already have the "visual foundation" from years of looking at the world; they just need to learn what sick lungs look like specifically. ResNet-18 comes pre-trained on millions of everyday images (like dogs, cars, and trees), so it already understands how to detect lines and textures.&lt;/p&gt;

&lt;p&gt;I used a two-phase training strategy to refine this pre-existing knowledge. In Phase 1 (Epochs 1-5), I froze most of the model and only trained the very last layer. This allowed the model to get a "feel" for the new medical data without overwriting its basic visual skills. In Phase 2 (Epochs 6-20), I unfroze everything and used a tiny learning rate to fine-tune the entire network. At the start of Phase 2, I saw a temporary spike in the loss curve. This is normal and expected—it's the mathematical equivalent of the model being slightly "confused" as it starts adjusting its deep-seated visual patterns to the nuances of X-ray tissue.&lt;/p&gt;

&lt;h2&gt;
  
  
  CAN YOU TRUST IT? GRAD-CAM EXPLAINABILITY
&lt;/h2&gt;

&lt;p&gt;A 96% accurate AI is useless if it's a "black box" that you can't verify. In medical AI, you have to know &lt;em&gt;why&lt;/em&gt; a decision was made. I implemented Grad-CAM (Gradient-weighted Class Activation Mapping), which is a tool that "shows you which pixels the model was looking at when it made its decision." Without this, a model might achieve 99% accuracy just by learning that images with a certain hospital's patient ID label in the corner are usually the pneumonia cases.&lt;/p&gt;

&lt;p&gt;When I ran Grad-CAM on my results, the heatmaps were revealing. For pneumonia cases, the "heat" (red and yellow zones) was concentrated directly on the lung tissue where opacities usually appear. For normal cases, the focus was much more diffuse across the entire chest cavity. This gave me the confidence that the model was actually learning medical features, not just memorizing background noise or image artifacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  THE RESULTS
&lt;/h2&gt;

&lt;p&gt;After 20 epochs and about 45 minutes of training on my MacBook, here is how the system performed on the 624 images in the test set:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test Accuracy&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AUC-ROC&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NORMAL F1&lt;/td&gt;
&lt;td&gt;0.94&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNEUMONIA F1&lt;/td&gt;
&lt;td&gt;0.96&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False Negatives&lt;/td&gt;
&lt;td&gt;15/390&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False Positives&lt;/td&gt;
&lt;td&gt;13/234&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's break these down into plain English. &lt;strong&gt;Test Accuracy&lt;/strong&gt; means the model was right 96 times out of 100 overall. &lt;strong&gt;AUC-ROC&lt;/strong&gt; measures how well the model can distinguish between the two classes across different confidence levels; 0.99 is nearly perfect separation. &lt;strong&gt;F1 Score&lt;/strong&gt; is a balanced average of precision (not flagging healthy people as sick) and recall (not missing sick people). &lt;/p&gt;

&lt;p&gt;Most importantly, we have to look at the &lt;strong&gt;15 missed pneumonia cases&lt;/strong&gt; (False Negatives). In a clinical context, missing a sick patient is far worse than accidentally flagging a healthy one. This is why "Recall" matters more than "Accuracy" in medical AI. While 15 misses out of 390 is low, it highlights that this system is a diagnostic assistant, not a replacement for a human doctor who would catch those edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I LEARNED
&lt;/h2&gt;

&lt;p&gt;This project taught me a few genuine technical lessons that go beyond the usual tutorials:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Validation Set Trap&lt;/strong&gt;: The original dataset only had 16 images in the validation folder. This made the validation accuracy bounce around wildly and become meaningless during training. You need a representative validation set to know if your model is actually improving.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Watch the Loss, Not the Accuracy&lt;/strong&gt;: Accuracy is a "lagging indicator." The loss curve tells you the "quality" of the model's learning. If the loss is still going down but accuracy is flat, you're still making progress.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Grad-CAM is Mandatory&lt;/strong&gt;: For medical AI, explainability isn't a "nice to have." It's the difference between a useful tool and a legal liability. If you can't see the heatmaps, you shouldn't trust the predictions.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Apple Silicon is Ready&lt;/strong&gt;: Training this on MPS (Metal Performance Shaders) was surprisingly fast. For this size of workload, you don't need a dedicated Linux server with a massive GPU; a modern MacBook Pro handles it in under an hour.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  WHAT'S NEXT
&lt;/h2&gt;

&lt;p&gt;I'm not finished with this system yet. My next steps are specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fix the Validation Set&lt;/strong&gt;: I'm going to move about 500 images from the training set into the validation set to get more reliable feedback during training.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try DenseNet-121&lt;/strong&gt;: This architecture is the current gold standard in chest X-ray research papers because of how it handles feature reuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build a Web UI&lt;/strong&gt;: I want to use Streamlit to create a simple drag-and-drop interface so anyone can test the model without looking at code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle Notebook&lt;/strong&gt;: I've already published a self-contained version of this project as a Kaggle notebook for the community to play with.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  CLOSING
&lt;/h2&gt;

&lt;p&gt;This project demonstrated that you don't need a supercomputer to build high-performing medical AI. By using transfer learning and being smart about how you handle imbalanced data, you can achieve professional-grade results on consumer hardware. It's a testament to how accessible deep learning has become, provided you focus on the data and the "why" behind the predictions.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This model is for educational purposes only and is not intended for clinical use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.kaggle.com/code/sreenandhunair/pneumonia-detection-with-deep-learning-96-acc" rel="noopener noreferrer"&gt;View the full notebook on Kaggle&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/saucynandhu/medical-image-diagnosis" rel="noopener noreferrer"&gt;View the code on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>deeplearning</category>
      <category>machinelearning</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Mastering Template Literals in JavaScript: Say Goodbye to String Concatenation Nightmares</title>
      <dc:creator>Ritam Saha</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:28:32 +0000</pubDate>
      <link>https://dev.to/ritam369/mastering-template-literals-in-javascript-13a4</link>
      <guid>https://dev.to/ritam369/mastering-template-literals-in-javascript-13a4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Imagine you're building a dynamic dashboard for your full-stack app. You need to greet users like "Welcome back, Ritam! Your last login was on April 24, 2026, at 4:28 PM IST." In the old days, you'd glue strings together with plus signs, escaping quotes, managing calculated spaces and praying for no typos. It quickly turns into a headache—unreadable for devs also, error-prone, and a maintenance nightmare. &lt;br&gt;
Now enters &lt;strong&gt;template literals&lt;/strong&gt;, JavaScript's elegant solution since ES6. They make string building feel natural, boosting your code's readability and productivity. Let's dive in and see why they're a game-changer.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Pain of Traditional String Concatenation
&lt;/h2&gt;

&lt;p&gt;Before template literals, we relied on concatenation with &lt;code&gt;+&lt;/code&gt; or arrays joined by &lt;code&gt;join('')&lt;/code&gt;. It works, but it's clunky and wasn't a good experience for the developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: Old-school greeting&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;const&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;Ritam&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;lastLogin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;April 24, 2026, 4:28 PM IST&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;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome back, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&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;! Your last login was on &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lastLogin&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// Output: "Welcome back, Ritam! Your last login was on April 24, 2026, 4:28 PM IST."&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Problems abound:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Readability suffers as strings grow; &lt;/li&gt;
&lt;li&gt;spotting variables amid quotes is tough.&lt;/li&gt;
&lt;li&gt;Easy to miss spaces or add extra ones (e.g., &lt;code&gt;'back,' + name&lt;/code&gt; vs. &lt;code&gt;'back, ' + name&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;No native multi-line support—forces ugly escape-sequence &lt;code&gt;\n&lt;/code&gt; or &lt;code&gt;+&lt;/code&gt; across lines.&lt;/li&gt;
&lt;li&gt;Debugging? Typos in long chains are brutal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This scales poorly in real apps, like API responses or HTML generation.&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%2Fsou9lk9hg0i4n6jbift7.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%2Fsou9lk9hg0i4n6jbift7.png" alt="Before &amp;amp; After" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Template Literal Syntax: Backticks Unlock the Magic
&lt;/h2&gt;

&lt;p&gt;Template literals use &lt;strong&gt;backticks (`)&lt;/strong&gt; instead of single (&lt;code&gt;'&lt;/code&gt;) or double (&lt;code&gt;"&lt;/code&gt;) quotes. Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interpolation&lt;/strong&gt;: Embed expressions with &lt;code&gt;${expression}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-line&lt;/strong&gt;: Spans lines without escapes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expression support&lt;/strong&gt;: &lt;code&gt;${}&lt;/code&gt; evaluates anything—variables, functions, math.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;const greeting = &lt;code&gt;Hello, world!&lt;/code&gt;;  // Simple string&lt;/p&gt;




&lt;h2&gt;
  
  
  Embedding Variables: String Interpolation Made Simple
&lt;/h2&gt;

&lt;p&gt;Interpolate with &lt;code&gt;${}&lt;/code&gt;—JavaScript evaluates and inserts the result/expression. It's dynamic and concise.&lt;/p&gt;

&lt;p&gt;const name = 'Ritam';&lt;br&gt;
const city = 'Kolkata';&lt;br&gt;
const greeting = &lt;code&gt;Welcome back to your dashboard, ${name} from ${city}!&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;// Output: "Welcome back to your dashboard, Ritam from Kolkata!"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical Breakdown:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;${name}&lt;/code&gt; calls &lt;code&gt;toString()&lt;/code&gt; on &lt;code&gt;name&lt;/code&gt; implicitly.&lt;/li&gt;
&lt;li&gt;Even supports complex expressions: &lt;code&gt;${name.toUpperCase()}&lt;/code&gt; or &lt;code&gt;${2 + 2}&lt;/code&gt; yields "RITAM" or "4".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare with concatenation:&lt;/p&gt;

&lt;p&gt;const oldGreeting = 'Welcome back to your dashboard, ' + name + ' from ' + city + '!';&lt;br&gt;
// Template literal (clean and readable)&lt;br&gt;
const newGreeting = &lt;code&gt;Welcome back to your dashboard, ${name} from ${city}!&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;Template literals win on readability—scan for &lt;code&gt;${}&lt;/code&gt; to spot variables instantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Line Strings: No More Escapes
&lt;/h2&gt;

&lt;p&gt;Need formatted text, like emails or SQL? &lt;strong&gt;Backticks handle newlines naturally&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;const user = 'Ritam';&lt;br&gt;
const emailBody = `&lt;br&gt;
Dear ${user},&lt;/p&gt;

&lt;p&gt;Your portfolio project deployed successfully on Vercel.&lt;br&gt;
Next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review PR on GitHub&lt;/li&gt;
&lt;li&gt;Test Node.js backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy coding!&lt;br&gt;
Team&lt;br&gt;
`;&lt;br&gt;
// Output preserves exact formatting, including indents.&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%2Fzgsmnwwmsc74kjgflp1e.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%2Fzgsmnwwmsc74kjgflp1e.png" alt="String Interpolation" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Cases in Modern JavaScript
&lt;/h2&gt;

&lt;p&gt;Template literals shine in full-stack dev:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Responses&lt;/strong&gt;: &lt;code&gt;const response = &lt;/code&gt;User ${userId} logged in at ${new Date().toISOString()}&lt;code&gt;;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML Templating&lt;/strong&gt; (pre-React): &lt;code&gt;&lt;/code&gt;Hello, ${name}!&lt;code&gt;&lt;/code&gt; (sanitize for security).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tagged Templates&lt;/strong&gt; (advanced): Libraries like styled-components use them for dynamicity, e.g., &lt;code&gt;styled.div&lt;/code&gt;Hello ${name}&lt;code&gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug Logs&lt;/strong&gt;: &lt;code&gt;console.log(&lt;/code&gt;Error in ${functionName}: ${error.message}&lt;code&gt;);&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL Builders&lt;/strong&gt;: &lt;code&gt;&lt;/code&gt;SELECT * FROM users WHERE id = ${userId}&lt;code&gt;&lt;/code&gt; (use params to prevent injection).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion: Level Up Your JS Strings Today
&lt;/h2&gt;

&lt;p&gt;Template literals transform string handling from a chore to a joy—readable, flexible, and modern. Ditch concatenation; embrace backticks for cleaner code that scales with your projects. Next time you're building that portfolio app or prepping for interviews, reach for &lt;code&gt;${}&lt;/code&gt;. Your future self (and teammates reviewing your PRs) will thank you.&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%2Frabxex7vctiqd6dmqjp3.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%2Frabxex7vctiqd6dmqjp3.png" alt="Strings usage before and after" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Built a Private, Local-First AI Assistant with Flask</title>
      <dc:creator>Ramesses_2</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:23:07 +0000</pubDate>
      <link>https://dev.to/r2/i-built-a-private-local-first-ai-assistant-with-flask-1ib2</link>
      <guid>https://dev.to/r2/i-built-a-private-local-first-ai-assistant-with-flask-1ib2</guid>
      <description>&lt;p&gt;The Goal: I wanted an AI assistant that doesn't save my logs to the cloud and utilize my hardware.&lt;/p&gt;

&lt;p&gt;Though it's quite slow as of now, I believe with further optimization and advancement in local silicon, edge AI would inevitably become the next big thing in the very near future.&lt;/p&gt;

&lt;p&gt;The Tech: I used Flask, TinyLlama, and Python-dotenv for security. I also took the help of Claude and Copilot wherever the code was repetitive and well beyond my current grasp on Python.&lt;br&gt;
I am a college freshman (about to become a sophomore), but I seek merciless feedbacks. Do provide me honest feedbacks about this project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sounak1410/Web-Based-Edge-AI-" rel="noopener noreferrer"&gt;Github Repository&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sounak1410" rel="noopener noreferrer"&gt;
        sounak1410
      &lt;/a&gt; / &lt;a href="https://github.com/sounak1410/Web-Based-Edge-AI-" rel="noopener noreferrer"&gt;
        Web-Based-Edge-AI-
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A personalized Small Language Model that runs locally on your device's hardware without having to access the internet or the cloud
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Y Assistant&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A privacy-focussed local AI chatbot powered by TinyLlama&lt;/p&gt;
&lt;p&gt;Y is a lightweight web-based AI assistant designed to run entirely on your local machine. It features a custom architecture demo, a secure login system, and per-session memory, ensuring that your conversations stay private and contextual. Created by S (My pseudonymn).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;No data leaves your machine. It runs on TinyLlama 1.1B.&lt;/li&gt;
&lt;li&gt;Contextual Memory: Remembers the last 10 messages for a natural conversation flow.&lt;/li&gt;
&lt;li&gt;Secure Access: Protected by a customizable password system.&lt;/li&gt;
&lt;li&gt;Privacy-First: No permanent logs are stored; session data is cleared on request.&lt;/li&gt;
&lt;li&gt;Architecture Demo: Includes a raw GPT-2 initialization script to show how LLMs are structured.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Install my-project with npm&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Clone the repository&lt;/span&gt;
git clone https://github.com/sounak1410/Web-Based-Edge-AI-.git

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Enter the directory&lt;/span&gt;
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; Web-Based-Edge-AI-

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Install dependencies&lt;/span&gt;
pip install -r requirements.txt

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Run the application&lt;/span&gt;
python Edge.py&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Chatting with Y&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open your browser to &lt;a href="http://127.0.0.1:5000" rel="nofollow noopener noreferrer"&gt;http://127.0.0.1:5000&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter the…&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/sounak1410/Web-Based-Edge-AI-" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>python</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Designing Go APIs That Don’t Age Badly</title>
      <dc:creator>Chiman Jain</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:20:56 +0000</pubDate>
      <link>https://dev.to/chiman_jain/designing-go-apis-that-dont-age-badly-59hd</link>
      <guid>https://dev.to/chiman_jain/designing-go-apis-that-dont-age-badly-59hd</guid>
      <description>&lt;p&gt;When building APIs in Go, it’s easy to get caught up in the rush to ship. You create an elegant endpoint, document it, and call it a day. But as your service evolves and your user base grows, what once seemed like a simple, clean API can quickly turn into a maintenance nightmare. If you don’t consider the long-term design of your Go APIs, you may find yourself with an API that becomes &lt;strong&gt;difficult to maintain&lt;/strong&gt;, &lt;strong&gt;fragile&lt;/strong&gt;, and &lt;strong&gt;hard to scale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post, we’ll cover some essential strategies for designing Go APIs that can stand the test of time. These include &lt;strong&gt;versioning&lt;/strong&gt;, &lt;strong&gt;avoiding breaking changes&lt;/strong&gt;, and &lt;strong&gt;mindful interface design&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with API Design in Go
&lt;/h2&gt;

&lt;p&gt;Go encourages simplicity and directness, but when it comes to managing evolving APIs, the language itself doesn’t impose conventions for API stability. The challenge for Go developers is to strike a balance between flexibility and longevity.&lt;/p&gt;

&lt;p&gt;A bad decision made early on in API design can result in &lt;strong&gt;breaking changes&lt;/strong&gt; that ripple through your application, potentially causing months of work for your team and frustration for consumers. But with careful planning and foresight, you can design APIs that are robust, adaptable, and flexible without introducing future headaches.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Versioning Your Go APIs Don’t Ignore It
&lt;/h2&gt;

&lt;p&gt;When building an API, versioning should be a core consideration from day one. Even if you don’t plan to make major changes now, assume that you will need to, and plan accordingly. &lt;/p&gt;

&lt;h3&gt;
  
  
  Why Versioning Matters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maintaining backward compatibility&lt;/strong&gt;: Versioning allows you to introduce changes to your API while ensuring that existing clients can continue to work without disruption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controlled deprecation&lt;/strong&gt;: You can signal to consumers which features or endpoints are deprecated and provide a migration path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns&lt;/strong&gt;: It allows your codebase to evolve while clearly distinguishing between stable and experimental functionality.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Versioning Strategies
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;URI Versioning&lt;/strong&gt; (e.g., &lt;code&gt;/v1/resource&lt;/code&gt; or &lt;code&gt;/v2/resource&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;: Simple to implement and understand. It’s immediately obvious which version of the API a client is calling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons&lt;/strong&gt;: If not managed carefully, it can lead to API version bloat, where you have to maintain multiple versions for a long time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for&lt;/strong&gt;: Major version changes, or when you need to make breaking changes that require a new major version of the API.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Header-based Versioning&lt;/strong&gt; (e.g., &lt;code&gt;X-API-Version&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;: Cleaner URLs, and you can keep versioning entirely out of the URL path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons&lt;/strong&gt;: Requires consumers to be aware of and set the correct headers. Not as intuitive as URI versioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for&lt;/strong&gt;: Small, non-breaking changes that do not require creating an entirely new version of the API.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Semantic Versioning&lt;/strong&gt; (e.g., &lt;code&gt;v1.0.0&lt;/code&gt;, &lt;code&gt;v1.1.0&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;: You can convey the type of changes made (major, minor, patch) through version numbers. Very clear for both developers and consumers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons&lt;/strong&gt;: Slightly more complex than simple URI-based versioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for&lt;/strong&gt;: APIs that evolve over time and need to clearly communicate how changes affect consumers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Versioning Tip
&lt;/h3&gt;

&lt;p&gt;Start versioning early. Even if your API doesn’t seem to need versioning now, &lt;strong&gt;create a versioning scheme&lt;/strong&gt; from the beginning. It’s easier to version early than to retroactively patch an unversioned API.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Avoiding Breaking Changes A Delicate Balance
&lt;/h2&gt;

&lt;p&gt;In Go, &lt;strong&gt;backward compatibility&lt;/strong&gt; is crucial, but sometimes breaking changes are inevitable as your API evolves. The goal is to minimize these changes, especially for clients relying on your service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common API Changes That Cause Breakage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Removing or renaming fields&lt;/strong&gt;: Clients may rely on specific fields being present, even if they aren’t always used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changing return types&lt;/strong&gt;: A seemingly small type change can break client code that relies on the original type.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changing HTTP methods&lt;/strong&gt;: A switch from &lt;code&gt;POST&lt;/code&gt; to &lt;code&gt;PUT&lt;/code&gt;, or modifying an endpoint’s expected behavior, can cause unexpected failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Introducing new required fields&lt;/strong&gt;: If your API starts requiring new fields that weren’t previously required, existing clients may break.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best Practices to Avoid Breaking Changes
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deprecate, Don’t Delete&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Rather than removing or renaming an endpoint, deprecate it and introduce a new one. Provide clear documentation about the deprecation, give consumers plenty of time to migrate, and offer new functionality alongside old functionality.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use Optional Fields&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When adding new fields, &lt;strong&gt;make them optional&lt;/strong&gt; and only introduce required fields in new versions of the API. This ensures existing consumers won’t be broken by your changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don’t Change Existing Behaviors&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If you change how an endpoint behaves, try to do so in a way that’s backward compatible. For example, you could add an optional query parameter to change the behavior, rather than altering the behavior entirely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Graceful Error Handling&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When an API change occurs, ensure that you handle errors gracefully. Provide clear, actionable error messages that guide users toward the changes they need to make in their client applications.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  3. Interface Design Pitfalls Avoiding Tomorrow’s Headaches
&lt;/h2&gt;

&lt;p&gt;In Go, interfaces are incredibly powerful, but they come with a lot of room for misuse. Designing APIs with poorly thought-out interfaces can lead to &lt;strong&gt;rigid, fragile systems&lt;/strong&gt; that break easily when your application evolves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Pitfalls in Interface Design
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Overuse of Interfaces&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Go interfaces are great, but they shouldn’t be overused. Often, developers fall into the trap of defining interfaces for every little component in their code. This can lead to unnecessary complexity and makes it harder to refactor in the future.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inconsistent Method Sets&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
An interface should have a well-defined, coherent set of methods. If you change the method set too frequently or make the methods too vague, you’ll find it difficult to maintain consistency across your codebase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interface Pollution&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Avoid creating interfaces that are too general and try to do too much. Each interface should have a clear, focused responsibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Breaking Changes in Interfaces&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Removing or changing methods in an interface is a breaking change. Instead, prefer adding methods and allowing optional extensions in your interfaces.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Best Practices for Interface Design
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Design Interfaces for Use, Not for Flexibility&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Focus on designing interfaces that actually solve problems for users of your API. Don’t make interfaces more general than they need to be just to add flexibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make Interfaces Small and Focused&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Keep interfaces small and focused on one responsibility. The more focused the interface, the easier it is to maintain and evolve without breaking things.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prefer Composition Over Inheritance&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Use struct embedding (composition) rather than inheritance (interface embedding) to compose behavior across different types. This avoids complex hierarchies and allows for better flexibility in adding new functionality without breaking existing code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoid Tight Coupling&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Make sure your API is loosely coupled. This can be achieved by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using dependency injection where necessary&lt;/li&gt;
&lt;li&gt;Avoiding direct dependencies on other packages in your interfaces&lt;/li&gt;
&lt;li&gt;Keeping interfaces simple and allowing different implementations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Thoughts: Future-Proofing Your Go API
&lt;/h2&gt;

&lt;p&gt;API design isn’t just about making something that works today it’s about making something that will still be usable, maintainable, and extensible &lt;strong&gt;tomorrow&lt;/strong&gt;. In Go, the key to future-proofing your APIs is &lt;strong&gt;thinking ahead&lt;/strong&gt; versioning early, avoiding breaking changes, and being mindful of interface design. With these strategies, you’ll ensure that your Go APIs not only work in the present but can also adapt to changes as your application and team evolve.&lt;/p&gt;

&lt;p&gt;If you’re planning a major API refactor or starting a new project, take the time to implement these principles early. By doing so, you’ll save yourself (and your team) from much greater pain later down the road.&lt;/p&gt;

&lt;p&gt;Remember: &lt;strong&gt;Good design is not just about elegance today it’s about resilience over time.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>api</category>
      <category>architecture</category>
      <category>design</category>
    </item>
    <item>
      <title>React Email 6.0, Vercel got hacked, Prevent flash before hydration, Logging in Next.js, shader-lab, TypeScript 7.0, nextmap</title>
      <dc:creator>Erfan Ebrahimnia</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:20:30 +0000</pubDate>
      <link>https://dev.to/erfanebrahimnia/react-email-60-vercel-got-hacked-prevent-flash-before-hydration-logging-in-nextjs-shader-lab-3425</link>
      <guid>https://dev.to/erfanebrahimnia/react-email-60-vercel-got-hacked-prevent-flash-before-hydration-logging-in-nextjs-shader-lab-3425</guid>
      <description>&lt;p&gt;&lt;a href="https://resend.com/blog/react-email-6" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fembed.filekitcdn.com%2Fe%2F6Hn37Q2unMsmjbqMTJFz2S%2F5zj8XzBDeujUkFfY3SZ9MC%3Fw%3D1000%26fit%3Dclip" alt="React Email 6.0" width="2400" height="1260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://resend.com/blog/react-email-6" rel="noopener noreferrer"&gt;React Email 6.0&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The highlight of this release is an open-source email editor that outputs inbox-ready HTML. It has a composable API for building custom blocks like image uploaders or social embeds. Also new: a unified single package for all components and a fresh set of templates for auth and e-commerce flows&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://vercel.com/kb/bulletin/vercel-april-2026-security-incident" rel="noopener noreferrer"&gt;Vercel April 2026 security incident&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Vercel disclosed a security incident where an attacker compromised a third-party AI tool (Context.ai) used by an employee, gaining access to some internal systems and non-sensitive environment variables. No npm packages were affected. Vercel recommends rotating credentials, enabling 2FA, and reviewing activity logs&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡️ Sponsor: Clerk
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://go.clerk.com/hOneCiJ" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fembed.filekitcdn.com%2Fe%2F6Hn37Q2unMsmjbqMTJFz2S%2FrForPCDT7BnmYzJQu7QRva%3Fw%3D1000%26fit%3Dclip" alt="Clerk CLI: manage auth from your terminal" width="1840" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://go.clerk.com/hOneCiJ" rel="noopener noreferrer"&gt;Clerk CLI: manage auth from your terminal&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The new Clerk CLI detects your framework and gets auth ready to configure. Run &lt;code&gt;clerk init&lt;/code&gt; to scaffold, &lt;code&gt;clerk config&lt;/code&gt; to manage sign-in methods, redirects, and session policies in code, or &lt;code&gt;clerk api&lt;/code&gt; to query users and orgs. Try it.&lt;/p&gt;




&lt;h2&gt;
  
  
  📙 Articles / Tutorials / News
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/vercel/next.js/commit/931868b86c4079300d059dab4301e8dab8924249#diff-c7ddc7709fa2b3f7c68d45c10aa119f5250bb8f758525cedb0329f9972660c68" rel="noopener noreferrer"&gt;New Docs: How to prevent flash before hydration&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;New docs explaining how to avoid the visible flicker when things like dates, themes, or localStorage values differ between server and client&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://valentinprugnaud.dev/posts/2026/04/if-you-cant-see-the-boundary-you-cant-reason-about-the-system" rel="noopener noreferrer"&gt;If You Can't See the Boundary, You Can't Reason About the System&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We featured the RSC Boundary package in a previous issue. This article from its creator explains the problem it solves: When working with React Server Components, you often have to guess where the server/client split is by reading &lt;code&gt;'use client'&lt;/code&gt; directives and tracing imports. RSC Boundary removes that guesswork. It's a dev-only overlay that highlights which parts of your page are server-rendered and which are client components, giving you a live visual map&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.codewithseb.com/blog/nextjs-16-migration-guide-breaking-changes" rel="noopener noreferrer"&gt;Next.js 16 Migration Guide: Everything That Breaks (And How to Fix It)&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A thorough, hands-on migration guide covering every breaking change in Next.js 16 and how to deal with it&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://yurimutti.com/posts/logging-nextjs-loglayer-instrumentation-console-override-structured-logs" rel="noopener noreferrer"&gt;Logging in Next.js with LogLayer&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Next.js logs can get messy across different runtimes. This post shows how to fix that with one shared logger using LogLayer, console interception in &lt;code&gt;instrumentation.ts&lt;/code&gt;, and structured output with Pino for production&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Projects / Packages / Tools
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://eng.basement.studio/tools/shader-lab" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fembed.filekitcdn.com%2Fe%2F6Hn37Q2unMsmjbqMTJFz2S%2F8JusztjgqkA6xSQVyvr7is%3Fw%3D1000%26fit%3Dclip" alt="@basementstudio/shader-lab" width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://eng.basement.studio/tools/shader-lab" rel="noopener noreferrer"&gt;@basementstudio/shader-lab&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A visual tool and React runtime for creating layered shader compositions. Render directly in your app, use them as Three.js textures, or apply built-in effects like ASCII, CRT, dithering, and pixel sorting as postprocessing over your own 3D scenes. Fully open source&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/icydotdev/nextmap" rel="noopener noreferrer"&gt;nextmap&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A CLI tool that scans your Next.js project and shows every route as an interactive graph in your browser. It picks up pages, API routes, middleware, and HTTP methods&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://animata.design/" rel="noopener noreferrer"&gt;Animata&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Handcrafted interaction animations and visual effects built with Tailwind CSS and Framer Motion. Works like shadcn/ui&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-0-beta/" rel="noopener noreferrer"&gt;Announcing TypeScript 7.0 Beta&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The first beta release of TypeScript 7.0 is here, and it's a big deal. The compiler has been completely rewritten in Go, making it around 10x faster than TypeScript 6.0. It now supports parallel type-checking and parallel project builds out of the box. Despite being a beta, the team says it's stable enough for everyday use&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡️ Sponsor: Expo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://expo.dev/blog/from-web-to-native-with-react?utm_source=Nextjsweekly&amp;amp;utm_medium=newsletter&amp;amp;utm_campaign=33087804-React%20to%20Native&amp;amp;utm_content=react_to_native_blog" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fembed.filekitcdn.com%2Fe%2F6Hn37Q2unMsmjbqMTJFz2S%2Fv8yG3S8r5VDQB2WDyJxXbz%3Fw%3D400%26fit%3Dclip" alt="How to go from Web to Native with React" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://expo.dev/blog/from-web-to-native-with-react?utm_source=Nextjsweekly&amp;amp;utm_medium=newsletter&amp;amp;utm_campaign=33087804-React%20to%20Native&amp;amp;utm_content=react_to_native_blog" rel="noopener noreferrer"&gt;How to go from Web to Native with React&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Everything that web devs need to know about building their first mobile app - You already know React. With Expo, you can use that knowledge to build fully native apps for iOS and Android without starting over or learning new tools&lt;/p&gt;




&lt;h2&gt;
  
  
  🌈 Related
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://frontendmasters.com/blog/building-a-ui-without-breakpoints/" rel="noopener noreferrer"&gt;Building a UI Without Breakpoints&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Modern interfaces are component-first, but most responsive CSS is still viewport-first. This article bridges that gap with four methods: intrinsic layouts using &lt;code&gt;auto-fit&lt;/code&gt; and &lt;code&gt;minmax()&lt;/code&gt;, fluid scaling with &lt;code&gt;clamp()&lt;/code&gt;, container units for sizing relative to a component's actual space, and container queries for true structural changes&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://neciudan.dev/10-react-tips-that-actually-matter" rel="noopener noreferrer"&gt;10 React tips I wish someone had told me before I mass-produced bugs&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Lessons learned from production bugs and code reviews. Includes tips like replacing multiple &lt;code&gt;useState&lt;/code&gt; calls with &lt;code&gt;useReducer&lt;/code&gt;, keeping state close to where it's used, using &lt;code&gt;key&lt;/code&gt; to reset components, and choosing Compound Components over prop-heavy APIs&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://techhub.iodigital.com/articles/a-typescript-class-based-websocket-library-for-react/websockets" rel="noopener noreferrer"&gt;A TypeScript Class-Based WebSocket Library for React&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Explaining a different approach to WebSockets in React. Instead of putting everything in hooks, the heavy lifting lives in TypeScript classes&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://frontendmasters.com/blog/ai-generated-ui-is-inaccessible-by-default/" rel="noopener noreferrer"&gt;AI-Generated UI Is Inaccessible by Default&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Breaks down exactly why LLMs produce inaccessible code and presents a practical five-layer fix: prompt constraints, static analysis with &lt;code&gt;eslint-plugin-jsx-a11y&lt;/code&gt;, runtime testing with axe-core, CI enforcement via GitHub Actions, and using accessible component libraries like Headless UI, Radix, or React Aria&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>AI Portfolio Generator for Developers: Stop Overthinking It and Ship the Thing in 2026</title>
      <dc:creator>Maya Bayers</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:19:21 +0000</pubDate>
      <link>https://dev.to/maya_bayers/ai-portfolio-generator-for-developers-stop-overthinking-it-and-ship-the-thing-in-2026-58b</link>
      <guid>https://dev.to/maya_bayers/ai-portfolio-generator-for-developers-stop-overthinking-it-and-ship-the-thing-in-2026-58b</guid>
      <description>&lt;p&gt;You have built production apps. You have debugged systems at 2am. You have shipped features that thousands of people use.&lt;/p&gt;

&lt;p&gt;And yet your portfolio is either a half-finished Next.js site you started six months ago, a GitHub profile with a decent README, or something you keep meaning to update when you have more time.&lt;/p&gt;

&lt;p&gt;You are not alone. Developer portfolios are one of the most consistently delayed personal projects in the industry — and the reason is almost never a lack of good work to show.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why developer portfolios stall&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The technical part is not the problem. Most developers could build a portfolio site in a weekend. The problem is the content layer.&lt;/p&gt;

&lt;p&gt;Writing about your own work is genuinely hard. Translating what you built, why you built it that way, and why it mattered into language that a non-technical hiring manager or potential client can understand — without dumbing it down so much it loses meaning — is a different skill set than the one you use every day.&lt;/p&gt;

&lt;p&gt;That is exactly where AI helps most in 2026.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What AI actually does well for a developer portfolio&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of AI as a drafting tool for the content layer — not a replacement for your technical judgment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bio writing.&lt;/strong&gt; Give AI a few bullet points about your stack, your focus, and your background. It will return a first draft you can edit from. That is infinitely faster than starting from a blank text file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project summarizing.&lt;/strong&gt; Paste in your README, your commit history summary, or rough notes about a project. AI can turn that into a readable three to five sentence portfolio description that explains the problem, your approach, and the outcome in plain language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section structure.&lt;/strong&gt; Not sure whether to lead with projects or stack? AI can suggest a logical page structure based on your experience level and the kind of role you are targeting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headline and CTA drafting.&lt;/strong&gt; The copy around your work — the homepage headline, the contact section, the about page intro — is where most developer portfolios feel weak. AI handles first drafts of these quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FAQ and stack descriptions.&lt;/strong&gt; Short, scannable blocks that answer the questions a recruiter or client actually has. AI drafts these well from your input.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What you must write yourself&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where most AI-assisted developer portfolios fall apart.&lt;/p&gt;

&lt;p&gt;Do not let AI fully own the technical substance of your portfolio. Specifically:&lt;/p&gt;

&lt;p&gt;What you actually built and what technical decisions you made. The tradeoffs you navigated — why you chose PostgreSQL over MongoDB, why you went with a monolith instead of microservices, what you would do differently now. The specific business or user problem your code solved and why it mattered. Performance improvements, scale numbers, or technical outcomes you can actually verify. What your real contribution was on collaborative or team projects.&lt;/p&gt;

&lt;p&gt;A technical hiring manager reading your portfolio is not just evaluating your stack. They are evaluating your thinking. They want to know whether you understand why the decisions you made were the right ones for the context — and whether you can communicate that clearly.&lt;/p&gt;

&lt;p&gt;AI cannot fake depth of technical reasoning convincingly. Only you can write that part well.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Best tools for a developer portfolio in 2026&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unicorn Platform&lt;/strong&gt; is the strongest no-code option for developers who want a clean, professional portfolio site live quickly without it becoming another side project. The editing is fast, the section structure is practical, and updating it does not require touching a codebase every time something changes. Strong for developers who bill hourly or run client-facing work and need a real site — not just a GitHub profile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lovable&lt;/strong&gt; is worth considering for developers comfortable with AI-generated site direction who want to move from concept to published draft quickly. Useful for testing portfolio structures before committing to a final build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figma Make&lt;/strong&gt; is less commonly the first choice for developers but worth knowing if the role you are targeting has a significant frontend, product, or design component — or if you want stronger layout control for a design-led portfolio structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gamma&lt;/strong&gt; is a strong fit for developers moving into technical leadership, product, or engineering management roles where the portfolio needs to demonstrate strategic thinking and communication as much as technical execution. Narrative-driven structure works well here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canva&lt;/strong&gt; is a practical fallback for developers who want something clean and professional without any design decision overhead. Less suited to deep multi-page structures but useful for a lightweight proof-of-work page.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Portfolio structure that actually works for developers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keep it simple. The structure should make it easy for someone to understand who you are, what you build, and why your projects mattered — in under two minutes.&lt;/p&gt;

&lt;p&gt;A strong developer portfolio structure in 2026:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hero section&lt;/strong&gt; — your role, focus area, and a one to two sentence positioning statement. Not a list of technologies. Not "passionate developer." Something specific like "I build performant React applications for early-stage SaaS products" or "I work on backend infrastructure for high-traffic consumer apps."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Featured projects&lt;/strong&gt; — three to five projects maximum. For each one: the problem it solved, your specific contribution, the stack, and a measurable outcome if possible. Link to a live demo or GitHub repo where relevant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack or capability block&lt;/strong&gt; — optional but useful if the role you are targeting values specific technical expertise. Keep it scannable and specific. Avoid listing every technology you have ever touched.&lt;br&gt;
About section — short personal context. Why you got into development, what kind of problems you find interesting, what you are working on or learning now. Two to three sentences is enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contact CTA&lt;/strong&gt; — make it obvious and frictionless. One clear action. No contact form with twelve fields.&lt;br&gt;
That is the complete structure. Resist the temptation to add more sections because more content feels like more effort.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Prompts developers can actually use&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For your hero section:&lt;/strong&gt; &lt;em&gt;"Write a two-sentence homepage positioning statement for a frontend developer with four years of experience building React and TypeScript applications for SaaS products. Focus on clean UI, performance, and shipping user-facing features. Keep it specific and avoid generic developer clichés."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a project description:&lt;/strong&gt; &lt;em&gt;"Write a four-sentence portfolio project description for a developer who built a real-time collaborative document editor using React, Socket.io, and Node.js. Explain the technical challenge, the key architectural decisions, the stack, and the outcome. Keep it readable for a non-technical audience without losing technical credibility."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For your about section:&lt;/strong&gt; &lt;em&gt;"Write a short about section for a full-stack developer portfolio. Background is three years building internal tools and dashboards for fintech startups. Interested in developer experience, API design, and building things that non-technical teams can actually use. Keep it human and specific."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a stack block:&lt;/strong&gt; &lt;em&gt;"Write a short technical capabilities section for a backend developer portfolio. Core stack is Python, FastAPI, PostgreSQL, and AWS. Keep it scannable, specific, and focused on what I actually use in production — not a comprehensive list of everything I have touched."&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;A workflow that gets your developer portfolio shipped&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reason most developer portfolios never launch is scope. The project feels too open-ended, the content feels too hard to write, and a client project or interesting open source problem always takes priority.&lt;/p&gt;

&lt;p&gt;This workflow keeps the scope manageable and gets you to a published first version:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step one.&lt;/strong&gt; Choose three to five projects you are genuinely proud of — ideally ones that represent the kind of work you want more of. Not necessarily your most technically complex work. The work that is most relevant to the roles or clients you are targeting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step two.&lt;/strong&gt; For each project, write three honest bullet points in plain language: the problem it solved, what you specifically built or contributed, and the real outcome. Do not worry about polish at this stage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step three.&lt;/strong&gt; Feed those bullets into AI and ask it to turn them into a readable project description. Review the output carefully. Add back the technical specifics. Remove anything that overstates your role or claims outcomes you cannot verify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step four.&lt;/strong&gt; Use AI to draft your bio and hero section from your background and focus. Rewrite until it sounds like you actually said it — not like a job description written by someone else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step five.&lt;/strong&gt; Choose the simplest site structure that answers the questions your target audience actually has. Resist adding sections because they look impressive. Every section should earn its place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step six.&lt;/strong&gt; Launch the first version. A live portfolio that is honest and specific is always more valuable than a perfect one that never ships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step seven.&lt;/strong&gt; Update it when you finish something worth adding. Treat it like a living document — not a project you complete once and forget.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;On domain and personal brand&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are serious about building a professional presence in 2026, your domain and broader digital footprint matter more than most developers give them credit for. A well-chosen personal domain supports discoverability, signals professionalism, and gives you a stable home for your work as your career evolves.&lt;/p&gt;

&lt;p&gt;This guide on &lt;a href="https://unicornplatform.com/blog/ai-domain-naming-strategy-for-2026/" rel="noopener noreferrer"&gt;AI-assisted domain naming strategy for 2026&lt;/a&gt; is worth reading before you finalize your online setup — especially if you are thinking about personal branding alongside your portfolio.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The bottom line&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI is one of the most useful tools available for developer portfolio building in 2026. It solves the content layer problem — the part that actually blocks most developers from shipping — without requiring you to become a professional copywriter.&lt;/p&gt;

&lt;p&gt;But the portfolio that gets you the interviews, the freelance clients, or the role you want is still the one that demonstrates real technical thinking. Honest project scope. Specific outcomes. A voice that sounds like a person with genuine opinions about how software should be built.&lt;/p&gt;

&lt;p&gt;Use AI to draft faster. Write the technical substance yourself. Ship the first version.&lt;/p&gt;

&lt;p&gt;Your GitHub profile is not a portfolio. A portfolio is a portfolio. Ship it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>startup</category>
    </item>
    <item>
      <title>How SNF Detects C2 Beacons on Air-Gapped Networks Without Ever Touching the Internet</title>
      <dc:creator>Snf Labs</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:18:08 +0000</pubDate>
      <link>https://dev.to/shadownf/how-snf-detects-c2-beacons-on-air-gapped-networks-without-ever-touching-the-internet-504n</link>
      <guid>https://dev.to/shadownf/how-snf-detects-c2-beacons-on-air-gapped-networks-without-ever-touching-the-internet-504n</guid>
      <description>&lt;h1&gt;
  
  
  How SNF Detects C2 Beacons on Air-Gapped Networks Without Ever Touching the Internet
&lt;/h1&gt;

&lt;p&gt;Most threat detection tools phone home. They pull threat feeds, push telemetry, verify licenses, or at minimum require a DNS resolver to function. That assumption is baked so deep into modern NDR that nobody questions it anymore.&lt;/p&gt;

&lt;p&gt;SNF questions it.&lt;/p&gt;

&lt;p&gt;SNF (Shadow Network Fingerprinting) is a passive network intelligence engine written entirely in Rust. It was designed from day one for environments where outbound connectivity is not just unavailable but prohibited: air-gapped defense networks, nuclear infrastructure, industrial control systems, classified SOCs.&lt;/p&gt;

&lt;p&gt;The architecture enforces this. There are no configuration flags to disable telemetry, no license server to bypass, no optional cloud sync to turn off. The binary makes zero network calls. Ever. If you audit the source, you will not find a single outbound socket.&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%2F397rft82jnygb7a2e9bz.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%2F397rft82jnygb7a2e9bz.png" alt="SNF coming soon page live at shadownf.com" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What SNF Actually Does
&lt;/h2&gt;

&lt;p&gt;SNF captures raw packets passively, reconstructs TCP/UDP flows, and runs them through 14 deterministic protocol analyzers in a fixed order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DNS, TLS, HTTP/1.1, HTTP/2, QUIC, DHCP, ICMP, SMB, mDNS, ICS, Enterprise, Discovery, DoH, and DoT.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On top of that it fingerprints TLS sessions using JA3 and JA4, detects C2 beacon patterns by analyzing packet timing and jitter, catches DGA domains through statistical entropy analysis, identifies DNS tunnels by payload size and query frequency, and covers the full ICS/SCADA protocol suite including Modbus, S7comm, EtherNet-IP, PROFINET, and DNP3.&lt;/p&gt;

&lt;p&gt;Everything it finds gets emitted as structured NDJSON with a deterministic guarantee: run the same PCAP with the same config on the same version and you get bit-identical output, verified by SHA-256. This matters in forensic and legal contexts where reproducibility is not optional.&lt;/p&gt;

&lt;p&gt;Here is SNF running against a real Emotet epoch 3 + Trickbot infection PCAP. No internet connection. No threat feed pulled at runtime.&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%2F69jxw6b2mfz53q9qndxj.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%2F69jxw6b2mfz53q9qndxj.png" alt="SNF session report on Emotet epoch 3 + Trickbot PCAP showing 23 IOC hits and 52 threat matches" width="800" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;23 IOC hits. 52 threat matches. JA3 fingerprinting attributed the TLS session directly to the Emotet/Trickbot loader. The stealth detector flagged three external IPs for portscan, tunnel, and exfil activity simultaneously. A DGA candidate scored 85 on a &lt;code&gt;.onion&lt;/code&gt; domain.&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%2F40dat9nb6r95kdshx6vr.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%2F40dat9nb6r95kdshx6vr.png" alt="SNF behavioral alerts showing Emotet_C2_epoch3 IOC matches, JA3 threat actor attribution, and DGA candidate with score 85" width="762" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Parse errors: 0. Capture errors: 0.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Determinism Matters
&lt;/h2&gt;

&lt;p&gt;Most tools produce slightly different output across runs because of timing, threading, or probabilistic components. SNF uses BTreeMap for all output so field ordering is always identical. It has a tamper-resistant monotonic session clock. It produces cryptographically signed evidence bundles that hold up in court.&lt;/p&gt;

&lt;p&gt;Same dataset plus same config plus same version equals identical SHA-256 output. Every single time.&lt;/p&gt;

&lt;p&gt;This is not a nice-to-have. In a DFIR investigation, in a government audit, or in a legal proceeding, output that changes between runs is inadmissible. SNF was built around this constraint from the first commit, not bolted on after.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance on Real Data
&lt;/h2&gt;

&lt;p&gt;On the MAWI backbone dataset (14.9 million packets, a real-world traffic sample used by network researchers globally), SNF processes at roughly 155,000 packets per second on a single core, sustaining around 1.25 Gbps throughput. On a 4-core VM it scales to 2.3x that with multi-worker mode.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Actually Get Out of a Run
&lt;/h2&gt;

&lt;p&gt;One command. One PCAP. SNF writes structured output automatically, split by category and timestamped to the session.&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%2Fd7sd6bfu4w4979ju7imh.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%2Fd7sd6bfu4w4979ju7imh.png" alt="SNF output files: all_flows, devices, dns, fingerprints, ioc_hits, threats, tls, top_flows in both NDJSON and CSV" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every run produces flows, devices, DNS, TLS fingerprints, IOC hits, threat matches, and a full session report in both NDJSON and CSV. No post-processing scripts. No manual export. Ready for SIEM import or court submission as-is.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Open-Core Layer
&lt;/h2&gt;

&lt;p&gt;The capture layer, flow reconstruction engine, and protocol parsers are open source under Apache 2.0 as snf-core on GitHub. The intelligence layer covering behavioral detection, fingerprinting, stealth mode, graph analysis, evidence bundles, SIEM export, and the threat databases is proprietary.&lt;/p&gt;

&lt;p&gt;If you work in DFIR, ICS security, or blue team work in regulated environments, the repo is worth a look. Contributions to the open-core layer are welcome.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://shadownf.com" rel="noopener noreferrer"&gt;https://shadownf.com&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/padigeltejas/snf-core" rel="noopener noreferrer"&gt;https://github.com/padigeltejas/snf-core&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/company/snflabs/" rel="noopener noreferrer"&gt;https://www.linkedin.com/company/snflabs/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cybersecurity</category>
      <category>networking</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Encode Base64 in JavaScript Without Breaking Unicode</title>
      <dc:creator>MOUSTAID Hamza</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:17:44 +0000</pubDate>
      <link>https://dev.to/hamzamoustaid/how-to-encode-base64-in-javascript-without-breaking-unicode-nci</link>
      <guid>https://dev.to/hamzamoustaid/how-to-encode-base64-in-javascript-without-breaking-unicode-nci</guid>
      <description>&lt;p&gt;Base64 encoding is one of those small developer tasks that looks simple until you hit Unicode characters, files, APIs, or URL-safe formats.&lt;/p&gt;

&lt;p&gt;In JavaScript, many tutorials show 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="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World&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;And yes, it works.&lt;/p&gt;

&lt;p&gt;But only for simple ASCII text.&lt;/p&gt;

&lt;p&gt;Once you try to encode something like:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Café 🚀&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you may run into errors or broken output.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll cover how Base64 encoding works in JavaScript, when &lt;code&gt;btoa()&lt;/code&gt; is enough, when it is not, and how to encode text, Unicode, files, images, and URL-safe Base64 correctly.&lt;/p&gt;

&lt;p&gt;You can also test examples quickly with &lt;a href="https://encodingbase64.com/" rel="noopener noreferrer"&gt;EncodingBase64&lt;/a&gt;, a free browser-based Base64 encoder for text, files, images, URL-safe Base64, hex, and URL encoding.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Base64 encoding?
&lt;/h2&gt;

&lt;p&gt;Base64 is a way to represent binary data using readable ASCII characters.&lt;/p&gt;

&lt;p&gt;It is commonly used when binary data needs to travel through systems that expect text, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;JSON payloads&lt;/li&gt;
&lt;li&gt;email attachments&lt;/li&gt;
&lt;li&gt;HTML/CSS data URIs&lt;/li&gt;
&lt;li&gt;authentication headers&lt;/li&gt;
&lt;li&gt;embedded images&lt;/li&gt;
&lt;li&gt;configuration files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SGVsbG8gV29ybGQ=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64 does &lt;strong&gt;not&lt;/strong&gt; encrypt data. Anyone can decode it.&lt;/p&gt;

&lt;p&gt;It only changes the representation of the data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basic Base64 encoding with btoa()
&lt;/h2&gt;

&lt;p&gt;JavaScript includes a built-in function called &lt;code&gt;btoa()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World&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;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;encoded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// SGVsbG8gV29ybGQ=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To decode it back:&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;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoded&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;decoded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Hello World&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fine for basic ASCII strings.&lt;/p&gt;

&lt;p&gt;But there is a problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Unicode problem with btoa()
&lt;/h2&gt;

&lt;p&gt;Try 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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Café 🚀&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;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;encoded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the environment, you may get an error like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InvalidCharacterError
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;btoa()&lt;/code&gt; expects a binary string where each character is in the Latin-1 range. It does not directly support full Unicode text.&lt;/p&gt;

&lt;p&gt;Modern apps often deal with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accented characters&lt;/li&gt;
&lt;li&gt;Arabic&lt;/li&gt;
&lt;li&gt;French&lt;/li&gt;
&lt;li&gt;Spanish&lt;/li&gt;
&lt;li&gt;German&lt;/li&gt;
&lt;li&gt;Portuguese&lt;/li&gt;
&lt;li&gt;emojis&lt;/li&gt;
&lt;li&gt;non-Latin scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we need a safer method.&lt;/p&gt;




&lt;h2&gt;
  
  
  Unicode-safe Base64 encoding
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;TextEncoder&lt;/code&gt; to convert the string into UTF-8 bytes first.&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;encodeBase64Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;bytes&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;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;binary&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="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;byte&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="nx"&gt;binary&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;byte&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;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binary&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="nf"&gt;encodeBase64Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Café 🚀&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Q2Fmw6kg8J+agA==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the Unicode string is safely converted to UTF-8 before Base64 encoding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Unicode-safe Base64 decoding
&lt;/h2&gt;

&lt;p&gt;To decode it back properly, use &lt;code&gt;TextDecoder&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;function&lt;/span&gt; &lt;span class="nf"&gt;decodeBase64Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;binary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;char&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;return&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&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="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="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&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;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encodeBase64Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Café 🚀&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;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeBase64Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoded&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;decoded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Café 🚀&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is safer for real-world text.&lt;/p&gt;




&lt;h2&gt;
  
  
  Encoding a file to Base64 in JavaScript
&lt;/h2&gt;

&lt;p&gt;For files, you can use &lt;code&gt;FileReader&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example with an HTML file input:&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;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fileInput"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;input&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="s2"&gt;fileInput&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&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="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;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;reader&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;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&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="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;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;The result will look 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;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is called a &lt;strong&gt;data URI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data:[mime-type];base64,[base64-data]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data:image/png;base64,iVBORw0KGgo...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only want the raw Base64 part, remove everything before the comma:&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;dataUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&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;rawBase64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataUrl&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="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&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;rawBase64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Encoding an image to Base64
&lt;/h2&gt;

&lt;p&gt;Images are one of the most common Base64 use cases.&lt;/p&gt;

&lt;p&gt;You might encode images to Base64 when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;embedding a tiny icon in HTML&lt;/li&gt;
&lt;li&gt;storing a small image in JSON&lt;/li&gt;
&lt;li&gt;sending an image through an API&lt;/li&gt;
&lt;li&gt;generating previews&lt;/li&gt;
&lt;li&gt;creating CSS background images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example HTML output:&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;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Example"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example CSS output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.icon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...")&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 is useful for small images.&lt;/p&gt;

&lt;p&gt;But be careful: Base64 increases file size, so it is usually not ideal for large images.&lt;/p&gt;

&lt;p&gt;For quick browser-based image conversion, &lt;a href="https://encodingbase64.com/image-to-base64/" rel="noopener noreferrer"&gt;EncodingBase64’s Image to Base64 tool&lt;/a&gt; can convert images into raw Base64, data URI, HTML, CSS, or Markdown formats.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is URL-safe Base64?
&lt;/h2&gt;

&lt;p&gt;Standard Base64 uses characters like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+
/
=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These characters can be problematic in URLs.&lt;/p&gt;

&lt;p&gt;URL-safe Base64 replaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+ with -
/ with _
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Padding &lt;code&gt;=&lt;/code&gt; may also be removed in some systems.&lt;/p&gt;

&lt;p&gt;Example helper:&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;toBase64Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;base64&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="sr"&gt;/g&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/g&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/=+$/g&lt;/span&gt;&lt;span class="p"&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encodeBase64Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World&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;urlSafe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toBase64Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoded&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;urlSafe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// SGVsbG8gV29ybGQ&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To convert Base64URL back to standard Base64:&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;fromBase64Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Url&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;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64Url&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="sr"&gt;/-/g&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_/g&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="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;%&lt;/span&gt; &lt;span class="mi"&gt;4&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="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base64&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;Base64URL is commonly used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWTs&lt;/li&gt;
&lt;li&gt;URL tokens&lt;/li&gt;
&lt;li&gt;authentication flows&lt;/li&gt;
&lt;li&gt;compact identifiers&lt;/li&gt;
&lt;li&gt;web-safe encoded data&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Common Base64 encoding mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Thinking Base64 is encryption
&lt;/h3&gt;

&lt;p&gt;Base64 is not secure. It is reversible.&lt;/p&gt;

&lt;p&gt;Do not use it to protect passwords, API keys, or private data.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Using btoa() directly with Unicode
&lt;/h3&gt;

&lt;p&gt;This can break:&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;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Café 🚀&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;Use &lt;code&gt;TextEncoder&lt;/code&gt; instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Using standard Base64 in URLs
&lt;/h3&gt;

&lt;p&gt;Standard Base64 can contain &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, and &lt;code&gt;=&lt;/code&gt;. Use Base64URL when the value needs to go inside a URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Encoding large images unnecessarily
&lt;/h3&gt;

&lt;p&gt;Base64 is usually better for small assets, not large files.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Forgetting the MIME type for images
&lt;/h3&gt;

&lt;p&gt;For browser display, this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iVBORw0KGgo...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may not be enough.&lt;/p&gt;

&lt;p&gt;You often need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data:image/png;base64,iVBORw0KGgo...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Quick answer
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;btoa()&lt;/code&gt; only for simple ASCII text.&lt;/p&gt;

&lt;p&gt;For Unicode-safe Base64 encoding in JavaScript, convert text to UTF-8 bytes first using &lt;code&gt;TextEncoder&lt;/code&gt;, then encode those bytes with &lt;code&gt;btoa()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For files and images, use &lt;code&gt;FileReader.readAsDataURL()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For URLs, convert standard Base64 into Base64URL by replacing &lt;code&gt;+&lt;/code&gt; with &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt; with &lt;code&gt;_&lt;/code&gt;, and optionally removing &lt;code&gt;=&lt;/code&gt; padding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Base64 encoding is simple in theory, but real-world usage gets more complex when you deal with Unicode, files, images, URLs, and APIs.&lt;/p&gt;

&lt;p&gt;For quick testing, debugging, and conversion, a browser-based tool like &lt;a href="https://encodingbase64.com/" rel="noopener noreferrer"&gt;EncodingBase64&lt;/a&gt; can save time because you can encode text, files, images, URL-safe Base64, hex, and URLs without setting up code.&lt;/p&gt;

&lt;p&gt;The key rule to remember:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Base64 is an encoding format, not a security feature.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use it when you need text-safe data representation, not when you need encryption.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Decode Base64 Safely: Text, Images, Files, and Common Errors</title>
      <dc:creator>MOUSTAID Hamza</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:17:33 +0000</pubDate>
      <link>https://dev.to/hamzamoustaid/how-to-decode-base64-safely-text-images-files-and-common-errors-33df</link>
      <guid>https://dev.to/hamzamoustaid/how-to-decode-base64-safely-text-images-files-and-common-errors-33df</guid>
      <description>&lt;p&gt;Base64 decoding looks simple.&lt;/p&gt;

&lt;p&gt;You take a string 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;SGVsbG8gV29ybGQ=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decode it, and get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in real projects, Base64 decoding can fail for many reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing padding&lt;/li&gt;
&lt;li&gt;invalid characters&lt;/li&gt;
&lt;li&gt;Unicode problems&lt;/li&gt;
&lt;li&gt;Base64URL format&lt;/li&gt;
&lt;li&gt;binary data being treated as text&lt;/li&gt;
&lt;li&gt;image data missing a MIME type&lt;/li&gt;
&lt;li&gt;whitespace copied into the string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide explains how to decode Base64 safely in JavaScript, Python, and the command line, and how to troubleshoot the most common Base64 decoding issues.&lt;/p&gt;

&lt;p&gt;You can also test strings quickly with &lt;a href="https://decodingbase64.com/" rel="noopener noreferrer"&gt;DecodingBase64&lt;/a&gt;, a free client-side Base64 decoder that converts Base64 to text, images, hex output, or downloadable files.&lt;/p&gt;




&lt;h2&gt;
  
  
  What does Base64 decoding do?
&lt;/h2&gt;

&lt;p&gt;Base64 decoding reverses Base64 encoding.&lt;/p&gt;

&lt;p&gt;Base64 represents bytes using readable characters. Decoding converts those characters back into the original bytes.&lt;/p&gt;

&lt;p&gt;Those bytes may represent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;plain text&lt;/li&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;li&gt;an image&lt;/li&gt;
&lt;li&gt;a PDF&lt;/li&gt;
&lt;li&gt;a ZIP file&lt;/li&gt;
&lt;li&gt;binary data&lt;/li&gt;
&lt;li&gt;an API token&lt;/li&gt;
&lt;li&gt;a data URI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Base64 decoding does not always produce readable text.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the decoded output is binary data.&lt;/p&gt;

&lt;p&gt;That is why a good decoder should support text, image, file, and hex output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basic Base64 decoding in JavaScript
&lt;/h2&gt;

&lt;p&gt;JavaScript provides &lt;code&gt;atob()&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;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SGVsbG8gV29ybGQ=&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;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;decoded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Hello World&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for simple ASCII text.&lt;/p&gt;

&lt;p&gt;But it can break or produce incorrect output with Unicode text.&lt;/p&gt;




&lt;h2&gt;
  
  
  Unicode-safe Base64 decoding in JavaScript
&lt;/h2&gt;

&lt;p&gt;If the original text contained Unicode characters, you should decode bytes using &lt;code&gt;TextDecoder&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;function&lt;/span&gt; &lt;span class="nf"&gt;decodeBase64Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;binary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;char&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;return&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&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="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="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&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="nf"&gt;decodeBase64Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Q2Fmw6kg8J+agA==&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Café 🚀&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is safer for real-world text because it handles UTF-8 correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decode Base64 in Python
&lt;/h2&gt;

&lt;p&gt;Python has a built-in &lt;code&gt;base64&lt;/code&gt; module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SGVsbG8gV29ybGQ=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;decoded_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;decoded_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decoded_bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Hello World
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Unicode text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Q2Fmw6kg8J+agA==&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Café 🚀
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the output is binary, do not decode it as text. Save it as bytes instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decode Base64 from the command line
&lt;/h2&gt;

&lt;p&gt;On macOS and Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"SGVsbG8gV29ybGQ="&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To decode a Base64 file into a binary file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; input.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; output.bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; image-base64.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; image.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct output extension depends on the original file type.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to decode a Base64 image
&lt;/h2&gt;

&lt;p&gt;A Base64 image often looks 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;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is called a data URI.&lt;/p&gt;

&lt;p&gt;It has two important parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data:image/png;base64,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then the Base64 data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iVBORw0KGgoAAAANSUhEUgAA...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MIME type tells the browser what kind of file it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;image/png
image/jpeg
image/webp
image/gif
image/svg+xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only have the raw Base64 string, you may need to know the original MIME type to display or save it correctly.&lt;/p&gt;

&lt;p&gt;For image debugging, &lt;a href="https://decodingbase64.com/base64-to-image" rel="noopener noreferrer"&gt;DecodingBase64’s Base64 to Image tool&lt;/a&gt; can turn a Base64 image string back into a preview and downloadable image file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Base64URL decoding
&lt;/h2&gt;

&lt;p&gt;Some strings are not standard Base64. They are Base64URL.&lt;/p&gt;

&lt;p&gt;Base64URL is common in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWTs&lt;/li&gt;
&lt;li&gt;URL tokens&lt;/li&gt;
&lt;li&gt;authentication systems&lt;/li&gt;
&lt;li&gt;web-safe identifiers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Standard Base64 uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+
/
=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64URL may use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-
_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and often removes padding.&lt;/p&gt;

&lt;p&gt;To decode Base64URL in JavaScript, normalize it first:&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;base64UrlToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Url&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;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64Url&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="sr"&gt;/-/g&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_/g&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="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;%&lt;/span&gt; &lt;span class="mi"&gt;4&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="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decodeBase64Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Url&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;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;base64UrlToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Url&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;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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="nf"&gt;decodeBase64Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SGVsbG8gV29ybGQ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Hello World&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a standard decoder fails, check whether your string is actually Base64URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Base64 decoding errors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Incorrect padding
&lt;/h3&gt;

&lt;p&gt;Base64 strings often end with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;==
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Padding helps make the length valid.&lt;/p&gt;

&lt;p&gt;If padding is missing, some decoders fail.&lt;/p&gt;

&lt;p&gt;Fix:&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;addBase64Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;%&lt;/span&gt; &lt;span class="mi"&gt;4&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="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base64&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;h3&gt;
  
  
  2. Invalid characters
&lt;/h3&gt;

&lt;p&gt;Standard Base64 should usually contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A-Z
a-z
0-9
+
/
=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;_&lt;/code&gt;, it may be Base64URL.&lt;/p&gt;

&lt;p&gt;If you see spaces, quotes, commas, or copied line breaks, clean the input first.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Whitespace problems
&lt;/h3&gt;

&lt;p&gt;Copied Base64 strings may contain line breaks or spaces.&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;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&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;h3&gt;
  
  
  4. Decoding binary as text
&lt;/h3&gt;

&lt;p&gt;Not all decoded Base64 is readable.&lt;/p&gt;

&lt;p&gt;If the decoded bytes represent an image or file, showing them as text will look broken.&lt;/p&gt;

&lt;p&gt;Use a file output, image preview, or hex view instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Wrong character set
&lt;/h3&gt;

&lt;p&gt;If the decoded text looks strange, it may be a charset issue.&lt;/p&gt;

&lt;p&gt;Try UTF-8 first. If that fails, the original data may use another encoding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick answer
&lt;/h2&gt;

&lt;p&gt;To decode Base64 safely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clean whitespace.&lt;/li&gt;
&lt;li&gt;Detect whether it is standard Base64 or Base64URL.&lt;/li&gt;
&lt;li&gt;Add missing padding if needed.&lt;/li&gt;
&lt;li&gt;Decode to bytes.&lt;/li&gt;
&lt;li&gt;Interpret the bytes correctly as text, image, file, or binary data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not assume every Base64 string is plain text.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is Base64 secure?
&lt;/h2&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Base64 is not encryption.&lt;/p&gt;

&lt;p&gt;Anyone can decode it.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YXBpX2tleV8xMjM=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;decodes to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api_key_123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So never use Base64 alone to protect sensitive information.&lt;/p&gt;

&lt;p&gt;Base64 is useful for representation and transport, not security.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Base64 decoding is easy when the input is clean ASCII text.&lt;/p&gt;

&lt;p&gt;But real-world Base64 often includes images, files, Unicode, Base64URL, missing padding, or binary data.&lt;/p&gt;

&lt;p&gt;The safest approach is to decode carefully and inspect the output type.&lt;/p&gt;

&lt;p&gt;For fast testing, debugging, and file/image recovery, &lt;a href="https://decodingbase64.com/" rel="noopener noreferrer"&gt;DecodingBase64&lt;/a&gt; can help because it runs client-side and supports text, image, hex, and file outputs.&lt;/p&gt;

&lt;p&gt;Remember:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Base64 decoding gives you bytes. What those bytes mean depends on the original data.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Base64 vs Base64URL vs URL Encoding: When Should You Use Each?</title>
      <dc:creator>MOUSTAID Hamza</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:17:18 +0000</pubDate>
      <link>https://dev.to/hamzamoustaid/base64-vs-base64url-vs-url-encoding-when-should-you-use-each-4k3e</link>
      <guid>https://dev.to/hamzamoustaid/base64-vs-base64url-vs-url-encoding-when-should-you-use-each-4k3e</guid>
      <description>&lt;p&gt;Base64, Base64URL, and URL encoding are often confused because they all seem to “encode” data.&lt;/p&gt;

&lt;p&gt;But they solve different problems.&lt;/p&gt;

&lt;p&gt;Using the wrong one can break URLs, APIs, JWTs, image data, or browser behavior.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base64&lt;/li&gt;
&lt;li&gt;Base64URL&lt;/li&gt;
&lt;li&gt;URL encoding / percent encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll learn what each one does, where each one is useful, and how to choose the right format.&lt;/p&gt;

&lt;p&gt;For quick testing, you can use &lt;a href="https://encodingbase64.com/" rel="noopener noreferrer"&gt;EncodingBase64&lt;/a&gt; for Base64, Base64URL, URL encoding, hex, and image-to-Base64 conversions. If you need to reverse Base64 back into text, images, or files, &lt;a href="https://decodingbase64.com/" rel="noopener noreferrer"&gt;DecodingBase64&lt;/a&gt; is the matching decoder.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick answer
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;Base64&lt;/strong&gt; when you need to represent binary data as text.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;Base64URL&lt;/strong&gt; when Base64 data must safely appear inside URLs, JWTs, or URL tokens.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;URL encoding&lt;/strong&gt; when you need to safely place normal text inside a URL query string or path.&lt;/p&gt;

&lt;p&gt;They are not interchangeable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Base64?
&lt;/h2&gt;

&lt;p&gt;Base64 converts bytes into readable ASCII characters.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SGVsbG8gV29ybGQ=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64 is commonly used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sending binary data in JSON&lt;/li&gt;
&lt;li&gt;embedding small images&lt;/li&gt;
&lt;li&gt;email attachments&lt;/li&gt;
&lt;li&gt;API payloads&lt;/li&gt;
&lt;li&gt;data URIs&lt;/li&gt;
&lt;li&gt;basic text-safe representation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Base64 uses characters like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A-Z
a-z
0-9
+
/
=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;=&lt;/code&gt; character is padding.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use Base64
&lt;/h2&gt;

&lt;p&gt;Use Base64 when the original data is binary or byte-based but needs to be represented as text.&lt;/p&gt;

&lt;p&gt;Good use cases include:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Embedding small images
&lt;/h3&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;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Sending binary data in JSON
&lt;/h3&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;"filename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"avatar.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iVBORw0KGgoAAAANSUhEUgAA..."&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;h3&gt;
  
  
  3. Encoding small files for transport
&lt;/h3&gt;

&lt;p&gt;Some APIs accept file content as Base64 strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Encoding credentials in specific protocols
&lt;/h3&gt;

&lt;p&gt;Basic authentication uses Base64 representation for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;username:password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But remember: this is not secure by itself. It must be used over HTTPS.&lt;/p&gt;




&lt;h2&gt;
  
  
  When not to use Base64
&lt;/h2&gt;

&lt;p&gt;Do not use Base64 for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encryption&lt;/li&gt;
&lt;li&gt;password protection&lt;/li&gt;
&lt;li&gt;hiding secrets&lt;/li&gt;
&lt;li&gt;large images when normal files work better&lt;/li&gt;
&lt;li&gt;URL parameters without URL-safe conversion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Base64 increases data size, often by about one-third.&lt;/p&gt;

&lt;p&gt;That is acceptable in many cases, but not always ideal.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Base64URL?
&lt;/h2&gt;

&lt;p&gt;Base64URL is a URL-safe version of Base64.&lt;/p&gt;

&lt;p&gt;Standard Base64 may contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+
/
=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These characters can cause issues in URLs.&lt;/p&gt;

&lt;p&gt;Base64URL replaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+ with -
/ with _
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Padding &lt;code&gt;=&lt;/code&gt; is often removed.&lt;/p&gt;

&lt;p&gt;Example standard Base64:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SGVsbG8+V29ybGQ/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64URL version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SGVsbG8-V29ybGQ_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64URL is common in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWTs&lt;/li&gt;
&lt;li&gt;URL-safe tokens&lt;/li&gt;
&lt;li&gt;password reset links&lt;/li&gt;
&lt;li&gt;email verification links&lt;/li&gt;
&lt;li&gt;OAuth flows&lt;/li&gt;
&lt;li&gt;compact identifiers&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why standard Base64 can break in URLs
&lt;/h2&gt;

&lt;p&gt;Imagine this Base64 string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;abc+def/ghi==
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If placed directly inside a URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/?token=abc+def/ghi==
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;+&lt;/code&gt; may be interpreted as a space in some URL contexts.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/&lt;/code&gt; may be interpreted as a path separator.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;=&lt;/code&gt; can interfere with query parsing.&lt;/p&gt;

&lt;p&gt;That is why Base64URL exists.&lt;/p&gt;

&lt;p&gt;A safer token would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;abc-def_ghi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Base64URL helper in JavaScript
&lt;/h2&gt;

&lt;p&gt;Convert standard Base64 to Base64URL:&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;toBase64Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;base64&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="sr"&gt;/g&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/g&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/=+$/g&lt;/span&gt;&lt;span class="p"&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;Convert Base64URL back to standard Base64:&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;fromBase64Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Url&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;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64Url&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="sr"&gt;/-/g&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_/g&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="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;%&lt;/span&gt; &lt;span class="mi"&gt;4&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="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base64&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;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;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SGVsbG8gV29ybGQ=&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;base64Url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toBase64Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;base64Url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// SGVsbG8gV29ybGQ&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What is URL encoding?
&lt;/h2&gt;

&lt;p&gt;URL encoding, also called percent encoding, is different from Base64.&lt;/p&gt;

&lt;p&gt;It makes text safe for URLs by replacing unsafe characters with &lt;code&gt;%&lt;/code&gt; followed by hexadecimal values.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hello world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hello%20world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;email@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;email%40example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In JavaScript, you can 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="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// hello%20world&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a full URL, you may 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="nf"&gt;encodeURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/search?q=hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// https://example.com/search?q=hello%20world&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  encodeURI vs encodeURIComponent
&lt;/h2&gt;

&lt;p&gt;These two are often confused.&lt;/p&gt;

&lt;h3&gt;
  
  
  encodeURI()
&lt;/h3&gt;

&lt;p&gt;Use it for a full URL:&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;encodeURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/search?q=hello world&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 keeps URL structure characters like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:
/
?
&amp;amp;
=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  encodeURIComponent()
&lt;/h3&gt;

&lt;p&gt;Use it for a URL parameter value:&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;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello world &amp;amp; coffee&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://example.com/search?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&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="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;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// https://example.com/search?q=hello%20world%20%26%20coffee&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the time, if you are encoding a query parameter value, use &lt;code&gt;encodeURIComponent()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Base64 vs URL encoding
&lt;/h2&gt;

&lt;p&gt;Here is the key difference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Base64&lt;/th&gt;
&lt;th&gt;URL Encoding&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Main purpose&lt;/td&gt;
&lt;td&gt;Represent bytes as text&lt;/td&gt;
&lt;td&gt;Make URL text safe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Used for binary data&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Used for query params&lt;/td&gt;
&lt;td&gt;Not directly&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output style&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SGVsbG8=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hello%20world&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reversible&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security feature&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want to encode an image, use Base64.&lt;/p&gt;

&lt;p&gt;If you want to safely put a search query inside a URL, use URL encoding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Base64URL vs URL encoding
&lt;/h2&gt;

&lt;p&gt;Base64URL is still Base64.&lt;/p&gt;

&lt;p&gt;It is designed to make Base64 output safe for URLs.&lt;/p&gt;

&lt;p&gt;URL encoding is designed to make normal text safe for URLs.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Original text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SGVsbG8gV29ybGQ=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SGVsbG8gV29ybGQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;URL encoded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello%20World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They look different because they solve different problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-world examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example 1: API file upload
&lt;/h3&gt;

&lt;p&gt;You have image bytes and need to send them in JSON.&lt;/p&gt;

&lt;p&gt;Use Base64.&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;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iVBORw0KGgoAAAANSUhEUgAA..."&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;h3&gt;
  
  
  Example 2: JWT token
&lt;/h3&gt;

&lt;p&gt;JWTs use Base64URL.&lt;/p&gt;

&lt;p&gt;A JWT looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;header.payload.signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each part is Base64URL encoded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Search query
&lt;/h3&gt;

&lt;p&gt;You want a URL like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/search?q=coffee &amp;amp; tea
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use URL encoding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/search?q=coffee%20%26%20tea
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 4: Small inline SVG or PNG
&lt;/h3&gt;

&lt;p&gt;Use Base64 or URL-encoded SVG depending on the case.&lt;/p&gt;

&lt;p&gt;For small PNG/JPEG/WebP assets, Base64 is common.&lt;/p&gt;

&lt;p&gt;For SVG, URL encoding can sometimes be more readable and efficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mistake 1: Using Base64 as encryption
&lt;/h3&gt;

&lt;p&gt;Base64 can be decoded by anyone.&lt;/p&gt;

&lt;p&gt;It does not protect data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 2: Putting standard Base64 directly in URLs
&lt;/h3&gt;

&lt;p&gt;Characters like &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, and &lt;code&gt;=&lt;/code&gt; can cause issues.&lt;/p&gt;

&lt;p&gt;Use Base64URL or URL encode the value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 3: Using URL encoding for binary files
&lt;/h3&gt;

&lt;p&gt;URL encoding is not designed for raw binary file representation.&lt;/p&gt;

&lt;p&gt;Use Base64 instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 4: Forgetting to decode in the right order
&lt;/h3&gt;

&lt;p&gt;If data is URL encoded and Base64 encoded, the order matters.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;URL decode&lt;/li&gt;
&lt;li&gt;Base64 decode&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;or the reverse, depending on how the data was originally encoded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 5: Not handling Unicode
&lt;/h3&gt;

&lt;p&gt;JavaScript’s &lt;code&gt;btoa()&lt;/code&gt; and &lt;code&gt;atob()&lt;/code&gt; are not always Unicode-safe by themselves.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;TextEncoder&lt;/code&gt; and &lt;code&gt;TextDecoder&lt;/code&gt; when working with modern text.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which one should you choose?
&lt;/h2&gt;

&lt;p&gt;Use this simple rule:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Encode text or binary as ASCII&lt;/td&gt;
&lt;td&gt;Base64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Put Base64 safely in a URL&lt;/td&gt;
&lt;td&gt;Base64URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encode a URL query parameter&lt;/td&gt;
&lt;td&gt;URL encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encode an image as text&lt;/td&gt;
&lt;td&gt;Base64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encode a JWT payload&lt;/td&gt;
&lt;td&gt;Base64URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encode spaces and symbols in URLs&lt;/td&gt;
&lt;td&gt;URL encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hide sensitive data&lt;/td&gt;
&lt;td&gt;None of these&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For sensitive data, use proper encryption, hashing, HTTPS, and secure storage depending on the use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Base64, Base64URL, and URL encoding are all useful, but they are not the same.&lt;/p&gt;

&lt;p&gt;Base64 helps represent bytes as text.&lt;/p&gt;

&lt;p&gt;Base64URL makes Base64 safer for URLs and tokens.&lt;/p&gt;

&lt;p&gt;URL encoding makes normal URL text safe.&lt;/p&gt;

&lt;p&gt;When debugging encoded data, first identify what kind of encoding you are dealing with. Then choose the right tool or function.&lt;/p&gt;

&lt;p&gt;For browser-based testing, &lt;a href="https://encodingbase64.com/" rel="noopener noreferrer"&gt;EncodingBase64&lt;/a&gt; is useful for encoding workflows like Base64, Base64URL, URL encoding, image to Base64, and hex. For reverse workflows, &lt;a href="https://decodingbase64.com/" rel="noopener noreferrer"&gt;DecodingBase64&lt;/a&gt; helps decode Base64 back into text, images, hex, or files.&lt;/p&gt;

&lt;p&gt;The main thing to remember:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Encoding is about representation. It is not the same as encryption.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A QA engineer's first AI testing project - FastAPI + local LLM + pytest</title>
      <dc:creator>Sara Bezjak</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:15:15 +0000</pubDate>
      <link>https://dev.to/sara_bezjak/a-qa-engineers-first-ai-testing-project-fastapi-local-llm-pytest-5b1c</link>
      <guid>https://dev.to/sara_bezjak/a-qa-engineers-first-ai-testing-project-fastapi-local-llm-pytest-5b1c</guid>
      <description>&lt;p&gt;I'm an automation engineer that writes mostly UI tests with some API sprinkled in. A recruiter wrote to me about an interesting job - AI/LLM testing. I was curious to learn more so I asked the model itself: what skills do I need to learn? The answer was this project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is it
&lt;/h2&gt;

&lt;p&gt;A FastAPI service with one endpoint (&lt;code&gt;/ask&lt;/code&gt;) that forwards a question to a local LLM (Ollama running llama3.2) and returns the answer. Plus a pytest suite.&lt;/p&gt;

&lt;p&gt;~90 lines of app code, 23 tests, 100% coverage, two-tier test split (fast &amp;lt;1s, full ~90s).&lt;/p&gt;

&lt;p&gt;The point was to learn what AI testing actually looks like compared to UI/API testing.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/sbezjak/llm-api-testing" rel="noopener noreferrer"&gt;https://github.com/sbezjak/llm-api-testing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One honest thing up front.&lt;/strong&gt; The suite worked first try. That made it harder to learn from, not easier — when nothing breaks, you don't have to understand it. I spent more time reading the code than I would have spent writing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Process timeline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Read every line before running anything.&lt;/strong&gt; Docs, code, tests, setup. I wanted the big picture — classes, endpoints, test structure in my head before I touched anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Ask questions instead of copy-pasting.&lt;/strong&gt; It's easy to create something that passes. It's harder to understand why it does. I spent 2 hours just discussing the project with the model. Questions like: Why 70% and not 100%? What does &lt;code&gt;ASGITransport&lt;/code&gt; actually do? Why does &lt;code&gt;ConnectError&lt;/code&gt; map to 503 and HTTP errors to 502? Why mock at all with &lt;code&gt;respx&lt;/code&gt;? What's xfail and why is it used like this? What's temperature?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Ran it. All passed.&lt;/strong&gt; But "10 passed in 99s" wasn't enough. I wanted to see which tests hit the model, how long each took, what the model actually answered. So I added structured logging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /ask verdict=allowed status=200 elapsed=0.42s answer='Paris.'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a &lt;code&gt;pytest-html&lt;/code&gt; report with per-test captured logs. Now every test run is a document I can read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Iterate with the model.&lt;/strong&gt; Added logs, reports, comments. Asked about code I didn't understand — why something was there, what a piece did. This is where the differences between UI and AI testing started to click. Probabilistic vs deterministic. The 70% Paris case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Make it production-ish.&lt;/strong&gt; Asked how a real team would harden this. Mocking Ollama and 100% coverage were added in this step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing that actually clicked — probabilistic vs deterministic
&lt;/h2&gt;

&lt;p&gt;The consistency test sends "What is the capital of France?" ten times and asserts ≥70% of answers contain "paris".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paris&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In UI testing, same input produces the same output. You assert on exact values. &lt;code&gt;assert button.opens_modal() == True&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;LLMs don't work like that. Same prompt, different valid answers every call — "Paris.", "The capital is Paris.", a paragraph about French geography. The model samples from a distribution. There is no single right string.&lt;/p&gt;

&lt;p&gt;So you assert on properties of the distribution, or on the envelope of acceptable answers. &lt;code&gt;assert ≥70% of answers contain "paris"&lt;/code&gt;. 70% is arbitrary — high enough to catch regressions, low enough to tolerate the model's variance. In a real system you'd tune per prompt. &lt;/p&gt;

&lt;p&gt;Point vs region. Four years of UI-testing instincts took a while to shift.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three bugs and what they taught me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bug 1 — latency test failing at 35s.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First thought: my M1 is slow. Then I ran &lt;code&gt;ollama run llama3.2 "say hi"&lt;/code&gt; directly in the terminal — instant. So the model was fine.&lt;/p&gt;

&lt;p&gt;llama3.2 is chatty. Asking "string" produced an essay on null-termination and Unicode. The 35 seconds was generation time, not system latency.&lt;/p&gt;

&lt;p&gt;Fix: &lt;code&gt;"options": {"num_predict": 200}&lt;/code&gt; to cap output tokens. Warm requests dropped to 1-3 seconds.&lt;/p&gt;

&lt;p&gt;Lesson: traditional APIs return what you ask for. LLMs return what they feel like returning. Latency tests measure output length unless you constrain it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 2 — coverage stuck at 85%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cause: no test exercised Ollama failure paths.&lt;/p&gt;

&lt;p&gt;Fix: three mocked tests with &lt;code&gt;respx&lt;/code&gt; — unreachable → 503, Ollama 5xx → 502, empty response → 502. Coverage hit 100%. New tests run in &amp;lt;50ms each because no real model is involved.&lt;/p&gt;

&lt;p&gt;Lesson: check coverage reports. Gaps usually point at untested failure modes, not untested happy paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 3 — moderation filter false positives.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The moderation filter is a substring blocklist — a Python list of phrases like &lt;code&gt;"how to kill"&lt;/code&gt;, &lt;code&gt;"how to hack"&lt;/code&gt;, etc. Any question containing one gets refused with a 400. Simple: &lt;code&gt;"how to kill a process on linux"&lt;/code&gt; contains &lt;code&gt;"how to kill"&lt;/code&gt;, so a normal dev question gets blocked.&lt;/p&gt;

&lt;p&gt;Fix: added the false positive to the benign dataset with &lt;code&gt;pytest.mark.xfail&lt;/code&gt; and a written reason. The test now runs, fails as expected, and shows as a yellow dot in the report instead of red. Documented in the suite itself.&lt;/p&gt;

&lt;p&gt;It flips to green the day the substring is replaced with a real classifier — a model that understands &lt;em&gt;intent&lt;/em&gt; ("is this user actually trying to cause harm?") instead of just matching strings. That could be a small fine-tuned model, an open-source moderation model like Llama Guard, or a commercial moderation API. The upgrade closes the false-positive gap, the test starts passing, and &lt;code&gt;xfail(strict=False)&lt;/code&gt; signals "unexpectedly passed" — the cue to remove the marker.&lt;/p&gt;

&lt;p&gt;Lesson: xfail makes the suite record what's broken, not just what works. I'd only used xfail for flaky tests before, not as living documentation of known bugs. Much better than hiding a bug in a backlog ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still don't fully understand
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The ASGI internals &lt;code&gt;ASGITransport&lt;/code&gt; relies on. I know what it does, not what's happening inside.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;respx&lt;/code&gt; is the right call vs building a proper fake.&lt;/li&gt;
&lt;li&gt;Embedding similarity math beyond "cosine measures angle."&lt;/li&gt;
&lt;li&gt;What a real production eval harness looks like.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  From a QA perspective
&lt;/h2&gt;

&lt;p&gt;Most UI-testing instincts didn't transfer. Equality assertions, fixed latency thresholds, asserting a single correct outcome — all had to shift.&lt;/p&gt;

&lt;p&gt;What did transfer: discipline around edge cases, thoughts about what happens when the upstream service dies, care about keeping the feedback loop fast, coverage reports.&lt;/p&gt;

&lt;p&gt;Setting up a local model was new. Using it as a dependency in a test suite was new. Testing something that returns different valid outputs every call was new. If you're a QA engineer looking at this direction — the probability side is the new thing. The rest is still testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install &amp;amp; start Ollama&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;ollama
ollama serve             &lt;span class="c"&gt;# leave running in its own terminal&lt;/span&gt;
ollama pull llama3.2     &lt;span class="c"&gt;# in another terminal&lt;/span&gt;

&lt;span class="c"&gt;# Python env&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Run the API&lt;/span&gt;
uvicorn app.main:app &lt;span class="nt"&gt;--reload&lt;/span&gt; &lt;span class="nt"&gt;--port&lt;/span&gt; 8000
&lt;span class="c"&gt;# → http://localhost:8000/docs&lt;/span&gt;

&lt;span class="c"&gt;# Tests&lt;/span&gt;
pytest &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"not ollama"&lt;/span&gt;   &lt;span class="c"&gt;# fast tier, no Ollama needed, ~1s&lt;/span&gt;
pytest                   &lt;span class="c"&gt;# full suite with HTML reports&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;When you're testing robustness (did the system stay well-behaved?) instead of correctness (did the right thing happen?), you assert the shape of acceptable failure, not the shape of success. AI systems fail in more ways, so the distinction matters more — a 500 is always a bug; anything else might be correct behavior for an edge case.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/sbezjak/llm-api-testing" rel="noopener noreferrer"&gt;https://github.com/sbezjak/llm-api-testing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next up — 5 more projects on the list: eval harness, RAG with observability, red-team suite, agent testing, model benchmarking. Writing each one up as I go.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>python</category>
      <category>qa</category>
    </item>
    <item>
      <title>Step-by-Step Webhook Signature Verification for Any Sender</title>
      <dc:creator>137Foundry</dc:creator>
      <pubDate>Fri, 24 Apr 2026 11:15:03 +0000</pubDate>
      <link>https://dev.to/137foundry/step-by-step-webhook-signature-verification-for-any-sender-2nje</link>
      <guid>https://dev.to/137foundry/step-by-step-webhook-signature-verification-for-any-sender-2nje</guid>
      <description>&lt;p&gt;Webhook signature verification is the first line of defense against forged events. Without it, any HTTP client that knows your endpoint URL can POST fabricated events. The verification process is the same across most webhook senders, even when the specific header names and hash algorithms differ.&lt;/p&gt;

&lt;p&gt;This guide walks through implementing signature verification for a webhook receiver, from parsing the header to computing the HMAC to returning the right response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Get the Raw Request Body Before Any Parsing
&lt;/h2&gt;

&lt;p&gt;This is the step most implementations get wrong. Signature verification computes an HMAC over the raw request body bytes. The HMAC must be computed before any framework deserialization happens.&lt;/p&gt;

&lt;p&gt;Web frameworks parse JSON bodies automatically. When they do, they may normalize whitespace, change encoding, or reorder keys. Computing the HMAC over the parsed-then-re-serialized body will produce a different hash than the sender computed over the original bytes. The verification will fail even for valid payloads.&lt;/p&gt;

&lt;p&gt;In Python with FastAPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/webhooks/events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;receive_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;raw_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# raw bytes, before any JSON parsing
&lt;/span&gt;    &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Signature-256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# verify against raw_body, not json.loads(raw_body)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Node.js with Express:&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Buffer, not parsed JSON&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-signature-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="c1"&gt;// verify against rawBody&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;express.raw()&lt;/code&gt; middleware captures the body as a Buffer instead of parsing it. Without it, &lt;code&gt;req.body&lt;/code&gt; contains the parsed JSON object, which breaks verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Parse the Signature Header
&lt;/h2&gt;

&lt;p&gt;Webhook senders typically include both the HMAC hash and a timestamp in the signature header, either as separate headers or combined in a single header. &lt;a href="https://stripe.com" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; combines them in a single header with a specific format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stripe-Signature: t=1714000000,v1=abc123...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parse the header to extract the timestamp and the hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_stripe_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;header&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;

&lt;span class="n"&gt;sig_parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_stripe_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stripe-Signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sig_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;received_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sig_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other senders use a simpler format with the hash prefixed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-Signature-256: sha256=abc123...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;received_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signature_header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeprefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the sender's documentation for their specific header format. The concept is the same; only the parsing differs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Compute the Expected HMAC
&lt;/h2&gt;

&lt;p&gt;With the raw body bytes and the shared secret, compute the expected HMAC using the algorithm your sender specifies. Most use SHA-256.&lt;/p&gt;

&lt;p&gt;For Stripe-style signatures where the hash is computed over &lt;code&gt;{timestamp}.{body}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_hmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;signed_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;raw_body&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;signed_payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For simpler senders where the hash is computed directly over the body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_hmac_simple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;raw_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Compare Using Constant-Time Equality
&lt;/h2&gt;

&lt;p&gt;Never use &lt;code&gt;==&lt;/code&gt; to compare the expected and received hashes. Standard string equality short-circuits on the first differing character, which creates a timing side channel. An attacker can measure how long the comparison takes to determine how many leading characters of the hash they got right, eventually reconstructing a valid hash without knowing the secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;received_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hmac.compare_digest&lt;/code&gt; takes the same amount of time regardless of where the strings differ. The equivalent in JavaScript uses the &lt;code&gt;crypto&lt;/code&gt; module's &lt;code&gt;timingSafeEqual&lt;/code&gt; function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Check the Timestamp Window
&lt;/h2&gt;

&lt;p&gt;If the sender includes a timestamp in the signature header, check that it's within an acceptable window (typically five minutes). This prevents replay attacks where an attacker captures a valid signed request and re-sends it later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_timestamp_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_age_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&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="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestamp&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;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_age_seconds&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the timestamp is more than five minutes old, return 400 and log the event for investigation. The sender should not be sending payloads with stale timestamps; this is a potential replay attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Return the Right Status Codes
&lt;/h2&gt;

&lt;p&gt;The response status code tells the sender whether to retry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;200&lt;/strong&gt;: Event received and accepted. Don't retry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;400&lt;/strong&gt;: Invalid signature or malformed request. Don't retry (retries won't fix a tampered payload).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;500&lt;/strong&gt;: Server error. Retry according to retry policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Return 400 on signature failures, not 500. A 500 tells the sender to retry, which is wrong behavior for a forged payload. A 400 tells the sender the request was rejected as invalid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Verification Logic
&lt;/h2&gt;

&lt;p&gt;The most important tests to write:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Valid signature passes verification&lt;/li&gt;
&lt;li&gt;Tampered body fails verification&lt;/li&gt;
&lt;li&gt;Wrong secret fails verification&lt;/li&gt;
&lt;li&gt;Missing signature header returns 400&lt;/li&gt;
&lt;li&gt;Expired timestamp is rejected&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use &lt;a href="https://www.postman.com" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; to send test requests with custom signature headers to a running server. &lt;a href="https://ngrok.com" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; lets you test against a real external sender during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Senders That Deviate from the Standard Pattern
&lt;/h2&gt;

&lt;p&gt;Not all webhook senders follow the same verification scheme. Most use HMAC-SHA256 with a shared secret, but the payload construction, header format, and timestamp handling vary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timestamp in the signed payload.&lt;/strong&gt; Stripe constructs the signed payload as &lt;code&gt;{timestamp}.{raw_body}&lt;/code&gt;, not just the raw body. This means you need to extract the timestamp from the signature header, construct the signed string manually before computing the HMAC, and verify that string against the received hash. A receiver that computes the HMAC over just the raw body will fail verification for Stripe webhooks even if the secret is correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple hash versions.&lt;/strong&gt; Some senders include multiple hash versions in the header (for example, both a v0 and v1 hash). The receiver should check whether any of the provided hashes matches the expected value, rather than requiring a specific version. This allows the sender to rotate their signing scheme without breaking receivers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No timestamp.&lt;/strong&gt; Some simpler webhook senders include only the HMAC hash with no timestamp. For these, timestamp validation isn't possible, but you should still verify the HMAC. The absence of a timestamp means replay attacks are technically possible, which is worth noting in your integration documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom algorithms.&lt;/strong&gt; A small number of senders use HMAC-SHA1 instead of SHA256. HMAC-SHA1 is not considered broken for this use case, but SHA256 is preferred. Implement the algorithm your specific sender specifies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Verification Failures in Production
&lt;/h2&gt;

&lt;p&gt;A few signature verification failures that appear consistently across production webhook integrations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Middleware that reads the body.&lt;/strong&gt; Some logging middleware or request parsing middleware reads the request body before your verification code runs. If the body is consumed and not replaced with the original bytes, your verification code gets an empty body. Make sure any body-reading middleware restores the raw bytes before the request reaches the webhook handler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content-Type normalization.&lt;/strong&gt; Some frameworks normalize or re-parse the body when the Content-Type header is &lt;code&gt;application/json&lt;/code&gt;. Using &lt;code&gt;express.raw()&lt;/code&gt; or the framework-equivalent prevents this. Always test signature verification explicitly in your actual framework environment, not just in isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Header name case sensitivity.&lt;/strong&gt; HTTP header names are case-insensitive by spec but some implementations are not. If your verification code looks for &lt;code&gt;X-Signature-256&lt;/code&gt; but the sender sends &lt;code&gt;x-signature-256&lt;/code&gt;, some frameworks will pass it through and some won't. Use case-insensitive header lookup.&lt;/p&gt;

&lt;p&gt;For the broader receiver architecture (async processing, idempotency, failure handling), &lt;a href="https://137foundry.com/articles/webhook-receiver-production-guide" rel="noopener noreferrer"&gt;How to Build a Webhook Receiver That Handles Real-World Traffic&lt;/a&gt; covers how signature verification fits into the complete pattern.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://137foundry.com" rel="noopener noreferrer"&gt;This development service&lt;/a&gt; builds and maintains data integration infrastructure including webhook receivers, event processors, and API integrations for production workloads.&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%2Fd6cf3a1qbuodg0n0waa3.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%2Fd6cf3a1qbuodg0n0waa3.jpg" alt="Security key lock mechanism close-up" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by &lt;a href="https://pixabay.com/users/Hans-2/" rel="noopener noreferrer"&gt;Hans&lt;/a&gt; on &lt;a href="https://pixabay.com" rel="noopener noreferrer"&gt;Pixabay&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>api</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
