<?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>Forem: Quicreatdev</title>
    <description>The latest articles on Forem by Quicreatdev (@quicreatdev).</description>
    <link>https://forem.com/quicreatdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3775706%2Fc961b8f3-850e-4467-ab8c-f96a293a6bfe.png</url>
      <title>Forem: Quicreatdev</title>
      <link>https://forem.com/quicreatdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/quicreatdev"/>
    <language>en</language>
    <item>
      <title>How to build a blazing fast EU VAT Validation Rule in Laravel 🚀</title>
      <dc:creator>Quicreatdev</dc:creator>
      <pubDate>Thu, 12 Mar 2026 12:37:50 +0000</pubDate>
      <link>https://forem.com/quicreatdev/how-to-build-a-blazing-fast-eu-vat-validation-rule-in-laravel-4nji</link>
      <guid>https://forem.com/quicreatdev/how-to-build-a-blazing-fast-eu-vat-validation-rule-in-laravel-4nji</guid>
      <description>&lt;p&gt;If you are building a B2B SaaS or an e-commerce platform in Europe with Laravel, you eventually hit the same wall: EU VAT validation.&lt;/p&gt;

&lt;p&gt;To legally apply the reverse charge mechanism (0% VAT), you must ensure your customer's VAT number is valid using the European Commission's VIES system. But integrating directly with the official VIES API is a pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It frequently goes down or times out.&lt;/li&gt;
&lt;li&gt;It's synchronous and slow, which hurts your checkout conversion rate.&lt;/li&gt;
&lt;li&gt;It limits concurrent requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of writing complex fallback logic and caching mechanisms from scratch, let's build a clean, robust Laravel Custom Validation Rule using &lt;a href="https://vatflow.net" rel="noopener noreferrer"&gt;VatFlow&lt;/a&gt;, a serverless proxy that caches VIES data for instant responses.&lt;/p&gt;

&lt;p&gt;Let's dive in! 🛠️&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A Laravel application (v9, v10, or v11).&lt;/li&gt;
&lt;li&gt;A free VatFlow API Key via RapidAPI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Install the SDK
&lt;/h3&gt;

&lt;p&gt;VatFlow provides an official, zero-dependency PHP wrapper that handles auto-retries out of the box. You can check out the source code on &lt;a href="https://github.com/Baptiste-Pignol/vatflow-php" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; (feel free to drop a ⭐!). &lt;/p&gt;

&lt;p&gt;Install it via Composer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require quicreatdev/vatflow-php

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Configure your API Key
&lt;/h3&gt;

&lt;p&gt;Add your RapidAPI key to your &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Then, map it in your &lt;code&gt;config/services.php&lt;/code&gt; file so Laravel can access it cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/services.php&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other services&lt;/span&gt;
    &lt;span class="s1"&gt;'vatflow'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'VATFLOW_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Create the Custom Validation Rule
&lt;/h3&gt;

&lt;p&gt;Laravel makes it incredibly easy to create custom rules. Run this artisan command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:rule ValidEuVat

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

&lt;/div&gt;



&lt;p&gt;Now, open the generated file &lt;code&gt;app/Rules/ValidEuVat.php&lt;/code&gt; and implement the logic using the VatFlow client. We will configure it to use cached data (up to 7 days old) to guarantee a response time of a few milliseconds during checkout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Rules&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Closure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Validation\ValidationRule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;VatFlow\VatFlowClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidEuVat&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ValidationRule&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Run the validation rule.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Initialize the client with your key&lt;/span&gt;
        &lt;span class="nv"&gt;$apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.vatflow.key'&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="nv"&gt;$apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'The VatFlow API key is missing.'&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="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$client&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;VatFlowClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Call the API (max cache age: 7 days, auto-retry: true)&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Handle network or API errors gracefully&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="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// We log the error, but we DO NOT fail the validation.&lt;/span&gt;
            &lt;span class="c1"&gt;// It's better to temporarily accept the order and check manually later &lt;/span&gt;
            &lt;span class="c1"&gt;// rather than losing a customer because the government API is down!&lt;/span&gt;
            &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'VAT Validation Service Unavailable: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'error'&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="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 4. Check if the VAT is actually valid&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="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'is_valid'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'The provided EU VAT number is invalid or inactive.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Use it in your Controller or Form Request
&lt;/h3&gt;

