<?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: Shawn P</title>
    <description>The latest articles on DEV Community by Shawn P (@mankyndp).</description>
    <link>https://dev.to/mankyndp</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F586518%2Fde49ac12-c877-4036-a415-9fab32d1d18e.png</url>
      <title>DEV Community: Shawn P</title>
      <link>https://dev.to/mankyndp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mankyndp"/>
    <language>en</language>
    <item>
      <title>Building a Zero-Dependency Validation API on Cloudflare Workers</title>
      <dc:creator>Shawn P</dc:creator>
      <pubDate>Mon, 07 Sep 2026 06:48:39 +0000</pubDate>
      <link>https://dev.to/mankyndp/building-a-zero-dependency-validation-api-on-cloudflare-workers-33e3</link>
      <guid>https://dev.to/mankyndp/building-a-zero-dependency-validation-api-on-cloudflare-workers-33e3</guid>
      <description>&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;I wanted a small side project that could actually run itself once shipped — no cron jobs to babysit, no upstream API to go down at 3am and take my uptime with it. That constraint led somewhere specific: an API that validates common business data formats — phone numbers, IBAN, VAT/tax IDs, BIC/SWIFT codes, credit card numbers, postal codes — using nothing but offline checksum and format rules.&lt;/p&gt;

&lt;p&gt;No third-party lookups. No API keys to rotate for an upstream provider. No rate limits inherited from someone else's infrastructure. If it's slow or wrong, it's my bug, not a dependency's outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://hono.dev" rel="noopener noreferrer"&gt;Hono&lt;/a&gt; on Cloudflare Workers — TypeScript, no cold starts, runs on the free tier comfortably up to 100k requests/day&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/catamphetamine/libphonenumber-js" rel="noopener noreferrer"&gt;libphonenumber-js&lt;/a&gt;, &lt;a href="https://github.com/Simplify/ibantools" rel="noopener noreferrer"&gt;ibantools&lt;/a&gt;, &lt;a href="https://github.com/se-panfilov/jsvat" rel="noopener noreferrer"&gt;jsvat&lt;/a&gt;, &lt;a href="https://github.com/braintree/card-validator" rel="noopener noreferrer"&gt;card-validator&lt;/a&gt; — all well-maintained, all pure computation, zero network calls&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vitest.dev" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt; for tests, run against real fixtures (not made-up test data — every "valid" example in my test suite is a real IBAN/VAT/card number pulled from each library's own published examples, verified against the actual library output before I trusted it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is about 300 lines of TypeScript across the router and six validator modules. Small enough to actually reason about, which mattered more to me than feature breadth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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="s2"&gt;/v1/iban/validate&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;c&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;body&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;c&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;json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;iban&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&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;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&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;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;iban&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;missing required field: iban&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;400&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validateIban&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="nx"&gt;iban&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;
  
  
  The part that actually surprised me
&lt;/h2&gt;

&lt;p&gt;I expected the code to be the hard part. It wasn't. Deploying and listing it on RapidAPI was.&lt;/p&gt;

&lt;p&gt;Two things stood out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CORS mattered even though I "shouldn't" need it.&lt;/strong&gt; Real production traffic through RapidAPI's gateway is server-to-server — CORS is a browser-enforced concept, so I assumed it was irrelevant. But RapidAPI's own in-dashboard request tester runs as a real browser fetch, and without an &lt;code&gt;OPTIONS&lt;/code&gt; handler my backend was completely unreachable from their testing console — failing with a generic "blocked by origin server" error that told me nothing useful. Turned out to be a five-line fix with &lt;code&gt;hono/cors&lt;/code&gt;, but it cost me an hour of assuming the bug was in my endpoint config instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The provider docs describe an older UI.&lt;/strong&gt; RapidAPI's own documentation references a "Base URL" field that doesn't exist anymore in their current Studio — it's now a custom environment variable buried under Requests → Environments → Gateway. Their proxy auth secret (&lt;code&gt;X-RapidAPI-Proxy-Secret&lt;/code&gt;) is generated by them, not something you configure — you copy their value into your backend, not the other way around, which is the opposite of how most API-key setups work. None of this is documented anywhere I could find; I only figured it out by testing requests and reading actual error responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monetization, briefly
&lt;/h2&gt;

&lt;p&gt;Listed with four tiers — free (500 req/mo), then $10/$30/$75 monthly tiers scaling request quota. RapidAPI takes a 25% cut and only pays out via PayPal, monthly, with roughly a two-month lag on when revenue actually lands in your account. Worth knowing going in — this is not a same-week payout model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Free tier, no credit card to start: &lt;a href="https://rapidapi.com/mankynd/api/business-data-validator" rel="noopener noreferrer"&gt;Business Data Validator on RapidAPI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Genuinely curious what other validators would be useful here — thinking about adding tax-ID formats beyond EU VAT next. Feedback welcome.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>cloudflareworkers</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