&lt;p&gt;Now for the beautiful part. You can use your new rule just like any native Laravel rule!&lt;/p&gt;

&lt;p&gt;Let's say you have a company registration form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Rules\ValidEuVat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CheckoutController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$validated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'company_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'max:255'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'vat_number'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidEuVat&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// If the code reaches here, the VAT number is 100% valid!&lt;/span&gt;
        &lt;span class="c1"&gt;// Proceed with user creation and 0% VAT billing...&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Company registered successfully!'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;By moving the VAT validation into a Custom Rule and using a cached proxy like VatFlow, your controllers stay incredibly clean, and your checkout process remains blazing fast—even when the European Commission's servers are struggling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus tip:&lt;/strong&gt; The API response also returns the company's official name and address. Since Laravel Validation Rules aren't meant to modify the Request data, if you want to use this data to auto-fill your customer's profile, you can simply call the &lt;code&gt;VatFlowClient&lt;/code&gt; directly in your Controller or within a dedicated Action class right after validation!&lt;/p&gt;

&lt;p&gt;If you want to try it out, you can get a free API key and check out the documentation on the &lt;a href="https://vatflow.net" rel="noopener noreferrer"&gt;VatFlow website&lt;/a&gt;. You can also find the PHP package directly on &lt;a href="https://packagist.org/packages/quicreatdev/vatflow-php" rel="noopener noreferrer"&gt;Packagist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Have you ever struggled with the VIES API in your Laravel projects? Let me know in the comments! 👇&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>saas</category>
    </item>
    <item>
      <title>I got tired of the official EU VAT API crashing, so I built a Serverless wrapper with Webhooks 🚀</title>
      <dc:creator>Quicreatdev</dc:creator>
      <pubDate>Tue, 03 Mar 2026 12:59:24 +0000</pubDate>
      <link>https://forem.com/quicreatdev/i-got-tired-of-the-official-eu-vat-api-crashing-so-i-built-a-serverless-wrapper-with-webhooks-4il5</link>
      <guid>https://forem.com/quicreatdev/i-got-tired-of-the-official-eu-vat-api-crashing-so-i-built-a-serverless-wrapper-with-webhooks-4il5</guid>
      <description>&lt;p&gt;Hello DEV community! 👋&lt;/p&gt;

&lt;p&gt;If you've ever built a B2B SaaS or an e-commerce checkout in Europe, you know the struggle. By law, you have to validate your customers' VAT numbers to apply the reverse charge mechanism.&lt;/p&gt;

&lt;p&gt;The official way to do this is via the European Commission's VIES API. But there are a few huge problems with it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It frequently crashes or rate-limits you during business hours.&lt;/li&gt;
&lt;li&gt;It's incredibly slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The biggest issue:&lt;/strong&gt; It only tells you the status &lt;em&gt;today&lt;/em&gt;. If your biggest client goes bankrupt or closes next month, you won't know until the unpaid invoices pile up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted a modern, fast, and proactive solution. So, I built &lt;strong&gt;&lt;a href="https://vatflow.net/" rel="noopener noreferrer"&gt;VatFlow&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛠 What I built
&lt;/h3&gt;

&lt;p&gt;VatFlow is a REST API hosted on RapidAPI that acts as a smart shield and monitor for B2B company data.&lt;/p&gt;

&lt;p&gt;Here is what makes it different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚡️ Smart Caching:&lt;/strong&gt; I built a DynamoDB caching layer. If you request a VAT number that was recently checked, it returns in milliseconds. No more VIES downtime impacting your checkout flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔔 Real-time Webhooks:&lt;/strong&gt; This is the feature I'm the most proud of. You can subscribe to a VAT number. Every night, my serverless cron job checks the company's status. If they close down or change their address, your server gets a &lt;code&gt;POST&lt;/code&gt; request instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🇫🇷 Deep Enrichment (France):&lt;/strong&gt; For French companies, the API automatically enriches the VIES data with financial data (revenue, net income) and executives' names using local Open Data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🏗 The Tech Stack (100% Serverless)
&lt;/h3&gt;

&lt;p&gt;I wanted this to be infinitely scalable and cost-effective, so I went all-in on AWS Serverless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway &amp;amp; AWS Lambda&lt;/strong&gt; (Node.js) for the endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt; for the lightning-fast caching and storing webhook subscriptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB Streams &amp;amp; EventBridge&lt;/strong&gt; to detect changes in the data and automatically trigger the webhook dispatcher.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💻 Developer Experience First
&lt;/h3&gt;

&lt;p&gt;I know how annoying it is to integrate a new API. So alongside the launch, I published two official, zero-dependency wrappers with &lt;strong&gt;built-in auto-retry mechanisms&lt;/strong&gt; (because network glitches happen).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Node.js (npm):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;vatflow

&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;VatFlowClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vatflow&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;client&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;VatFlowClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_RAPIDAPI_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Validate a VAT number in one line&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FR14652014051&lt;/span&gt;&lt;span class="dl"&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;For PHP (Composer):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require quicreatdev/vatflow-php

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  🎁 Try it out!
&lt;/h3&gt;

&lt;p&gt;I've published the API on RapidAPI with a &lt;strong&gt;Free Tier&lt;/strong&gt; so you can test it without putting down a credit card.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://rapidapi.com/BaptistePignol/api/vatflow-eu-vat-company-monitor" rel="noopener noreferrer"&gt;Check out VatFlow on RapidAPI here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I would absolutely love to hear your feedback on the architecture, the DX, or the RapidAPI integration. Have you ever struggled with the VIES API before? Let me know in the comments! 👇&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>serverless</category>
      <category>api</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How I turned Google Sheets into a Blazing-Fast REST API using AWS Serverless</title>
      <dc:creator>Quicreatdev</dc:creator>
      <pubDate>Sun, 22 Feb 2026 16:14:21 +0000</pubDate>
      <link>https://forem.com/quicreatdev/how-i-turned-google-sheets-into-a-blazing-fast-rest-api-using-aws-serverless-3g5o</link>
      <guid>https://forem.com/quicreatdev/how-i-turned-google-sheets-into-a-blazing-fast-rest-api-using-aws-serverless-3g5o</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
We’ve all been there: you need a quick backend for a prototype, an internal tool, or a NoCode project. Setting up PostgreSQL or MongoDB feels like overkill, so you think, &lt;em&gt;"I'll just use Google Sheets as a database!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It sounds great until you hit the reality of the Google Sheets API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It’s slow&lt;/strong&gt; (often 1-2 seconds per request).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limits are strict&lt;/strong&gt; (around 60 requests per user per minute).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No native query language&lt;/strong&gt; (filtering data is a pain).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted the simplicity of Google Sheets but the performance and security of a real backend. So, I built &lt;strong&gt;SheetToJSON&lt;/strong&gt;, a serverless wrapper that solves these exact issues.&lt;/p&gt;

&lt;p&gt;Here is a deep dive into the architecture and how I built it using AWS.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;The Architecture Stack&lt;/strong&gt;&lt;br&gt;
To keep costs low and performance high, I went 100% Serverless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Routing:&lt;/strong&gt; AWS HTTP API Gateway&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute:&lt;/strong&gt; AWS Lambda (Node.js ARM64)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching:&lt;/strong&gt; Amazon S3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security &amp;amp; Auth:&lt;/strong&gt; Amazon DynamoDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monetization &amp;amp; Gateway:&lt;/strong&gt; RapidAPI&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;strong&gt;Challenge 1: Beating the Rate Limits and Latency&lt;/strong&gt;&lt;br&gt;
The biggest bottleneck of the Google Sheets API is the response time. To fix this, I implemented an aggressive caching strategy using &lt;strong&gt;Amazon S3&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a user makes a &lt;code&gt;GET&lt;/code&gt; request (e.g., fetching a list of products), the Lambda function first checks an S3 bucket for a cached &lt;code&gt;.json&lt;/code&gt; version of that sheet.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache Hit:&lt;/strong&gt; Data is returned in milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Miss:&lt;/strong&gt; Lambda fetches data from Google Sheets, saves the raw JSON to S3 (with a 5-minute TTL), and returns the response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But what about stale data? I implemented a &lt;strong&gt;Write-Through Cache&lt;/strong&gt;. Whenever a user makes a &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt; request through my API, the Lambda function updates Google Sheets &lt;em&gt;and&lt;/em&gt; immediately rewrites the local S3 cache.&lt;br&gt;
Result: Lightning-fast &lt;code&gt;GET&lt;/code&gt; requests without sacrificing data freshness.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Challenge 2: Smart Querying (No Database Engine)&lt;/strong&gt;&lt;br&gt;
Google Sheets doesn't have a SQL engine. If a user wants to find all products where the price is greater than $50, they usually have to download the whole sheet and filter it on the client side.&lt;/p&gt;

&lt;p&gt;I built a custom query engine directly in the Lambda function. When reading from the S3 cache, the API intercepts query parameters and processes them in-memory:&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="c1"&gt;// Example: GET /v1/SPREADSHEET_ID/Products?price=gt:50&amp;amp;sort=price:desc&lt;/span&gt;

&lt;span class="c1"&gt;// The API engine automatically parses "gt:" (greater than), "lt:" (less than), &lt;/span&gt;
&lt;span class="c1"&gt;// and "lk:" (like/contains) before returning the paginated payload.&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This turns a dumb spreadsheet into a queryable NoSQL database.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Challenge 3: Multi-Tenant Security (Preventing IDOR)&lt;/strong&gt;&lt;br&gt;
If you are building a SaaS, security is your #1 priority. Because my API uses a single Google Service Account to read sheets, what prevents User A from guessing User B’s Spreadsheet ID and reading their data?&lt;/p&gt;

&lt;p&gt;To prevent this Insecure Direct Object Reference (IDOR), I added a mandatory &lt;code&gt;/register&lt;/code&gt; step backed by &lt;strong&gt;DynamoDB&lt;/strong&gt;.&lt;br&gt;
Before using the API, a user must register their sheet. The API links their &lt;code&gt;X-RapidAPI-User&lt;/code&gt; ID to the &lt;code&gt;spreadsheetId&lt;/code&gt; in DynamoDB.&lt;/p&gt;

&lt;p&gt;Now, every CRUD request does a lightning-fast (&lt;code&gt;~15ms&lt;/code&gt;) lookup in DynamoDB. If the user ID making the request doesn't match the owner ID of the sheet, the API throws a &lt;code&gt;403 Forbidden&lt;/code&gt;. Zero data leakage.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;The Result: Developer Experience First&lt;/strong&gt;&lt;br&gt;
I wanted the integration to be as seamless as possible, so I published zero-dependency, official SDKs for both &lt;strong&gt;Node.js&lt;/strong&gt; and &lt;strong&gt;PHP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is what it looks like in Node (fully typed with TypeScript!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SheetToJSON&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sheettojson-api&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;db&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;SheetToJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_RAPIDAPI_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SPREADSHEET_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;limit&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="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gt:50&lt;/span&gt;&lt;span class="dl"&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Try it out!&lt;/strong&gt;&lt;br&gt;
Building this was an amazing journey into AWS Serverless architecture. If you are building a NoCode tool, a quick prototype, or need an instant backend, I'd love for you to try it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;&lt;a href="https://rapidapi.com/BaptistePignol/api/sheettojson-api" rel="noopener noreferrer"&gt;Check out the API on RapidAPI&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/sheettojson-api" rel="noopener noreferrer"&gt;Node.js SDK on NPM&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🐘 &lt;strong&gt;&lt;a href="https://packagist.org/packages/sheettojson-api/client" rel="noopener noreferrer"&gt;PHP SDK on Packagist&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm currently looking for feedback on the caching system and the query engine. Let me know what you think in the comments! 👇&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>aws</category>
      <category>serverless</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stop Running Puppeteer on Your Main Server: The Serverless Approach to Screenshots</title>
      <dc:creator>Quicreatdev</dc:creator>
      <pubDate>Mon, 16 Feb 2026 12:39:40 +0000</pubDate>
      <link>https://forem.com/quicreatdev/stop-running-puppeteer-on-your-main-server-the-serverless-approach-to-screenshots-37f7</link>
      <guid>https://forem.com/quicreatdev/stop-running-puppeteer-on-your-main-server-the-serverless-approach-to-screenshots-37f7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;We've all been there. You're building a side project—maybe a link preview generator, an SEO tool, or a dashboard—and you think: &lt;em&gt;"I just need to take a quick screenshot of this URL."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So you &lt;code&gt;npm install puppeteer&lt;/code&gt;, write 10 lines of code, and it works locally. Great!&lt;/p&gt;

&lt;p&gt;Then you deploy it to production (Docker, Ubuntu, or Heroku), and hell breaks loose.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The fonts are broken (rectangles instead of text).&lt;/li&gt;
&lt;li&gt;The memory usage spikes to 2GB and crashes your server.&lt;/li&gt;
&lt;li&gt;The target website shows a giant "Accept Cookies" banner covering the content.&lt;/li&gt;
&lt;li&gt;Half the images are missing because of lazy-loading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I spent the last month fighting these battles while building a screenshot microservice. Here is what I learned about doing it the hard way, and why I eventually turned it into a dedicated API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trap: "It works on my machine"
&lt;/h2&gt;

&lt;p&gt;Basic Puppeteer is deceptive. Here is the code everyone starts with:&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;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;puppeteer&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;async &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;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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 works fine for &lt;code&gt;example.com&lt;/code&gt;. But try running this against a modern Single Page Application (SPA) or a news site, and you will hit three major walls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wall #1: The Lazy Loading Problem
&lt;/h3&gt;

&lt;p&gt;Modern web performance relies on lazy loading. Images only load when they enter the viewport. If you take a screenshot immediately after &lt;code&gt;page.goto&lt;/code&gt;, you get a page full of placeholders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; You need to simulate a user scrolling down, or wait for network activity to settle.&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="c1"&gt;// Waiting for networkidle0 is reliable but SLOW (can take 10s+)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle0&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;
  
  
  Wall #2: The "Cookie Banner" Apocalypse
&lt;/h3&gt;

&lt;p&gt;In 2026, the web is 50% content and 50% GDPR popups. A screenshot tool that captures the cookie banner is useless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; You have to inject CSS or JS to nuke these elements before the shutter clicks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStyleTag&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#onetrust-banner-sdk, .cookie-popup { display: none !important; }&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;But maintaining a list of selectors for every site on the internet? Impossible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wall #3: Server Costs &amp;amp; Zombie Processes
&lt;/h3&gt;

&lt;p&gt;Chromium is heavy. Running it in a standard container requires significant RAM. If your script crashes before &lt;code&gt;browser.close()&lt;/code&gt; is called, you are left with "zombie" Chrome processes eating up your CPU until the server dies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Going Serverless (AWS Lambda)
&lt;/h2&gt;

&lt;p&gt;To solve the crashing and scaling issues, I moved the architecture to &lt;strong&gt;AWS Lambda&lt;/strong&gt;. This ensures that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each screenshot gets a fresh, isolated environment.&lt;/li&gt;
&lt;li&gt;If it crashes, it doesn't take down my main server.&lt;/li&gt;
&lt;li&gt;I only pay when a screenshot is taken.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, getting Puppeteer on Lambda is tricky (binary sizes, font packages). I used &lt;code&gt;puppeteer-core&lt;/code&gt; and &lt;code&gt;@sparticuz/chromium&lt;/code&gt; to keep the package size under the 50MB limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing FlashCapture
&lt;/h2&gt;

&lt;p&gt;After refining this architecture to handle ad-blocking, dark mode, and full-page stitching automatically, I realized this was too valuable to keep as a messy internal script.&lt;/p&gt;

&lt;p&gt;So, I wrapped it into a clean, public API called &lt;strong&gt;FlashCapture&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It handles all the edge cases I mentioned above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Smart Ad-Blocker:&lt;/strong&gt; Automatically hides banners and trackers.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Async Processing:&lt;/strong&gt; No HTTP timeouts on large pages.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Lazy Loading Support:&lt;/strong&gt; We handle the wait logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Trying it out
&lt;/h3&gt;

&lt;p&gt;If you are tired of maintaining your own Puppeteer instance, you can use the API directly via RapidAPI. There is a &lt;strong&gt;free tier&lt;/strong&gt; for developers.&lt;/p&gt;

&lt;p&gt;Here is how simple it is compared to the 50 lines of Puppeteer code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://flashcapture.p.rapidapi.com/capture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-RapidAPI-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-RapidAPI-Host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flashcapture.p.rapidapi.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.reddit.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fullPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;darkMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Automagically renders in dark mode&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Job ID:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Then just poll the /status endpoint to get your image!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you are building a production app, think twice before running a headless browser on your primary web server. It's a resource hog that introduces security risks and stability issues.&lt;/p&gt;

&lt;p&gt;Whether you build your own microservice on AWS Lambda (like I did initially) or use a managed API like &lt;strong&gt;FlashCapture&lt;/strong&gt;, decoupling this heavy task is the best architectural decision you can make.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://rapidapi.com/BaptistePignol/api/flashcapture-screenshot-api" rel="noopener noreferrer"&gt;&lt;strong&gt;Check out FlashCapture on RapidAPI here&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

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