<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Faiz Ahmed Farooqui]]></title><description><![CDATA[Tech Lead (Backend) from Bengaluru, Karnataka, India]]></description><link>https://blog.faizahmed.in</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1639984646889/f69nr3aGZ.png</url><title>Faiz Ahmed Farooqui</title><link>https://blog.faizahmed.in</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 00:01:37 GMT</lastBuildDate><atom:link href="https://blog.faizahmed.in/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Stop Putting Secrets in process.env: Encrypt Env Vars with AWS KMS]]></title><description><![CDATA[This post starts with the production problem we hit in late 2025, the critical security vulnerability in React Server Components and Next.js (CVE-2025-66478). Then it explains the library I built in r]]></description><link>https://blog.faizahmed.in/secret-keystore</link><guid isPermaLink="true">https://blog.faizahmed.in/secret-keystore</guid><category><![CDATA[Security]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Attestation]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[nestjs]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[backend]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Sat, 21 Feb 2026 19:30:57 GMT</pubDate><enclosure url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/5fe08e581485f22e24b3799d/de46789b-f4d1-41bd-9ded-ef502269cdaa.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post starts with the production problem we hit in late 2025, the <a href="https://nextjs.org/blog/CVE-2025-66478">critical security vulnerability in React Server Components and Next.js</a> (CVE-2025-66478). Then it explains the library I built in response: <strong>@faizahmedfarooqui/secret-keystore</strong>: encrypt <code>.env</code>, JSON, and YAML with AWS KMS, reduce blast radius, and make secret access explicit and scoped. Includes threat model, what this actually improves (and what it doesn’t), plus ready-to-run Next.js and NestJS examples.</p>
<h2>The production problem: CVE-2025-66478</h2>
<p>In <strong>November 2025</strong>, we patched a <strong>CVSS 10.0 RCE</strong> in Next.js. Then came the worst part: rotating every secret in the system, because we couldn’t prove which ones had been exposed.</p>
<p>The vulnerability was in the <a href="https://nextjs.org/blog/CVE-2025-66478">React Server Components (RSC) protocol and Next.js App Router</a>: <a href="https://github.com/vercel/next.js/security/advisories/GHSA-9qr9-h5gf-34mp">CVE-2025-66478</a> allowed <strong>remote code execution</strong> when processing attacker-controlled requests in unpatched Next.js 15.x, 16.x, and certain 14.x canary builds. Next.js patched quickly and <a href="https://nextjs.org/blog/CVE-2025-66478#rotating-environment-variables">recommended rotating all application secrets</a> once you’d upgraded and redeployed.</p>
<p>That’s the right response, but it forced a hard question: <strong>how many of our secrets were even in scope?</strong></p>
<p>If every credential lives in <code>process.env</code> or in plaintext config files, then in the window when an app was unpatched and exposed, an attacker with RCE could have read all of them.</p>
<p>You’re left rotating <em>everything</em> and hoping nothing was exfiltrated. There’s no way to say “only these keys were ever decrypted in a constrained path.”</p>
<p>During the patch-and-rotate scramble, we realized we couldn’t even confidently answer <em>which</em> secrets had been in memory. Everything was in <code>process.env</code>.</p>
<p>That was the wake-up call. That incident is exactly what pushed me to stop treating env vars as “good enough” and to build something that keeps secrets out of the default blast radius.</p>
<h2>What I built: secret-keystore</h2>
<p><strong>secret-keystore</strong> is a Node.js library that:</p>
<ul>
<li><p><strong>Encrypts secrets at rest</strong>: Values in <code>.env</code>, JSON, or YAML are stored as <code>ENC[...]</code> ciphertext using <strong>AWS KMS</strong>. Without the key and IAM permissions, the config file is useless.</p>
</li>
<li><p><strong>Does not auto-load into global runtime state</strong>: At runtime, plaintext lives only inside a small <strong>in-memory keystore</strong> you access via <code>keyStore.get('KEY')</code>. <code>process.env</code> still holds the encrypted string. Only code paths that explicitly request a key trigger decryption; you can limit which keys are ever decrypted.</p>
</li>
<li><p><strong>Makes secret access explicit and scoped</strong>: This does <strong>not</strong> make RCE harmless. An attacker with code execution could still import the keystore, call <code>keyStore.get()</code>, dump memory, or hook into the decryption flow. What it does is <strong>reduce bulk exposure</strong>: no single dump of <code>process.env</code> with every secret, and secret access is explicit and scoped, because you choose which keys are decryptable and which code paths call them. At minimum that’s auditable by code search (e.g. grep for <code>keyStore.get</code>); optionally you can log or trace key access points without logging values.</p>
</li>
</ul>
<p>So for incidents like <a href="https://nextjs.org/blog/CVE-2025-66478">CVE-2025-66478</a>: you still patch and rotate. But you have a clearer story: secrets weren’t sitting in <code>process.env</code> by default; they were decrypted on demand in a controlled layer. That’s the production problem we faced, and that’s why I built this. We’ve since rolled this pattern across multiple services in production.</p>
<p>You also get:</p>
<ul>
<li><p><strong>Server-side only</strong>: Designed for Node backends, API routes, Server Components, and Lambdas. Not for browser or <code>NEXT_PUBLIC_*</code> (the docs explain why).</p>
</li>
<li><p><strong>Optional hardening</strong>: TTL, auto-refresh, in-memory encryption of the keystore, and optional Nitro Enclave attestation for high-security environments.</p>
</li>
</ul>
<p><strong>Before vs after</strong> (bulk exposure at a glance):</p>
<pre><code class="language-plaintext">Before:  App startup → process.env (all secrets decrypted, loaded into global state)
         One RCE or dump = every secret exposed.

After:   process.env holds ENC[...] only.
         App → keyStore.get("KEY") → KMS decrypt on demand → plaintext in keystore only.
         No bulk dump of process.env; access is per-key and explicit.
</code></pre>
<h2>What this actually improves</h2>
<p>Spelled out:</p>
<ul>
<li><p><strong>No bulk secret dump via</strong> <code>process.env</code>: An attacker or bug that reads <code>process.env</code> only sees ciphertext (and non-secret config). Plaintext is only in the keystore, and only for keys your code has requested.</p>
</li>
<li><p><strong>Reduced accidental logging</strong>: Logging <code>process.env</code> or a generic config dump doesn’t leak decrypted secrets.</p>
</li>
<li><p><strong>Reduced accidental client bundling</strong>: Anything that accidentally exposes env values to the client only ever sees <code>ENC[...]</code> ciphertext, not plaintext.</p>
</li>
<li><p><strong>Easier key rotation</strong>: Re-encrypt with a new KMS key or new values and redeploy; no need to hunt for every place that might have cached <code>process.env</code>.</p>
</li>
<li><p><strong>Explicit secret access boundaries</strong>: You see exactly which modules call <code>keyStore.get('KEY')</code> (easy to audit in code; optionally trace key access without values).</p>
</li>
</ul>
<h2>Threat model</h2>
<p><strong>This protects against:</strong></p>
<ul>
<li><p>Accidental exposure (e.g. logging, error reports, config dumps).</p>
</li>
<li><p>Source control leaks (committed config holds ciphertext; without KMS access it’s useless).</p>
</li>
<li><p>Some RCE blast radius (no single <code>process.env</code> dump with all secrets; access is per-key and explicit).</p>
</li>
</ul>
<p><strong>This does not protect against:</strong></p>
<ul>
<li><p>Full system compromise (attacker can still use your IAM role, call KMS, or read memory).</p>
</li>
<li><p>IAM role or credential compromise (whoever can decrypt with your key can get plaintext).</p>
</li>
<li><p>Memory scraping or post-decryption attacks (once a secret is in memory, it can be read by something with the same process or a memory dump).</p>
</li>
</ul>
<p>Being explicit about this keeps the post credible and security-aware.</p>
<h2>Costs &amp; tradeoffs</h2>
<ul>
<li><p><strong>KMS decrypt adds latency</strong>: Especially on Lambda cold start; use TTL/caching so you’re not re-decrypting on every request (see <a href="http://SECURITY.md">SECURITY.md</a> and the README).</p>
</li>
<li><p><strong>KMS has per-request cost</strong>: Cache decrypted values with TTL; avoid hitting KMS on every <code>keyStore.get()</code>.</p>
</li>
<li><p><strong>IAM must be scoped</strong>: Least privilege: grant only the decrypt permission needed for the key(s) you use (see minimal policy below).</p>
</li>
<li><p><strong>Secrets exist in memory after access</strong>: Covered in the threat model; no protection against memory scraping once decrypted.</p>
</li>
</ul>
<h3>Minimal IAM policy (least privilege)</h3>
<p>Restrict to a single KMS key and only decrypt. Full policy document (copy-paste ready):</p>
<pre><code class="language-json">{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowDecryptForSecretKeystore",
      "Effect": "Allow",
      "Action": ["kms:Decrypt"],
      "Resource": "arn:aws:kms:us-east-1:YOUR_ACCOUNT:key/YOUR_KEY_ID"
    }
  ]
}
</code></pre>
<p>Add <code>kms:DescribeKey</code> only if your flow needs it. Tighten further with conditions (e.g. encryption context) if your deployment supports it; at minimum scope by key ARN.</p>
<p><strong>Why not just use AWS Secrets Manager or SSM Parameter Store?</strong> If you’re already standardized on Secrets Manager or SSM, use that. secret-keystore is for teams that want encrypted config files while keeping existing env/config workflows. Same KMS, different consumption model: file-based, encrypted-at-rest <code>.env</code> (or JSON/YAML) in git, minimal infra changes.</p>
<p><strong>When this pattern fits best</strong></p>
<ul>
<li><p>Apps with file-based env/config workflows that still want encrypted-at-rest config.</p>
</li>
<li><p>Teams on AWS who can rely on IAM roles (ECS, EC2, Lambda).</p>
</li>
<li><p>Teams who want to reduce bulk secret exposure without migrating to a managed secret store.</p>
</li>
</ul>
<p>Below is how to use it and where the Next.js and NestJS examples live.</p>
<h2>How to use it (three steps)</h2>
<h3>1. Install and prepare config</h3>
<pre><code class="language-shell">npm install @faizahmedfarooqui/secret-keystore
</code></pre>
<p>Use a normal <code>.env</code> (or JSON/YAML) with your secrets in plaintext at first. You’ll encrypt them in the next step.</p>
<pre><code class="language-shell">KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/your-key-id
AWS_REGION=us-east-1

DB_PASSWORD=mysecretpassword
API_KEY=sk-1234567890abcdef
JWT_SECRET=super-secret-jwt-key
</code></pre>
<p><code>KMS_KEY_ID</code> and <code>AWS_REGION</code> stay in plaintext (needed for decryption). They aren’t secrets, but treat them as configuration you still don’t want to leak unnecessarily. Everything else can be encrypted.</p>
<h3>2. Encrypt secrets with the CLI</h3>
<p>Run the CLI once (e.g. locally or in CI) to turn chosen keys into KMS ciphertext. The file is updated in place.</p>
<pre><code class="language-bash">npx @faizahmedfarooqui/secret-keystore encrypt \
  --kms-key-id="arn:aws:kms:us-east-1:123456789012:key/your-key-id" \
  --keys="DB_PASSWORD,API_KEY,JWT_SECRET"
</code></pre>
<p>After this, your file looks like:</p>
<pre><code class="language-env">DB_PASSWORD=ENC[AQICAHh2nZPq...]
API_KEY=ENC[AQICAHh2nZPq...]
JWT_SECRET=ENC[AQICAHh2nZPq...]
</code></pre>
<p>You can commit this file if your repo is private and access is controlled; without the KMS key and IAM permissions, the ciphertext is useless.</p>
<h3>3. Use the keystore at runtime</h3>
<p>In your server code, read the config, create the keystore with the same KMS key, and read secrets via <code>keyStore.get(...)</code>. In production, the encrypted config can come from a file baked into the image, an attached volume, S3, or your CI artifact; anything that can safely store ciphertext.</p>
<pre><code class="language-javascript">const { createSecretKeyStore } = require('@faizahmedfarooqui/secret-keystore');
const fs = require('node:fs');

async function bootstrap() {
  const content = fs.readFileSync('./.env', 'utf-8');
  const kmsKeyId = process.env.KMS_KEY_ID;

  const keyStore = await createSecretKeyStore(
    { type: 'env', content },
    kmsKeyId,
    {
      paths: ['DB_PASSWORD', 'API_KEY', 'JWT_SECRET'],
      aws: { region: process.env.AWS_REGION }
    }
  );

  const dbPassword = keyStore.get('DB_PASSWORD');  // plaintext only here
  const apiKey = keyStore.get('API_KEY');

  // process.env.DB_PASSWORD is still ENC[...]; safe to log
  connectToDatabase({ password: dbPassword });
}

bootstrap();
</code></pre>
<p>By default the library uses the IAM role of the process (e.g. EC2, ECS, Lambda). For local dev you can pass explicit credentials or use <code>--use-credentials</code> with the CLI. Where the encrypted file lives (container image, volume, S3, etc.) is up to your deployment; the library just needs the ciphertext string.</p>
<h2>Two full examples: Next.js and NestJS</h2>
<p>The repo includes two runnable examples so you can see patterns that work and ones that don’t.</p>
<h3>Next.js (App Router)</h3>
<ul>
<li><p><strong>Location:</strong> <code>examples/nextjs</code> in the <a href="https://github.com/faizahmedfarooqui/secret-keystore">GitHub repo</a>.</p>
</li>
<li><p><strong>What it shows:</strong> A shared keystore created once and used in API routes and Server Components. Secrets are never passed to Client Components or <code>NEXT_PUBLIC_*</code>.</p>
</li>
<li><p><strong>Highlights:</strong></p>
<ul>
<li><p>Keystore is created in a small <code>lib/keystore</code> module and reused.</p>
</li>
<li><p>API route <code>app/api/secrets/[key]/route.ts</code> returns a secret by key (for demo only). In real apps, don’t build a “get secret by key” endpoint: use the keystore inside server-side logic only.</p>
</li>
<li><p>A Server Component (no <code>"use client"</code>) can call <code>getSecret(...)</code>; a Client Component cannot and must get data via API or server-rendered props.</p>
</li>
</ul>
</li>
<li><p><strong>Run it:</strong> Copy <code>.env.example</code> to <code>.env.local</code>, set <code>KMS_KEY_ID</code>, run <code>npm run encrypt:keys</code>, then <code>npm run dev</code>. See the example README for details.</p>
</li>
</ul>
<h3>NestJS</h3>
<ul>
<li><p><strong>Location:</strong> <code>examples/nestjs</code> in the <a href="https://github.com/faizahmedfarooqui/secret-keystore">GitHub repo</a>.</p>
</li>
<li><p><strong>What it shows:</strong> A global <code>KeyStoreModule</code> that builds the keystore at startup and injects it into services. Controllers and services use the same keystore instance.</p>
</li>
<li><p><strong>Highlights:</strong></p>
<ul>
<li><p><code>KeystoreModule</code> reads config, calls <code>createSecretKeyStore</code>, and exposes the keystore via a custom provider.</p>
</li>
<li><p>A <code>SecretsController</code> (or any service) injects the keystore and calls <code>.get('KEY')</code> when it needs a secret.</p>
</li>
<li><p>Fits standard NestJS patterns: one module, one place to configure KMS and paths, then inject wherever you need secrets.</p>
</li>
</ul>
</li>
<li><p><strong>Run it:</strong> Same idea: <code>.env</code> from <code>.env.example</code>, set <code>KMS_KEY_ID</code>, run the encrypt script, then <code>npm run start:dev</code>. See the example README for step-by-step commands.</p>
</li>
</ul>
<p>Both examples use the same package and the same three-step flow: install → encrypt with CLI → use <code>createSecretKeyStore</code> and <code>keyStore.get()</code> in server code.</p>
<h2>What to keep in mind</h2>
<ul>
<li><p><strong>Server-side only.</strong> The library relies on AWS KMS and Node.js. It does <strong>not</strong> work in the browser, in Client Components, or for <code>NEXT_PUBLIC_*</code> (those are baked into client JS at build time). The README has a clear “works with / doesn’t work with” section and small code snippets for Next.js.</p>
</li>
<li><p><strong>KMS key required.</strong> Every encrypt/decrypt path needs a KMS key ID (ARN, alias, or key id). The library doesn’t read it from the file; you pass it in (e.g. via env or config).</p>
</li>
<li><p><strong>Formats.</strong> You can encrypt keys in <code>.env</code>, JSON, or YAML. For YAML you can use glob patterns like <code>**.password</code> to target nested keys. Optional dependency <code>js-yaml</code> improves YAML support.</p>
</li>
<li><p><strong>Production tuning.</strong> For Lambda, cold starts add KMS decrypt latency; the README and <a href="http://SECURITY.md">SECURITY.md</a> cover caching, TTL, and rate limits. Use TTL and auto-refresh where appropriate so you’re not re-decrypting on every request.</p>
</li>
</ul>
<h2>Where to go from here</h2>
<ul>
<li><p><strong>Package on npm:</strong> <a href="https://www.npmjs.com/package/@faizahmedfarooqui/secret-keystore">@faizahmedfarooqui/secret-keystore</a></p>
</li>
<li><p><strong>Repo and examples:</strong> <a href="https://github.com/faizahmedfarooqui/secret-keystore">faizahmedfarooqui/secret-keystore</a>: clone it and run <code>examples/nextjs</code> or <code>examples/nestjs</code> for a full walkthrough.</p>
</li>
<li><p><strong>Security and options:</strong> The main README and <a href="http://SECURITY.md">SECURITY.md</a> in the repo cover threat model, IAM, optional in-memory encryption, TTL/caching (including Lambda cold start considerations), and Nitro Enclave attestation.</p>
</li>
</ul>
<p>If you’re building a Next.js or NestJS app on AWS and want env-based secrets that stay encrypted at rest and out of <code>process.env</code> at runtime, <strong>secret-keystore</strong> and the two examples are a good place to start.</p>
]]></content:encoded></item><item><title><![CDATA[Scaling the Boring Stuff: Sending 10 Million Notifications with a Simple Node.js Job Worker]]></title><description><![CDATA[At scale, notifications are not special. They are just one of the most common million job problems you will ever solve.
Email, SMS, WhatsApp, and app notifications all share the same pain points. External providers enforce limits you do not control. ...]]></description><link>https://blog.faizahmed.in/scaling-the-boring-stuff-sending-10-million-notifications-with-a-simple-nodejs-job-worker</link><guid isPermaLink="true">https://blog.faizahmed.in/scaling-the-boring-stuff-sending-10-million-notifications-with-a-simple-nodejs-job-worker</guid><category><![CDATA[Node.js]]></category><category><![CDATA[System Design]]></category><category><![CDATA[scalability]]></category><category><![CDATA[Backend Engineering]]></category><category><![CDATA[distributed systems]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Wed, 04 Feb 2026 13:30:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/7nrsVjvALnA/upload/3c22bc2db7624af12c57c0d9cf946f10.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At scale, notifications are not special. They are just one of the most common million job problems you will ever solve.</p>
<p>Email, SMS, WhatsApp, and app notifications all share the same pain points. External providers enforce limits you do not control. Jobs fail midway. Retries happen. Throughput fluctuates. If your system ignores these realities, it will eventually break in loud and expensive ways.</p>
<p>This post walks through a simple, reusable Node.js worker pattern for processing millions of notification jobs safely. Notifications are the example, but the same design works for data pipelines, webhooks, background processing, and batch workloads.</p>
<p>No frameworks. No theory heavy definitions. Just patterns that survive production.</p>
<h3 id="heading-notifications-are-just-jobs-with-consequences">Notifications are just jobs with consequences</h3>
<p>When someone says “send 10 million notifications”, what they really want is:</p>
<ul>
<li><p>jobs processed eventually</p>
</li>
<li><p>no duplicate sends</p>
</li>
<li><p>controlled throughput</p>
</li>
<li><p>safe retries</p>
</li>
<li><p>predictable failure handling</p>
</li>
</ul>
<p>Email, SMS, and messaging providers just punish mistakes faster than most systems. That is why notifications make such a good example.</p>
<p>The core idea is simple.</p>
<p>Sending a notification is not a function call.<br />It is a job that moves through states.</p>
<h3 id="heading-the-basic-architecture">The basic architecture</h3>
<p>The system has four boring parts:</p>
<ol>
<li><p>API that creates notification jobs</p>
</li>
<li><p>Queue that holds job IDs</p>
</li>
<li><p>Workers that process jobs</p>
</li>
<li><p>A datastore that tracks job state</p>
</li>
</ol>
<p>The API never sends notifications. It only enqueues work.</p>
<p>This separation is what allows the system to scale without lying to users.</p>
<h3 id="heading-a-reusable-nodejs-worker-pattern">A reusable Node.js worker pattern</h3>
<p>Below is a minimal worker pattern that works for email, SMS, WhatsApp, push notifications, or any other background job.</p>
<p>No frameworks. Just Node.js.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// worker.js</span>
<span class="hljs-keyword">const</span> MAX_CONCURRENCY = <span class="hljs-number">10</span>
<span class="hljs-keyword">const</span> RATE_LIMIT_PER_SECOND = <span class="hljs-number">50</span>

<span class="hljs-keyword">let</span> active = <span class="hljs-number">0</span>
<span class="hljs-keyword">let</span> tokens = RATE_LIMIT_PER_SECOND

<span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
  tokens = RATE_LIMIT_PER_SECOND
}, <span class="hljs-number">1000</span>)

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchJob</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// pull one job ID from queue</span>
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadJob</span>(<span class="hljs-params">jobId</span>) </span>{
  <span class="hljs-comment">// load job record from DB</span>
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">markState</span>(<span class="hljs-params">jobId, state, meta = {}</span>) </span>{
  <span class="hljs-comment">// update job state in DB</span>
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processJob</span>(<span class="hljs-params">job</span>) </span>{
  <span class="hljs-comment">// send email, SMS, WhatsApp, push notification, etc</span>
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">workerLoop</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (active &gt;= MAX_CONCURRENCY || tokens &lt;= <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span>
  }

  <span class="hljs-keyword">const</span> jobId = <span class="hljs-keyword">await</span> fetchJob()
  <span class="hljs-keyword">if</span> (!jobId) <span class="hljs-keyword">return</span>

  active++
  tokens--

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> job = <span class="hljs-keyword">await</span> loadJob(jobId)

    <span class="hljs-keyword">if</span> (job.state === <span class="hljs-string">"completed"</span>) {
      <span class="hljs-keyword">return</span>
    }

    <span class="hljs-keyword">await</span> markState(job.id, <span class="hljs-string">"processing"</span>)

    <span class="hljs-keyword">await</span> processJob(job)

    <span class="hljs-keyword">await</span> markState(job.id, <span class="hljs-string">"completed"</span>)
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-keyword">if</span> (err.retryable) {
      <span class="hljs-keyword">await</span> markState(jobId, <span class="hljs-string">"queued"</span>)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">await</span> markState(jobId, <span class="hljs-string">"failed"</span>, { <span class="hljs-attr">reason</span>: err.message })
    }
  } <span class="hljs-keyword">finally</span> {
    active--
  }
}

<span class="hljs-built_in">setInterval</span>(workerLoop, <span class="hljs-number">10</span>)
</code></pre>
<p>This single pattern gives you:</p>
<ul>
<li><p>controlled concurrency</p>
</li>
<li><p>built in rate limiting</p>
</li>
<li><p>safe retries</p>
</li>
<li><p>idempotent execution</p>
</li>
</ul>
<p>Nothing fancy. Very hard to break.</p>
<h3 id="heading-idempotency-is-not-optional">Idempotency is not optional</h3>
<p>Retries will happen. Crashes will happen. Deploys will interrupt jobs.</p>
<p>Without idempotency, retries mean duplicate notifications. Duplicate SMS or WhatsApp messages are not just annoying, they are expensive.</p>
<p>Every job must have a unique key that represents the work, not the attempt.</p>
<p>For notifications, a good key looks like:</p>
<pre><code class="lang-plaintext">notificationType + templateVersion + userId + channel
</code></pre>
<p>Before processing, check if this key already completed.</p>
<p>If yes, skip.<br />If no, proceed.</p>
<p>Exactly once delivery is a myth.<br />Exactly once effects are achievable.</p>
<h3 id="heading-backpressure-keeps-your-system-and-providers-alive">Backpressure keeps your system and providers alive</h3>
<p>Backpressure means slowing down when downstream systems struggle.</p>
<p>In practice:</p>
<ul>
<li><p>reduce concurrency when error rates rise</p>
</li>
<li><p>stop pulling from queue when retries spike</p>
</li>
<li><p>throttle globally when providers respond slowly</p>
</li>
</ul>
<p>If your workers keep running at full speed during throttling, you are not scaling. You are burning reputation.</p>
<p>A rising queue is not failure.<br />Ignoring it is.</p>
<h3 id="heading-job-state-beats-logs-every-time">Job state beats logs every time</h3>
<p>Logs help debug. They do not help operate.</p>
<p>Store explicit states:</p>
<ul>
<li><p>queued</p>
</li>
<li><p>processing</p>
</li>
<li><p>completed</p>
</li>
<li><p>failed</p>
</li>
</ul>
<p>This allows:</p>
<ul>
<li><p>safe restarts</p>
</li>
<li><p>replaying failures</p>
</li>
<li><p>accurate dashboards</p>
</li>
<li><p>real progress tracking</p>
</li>
</ul>
<p>Logs explain the past.<br />State controls the present.</p>
<h3 id="heading-dead-letter-queues-need-exits">Dead letter queues need exits</h3>
<p>Some notification jobs will never succeed:</p>
<ul>
<li><p>invalid phone numbers</p>
</li>
<li><p>blocked email domains</p>
</li>
<li><p>users who opted out</p>
</li>
<li><p>malformed payloads</p>
</li>
</ul>
<p>Move them to a dead letter queue with:</p>
<ul>
<li><p>reason</p>
</li>
<li><p>payload</p>
</li>
<li><p>timestamp</p>
</li>
</ul>
<p>And most importantly: replay support.</p>
<p>A dead letter queue without replay is just data loss with better branding.</p>
<h3 id="heading-real-world-rate-limits-you-will-hit">Real world rate limits you will hit</h3>
<h4 id="heading-aws-sns">AWS SNS</h4>
<ul>
<li><p>Publish rate depends on region and account</p>
</li>
<li><p>Typical defaults are a few hundred messages per second</p>
</li>
<li><p>Fanout helps, but downstream systems still apply limits</p>
</li>
</ul>
<p>SNS is good at distribution, not protection.</p>
<h4 id="heading-aws-sqs">AWS SQS</h4>
<ul>
<li><p>Standard queues scale extremely well</p>
</li>
<li><p>Practically unlimited throughput</p>
</li>
<li><p>Ordering is not guaranteed</p>
</li>
<li><p>Visibility timeouts matter more than rate limits</p>
</li>
</ul>
<p>SQS absorbs load. It does not enforce safety.</p>
<h4 id="heading-aws-ses">AWS SES</h4>
<ul>
<li><p>Strict per second and per day quotas</p>
</li>
<li><p>New accounts start with very low limits</p>
</li>
<li><p>Throttling affects deliverability silently</p>
</li>
</ul>
<p>SES is unforgiving if you spike too fast.</p>
<h3 id="heading-if-aws-is-not-your-option">If AWS is not your option</h3>
<p>Common alternatives and their realities:</p>
<ul>
<li><p>RabbitMQ: great control, you own scaling and backpressure</p>
</li>
<li><p>Kafka: massive throughput, higher operational cost</p>
</li>
<li><p>Postgres queues: surprisingly good for many teams</p>
</li>
<li><p>Redis streams: fast, but memory bound</p>
</li>
</ul>
<p>Notification providers like Twilio, SendGrid, Mailgun, Postmark, WhatsApp APIs all enforce:</p>
<ul>
<li><p>per second limits</p>
</li>
<li><p>per sender reputation</p>
</li>
<li><p>aggressive throttling on spikes</p>
</li>
</ul>
<p>No provider lets you brute force scale.</p>
<h3 id="heading-why-this-pattern-works-everywhere">Why this pattern works everywhere</h3>
<p>This worker pattern works because it accepts reality:</p>
<ul>
<li><p>failures are normal</p>
</li>
<li><p>retries are required</p>
</li>
<li><p>limits exist</p>
</li>
<li><p>speed is negotiated, not demanded</p>
</li>
</ul>
<p>Once you build this, notifications become boring again.</p>
<p>And boring systems are the ones that scale.</p>
<h3 id="heading-closing">Closing</h3>
<p>Scaling is not about sending faster. It is about finishing safely.</p>
<p>Queues buy time.<br />Backpressure buys trust.<br />Idempotency buys sleep.</p>
<p>Do these three well, and processing millions of notifications becomes just another day at work.</p>
]]></content:encoded></item><item><title><![CDATA[Handling BigInt in JavaScript: What Happens When You Don’t]]></title><description><![CDATA[Learn how JavaScript handles BigInt, why the default Number type silently loses precision with large integers, and how to avoid overflow, rounding errors.
JavaScript has two numeric systems:

Number – a 64-bit floating-point type used everywhere by d...]]></description><link>https://blog.faizahmed.in/handling-bigint-in-javascript-what-happens-when-you-dont</link><guid isPermaLink="true">https://blog.faizahmed.in/handling-bigint-in-javascript-what-happens-when-you-dont</guid><category><![CDATA[javascript number limits]]></category><category><![CDATA[js number vs bigint]]></category><category><![CDATA[safe integer javascript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[#javascript-bigint]]></category><category><![CDATA[IEEE 754 ]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Wed, 19 Nov 2025 10:21:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Wpnoqo2plFA/upload/ecb06740aba4a5b128d7f57788131790.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Learn how JavaScript handles BigInt, why the default Number type silently loses precision with large integers, and how to avoid overflow, rounding errors.</p>
<p>JavaScript has two numeric systems:</p>
<ol>
<li><p><strong>Number</strong> – a 64-bit floating-point type used everywhere by default.</p>
</li>
<li><p><strong>BigInt</strong> – an arbitrary-precision integer type introduced to handle values that Numbers simply cannot represent.</p>
</li>
</ol>
<p>Most developers use the <code>Number</code> type without thinking much about it, and that’s where subtle bugs creep in. Once your integers grow beyond a certain size such as during cryptographic operations, blockchain transactions, financial calculations, or system-generated IDs.</p>
<p>JavaScript will quietly corrupt these values unless you switch to BigInt intentionally.</p>
<p>This post breaks down how BigInt works, how Number fails, and what happens when you miss the details.</p>
<h2 id="heading-1-the-hard-limits-of-javascripts-number-type"><strong>1. The Hard Limits of JavaScript’s Number Type</strong></h2>
<p>JavaScript Numbers follow IEEE-754 double-precision rules.</p>
<p>They can only store integers accurately up to:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">Number</span>.MAX_SAFE_INTEGER  <span class="hljs-comment">// 9007199254740991</span>
<span class="hljs-built_in">Number</span>.MIN_SAFE_INTEGER  <span class="hljs-comment">// -9007199254740991</span>
</code></pre>
<p>Anything beyond these values can’t be represented exactly.</p>
<h3 id="heading-example">Example</h3>
<pre><code class="lang-javascript"><span class="hljs-number">9007199254740992</span> === <span class="hljs-number">9007199254740993</span>  
<span class="hljs-comment">// true (precision lost)</span>
</code></pre>
<p>The moment you cross this threshold, JavaScript starts approximating integers using floating-point rounding.</p>
<h3 id="heading-where-this-breaks">Where this breaks</h3>
<ul>
<li><p>Snowflake-style distributed IDs</p>
</li>
<li><p>Crypto and hashing inputs</p>
</li>
<li><p>Database primary keys</p>
</li>
<li><p>Blockchain units (e.g., Wei)</p>
</li>
<li><p>Financial figures in cents</p>
</li>
<li><p>High-resolution timestamps</p>
</li>
</ul>
<p>Precision errors here are dangerous because they’re <strong>silent</strong>. JavaScript won’t warn you.</p>
<h2 id="heading-2-bigint-precise-integers-for-any-size"><strong>2. BigInt: Precise Integers for Any Size</strong></h2>
<p>BigInt was introduced to fix this issue by allowing integers without size limits.</p>
<h3 id="heading-creating-bigints">Creating BigInts</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> a = BigInt(<span class="hljs-string">"123456789012345678901234567890"</span>);
<span class="hljs-keyword">const</span> b = <span class="hljs-number">123456789012345678901234567890n</span>;
</code></pre>
<h3 id="heading-exact-comparisons">Exact comparisons</h3>
<pre><code class="lang-javascript">BigInt(<span class="hljs-string">"9007199254740993"</span>) === BigInt(<span class="hljs-string">"9007199254740994"</span>);
<span class="hljs-comment">// false (correct)</span>
</code></pre>
<p>BigInt behaves like a true integer type. No exponent rounding. No overflow.</p>
<h2 id="heading-3-the-trap-javascript-never-auto-converts-to-bigint"><strong>3. The Trap: JavaScript Never Auto-Converts to BigInt</strong></h2>
<p>A common misconception is that JavaScript will "switch" to BigInt when needed.</p>
<p>It will not.</p>
<p>If you do this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> x = <span class="hljs-number">999999999999999999999999999999</span>;
</code></pre>
<p>JavaScript outputs:</p>
<pre><code class="lang-javascript"><span class="hljs-number">1e+30</span>
</code></pre>
<p>This is <strong>not</strong> a BigInt. It is a lossy floating-point approximation.</p>
<h3 id="heading-real-problem-you-wont-notice-unless-you-check">Real problem: You won’t notice unless you check</h3>
<ul>
<li><p>IDs stop matching</p>
</li>
<li><p>Hash inputs change</p>
</li>
<li><p>Database lookups fail</p>
</li>
<li><p>Comparisons return false negatives</p>
</li>
<li><p>Values get rounded, often catastrophically</p>
</li>
</ul>
<p>These bugs are notoriously hard to trace.</p>
<h2 id="heading-4-what-actually-happens-when-you-use-large-integers-as-numbers"><strong>4. What Actually Happens When You Use Large Integers as Numbers</strong></h2>
<h3 id="heading-case-1-precision-loss"><strong>Case 1: Precision Loss</strong></h3>
<pre><code class="lang-javascript"><span class="hljs-number">9999999999999999</span> === <span class="hljs-number">10000000000000000</span>  
<span class="hljs-comment">// true</span>
</code></pre>
<p>Both collapse to the same float.</p>
<h3 id="heading-case-2-scientific-notation"><strong>Case 2: Scientific Notation</strong></h3>
<p>Large strings parsed incorrectly:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">parseInt</span>(<span class="hljs-string">"8888888888888888888888888888"</span>);
<span class="hljs-comment">// → 8.888888888888889e+30</span>
</code></pre>
<p>You didn’t ask for floats but you got floats.</p>
<h3 id="heading-case-3-overflow-infinity"><strong>Case 3: Overflow → Infinity</strong></h3>
<pre><code class="lang-javascript"><span class="hljs-number">10</span> ** <span class="hljs-number">400</span>;
<span class="hljs-comment">// → Infinity</span>
</code></pre>
<h3 id="heading-case-4-logic-errors-with-no-visible-warning"><strong>Case 4: Logic Errors with No Visible Warning</strong></h3>
<p>Imagine your IDs:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> incoming = <span class="hljs-string">"1623439434039488512"</span>;
<span class="hljs-keyword">const</span> stored = <span class="hljs-string">"1623439434039488512"</span>;

<span class="hljs-built_in">Number</span>(incoming) === <span class="hljs-built_in">Number</span>(stored);  
<span class="hljs-comment">// false (or sometimes true for the wrong reason)</span>
</code></pre>
<p>This breaks authentication, logging, analytics, and distributed systems.</p>
<h2 id="heading-5-how-to-correctly-handle-big-integers"><strong>5. How to Correctly Handle Big Integers</strong></h2>
<h3 id="heading-always-treat-large-integers-as-strings-until-conversion">✔️ Always treat large integers as strings until conversion</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> id = BigInt(req.body.userId);
</code></pre>
<h3 id="heading-use-bigint-literals-for-constants">✔️ Use BigInt literals for constants</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> MAX = <span class="hljs-number">999999999999999999999n</span>;
</code></pre>
<h3 id="heading-never-rely-on-number-for-large-values">✔️ Never rely on Number for large values</h3>
<p>Especially when consuming:</p>
<ul>
<li><p>JSON</p>
</li>
<li><p>API responses</p>
</li>
<li><p>Database IDs</p>
</li>
<li><p>Blockchain RPC data</p>
</li>
</ul>
<h3 id="heading-validate-safe-integer-boundaries">✔️ Validate safe integer boundaries</h3>
<pre><code class="lang-javascript"><span class="hljs-built_in">Number</span>.isSafeInteger(value)
</code></pre>
<h2 id="heading-6-cannot-mix-bigint-and-number-why-javascript-throws"><strong>6. “Cannot mix BigInt and Number”: Why JavaScript Throws</strong></h2>
<p>Once you use BigInt, JS becomes strict:</p>
<pre><code class="lang-javascript"><span class="hljs-number">1n</span> + <span class="hljs-number">1</span>  
<span class="hljs-comment">// TypeError</span>
</code></pre>
<p><strong>Why?</strong> Because combining Number and BigInt can silently corrupt values.</p>
<h3 id="heading-fix">Fix</h3>
<pre><code class="lang-javascript"><span class="hljs-number">1n</span> + <span class="hljs-number">1n</span>;
</code></pre>
<p>Or explicitly convert (carefully):</p>
<pre><code class="lang-javascript">BigInt(<span class="hljs-number">1</span>);  
<span class="hljs-built_in">Number</span>(<span class="hljs-number">1n</span>);  <span class="hljs-comment">// risky for large n</span>
</code></pre>
<h2 id="heading-7-json-and-bigint-a-known-limitation"><strong>7. JSON and BigInt: A Known Limitation</strong></h2>
<p><code>JSON.stringify()</code> does not support BigInt.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-number">1n</span>);
<span class="hljs-comment">// TypeError</span>
</code></pre>
<h3 id="heading-workaround">Workaround</h3>
<pre><code class="lang-javascript"><span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">value</span>: big.toString() })
</code></pre>
<p>Or use libraries like:</p>
<ul>
<li><p><code>json-bigint</code></p>
</li>
<li><p><code>lossless-json</code></p>
</li>
</ul>
<h2 id="heading-8-real-world-failures-caused-by-ignoring-bigint"><strong>8. Real-World Failures Caused by Ignoring BigInt</strong></h2>
<h3 id="heading-1-broken-authentication"><strong>1. Broken authentication</strong></h3>
<p>Snowflake IDs cannot be stored as Numbers.<br />Users are logged in as someone else or fail auth randomly.</p>
<h3 id="heading-2-corrupted-financial-values"><strong>2. Corrupted financial values</strong></h3>
<p>Large cent-level integers lose precision.<br />This leads to rounding deviations that cause balance mismatches.</p>
<h3 id="heading-3-blockchain-values-break"><strong>3. Blockchain values break</strong></h3>
<p>Ethereum Wei values exceed <code>2^53</code> routinely.<br />Contract interactions fail or verify incorrectly.</p>
<h3 id="heading-4-distributed-system-ids-mismatch"><strong>4. Distributed system IDs mismatch</strong></h3>
<p>Comparisons give unpredictable results when Numbers overflow.</p>
<h1 id="heading-conclusion"><strong>Conclusion</strong></h1>
<p>JavaScript’s Number type is convenient, but it is fundamentally limited.</p>
<p>If your application handles anything that exceeds 53 bits or must remain exact, you must consciously use BigInt.</p>
<p>The key takeaways:</p>
<ul>
<li><p>JavaScript will never auto-upcast to BigInt</p>
</li>
<li><p>Large integers silently lose precision</p>
</li>
<li><p>Overflow leads to scientific notation or Infinity</p>
</li>
<li><p>BigInt solves these problems but requires explicit usage</p>
</li>
<li><p>JSON doesn’t support BigInt without custom handling</p>
</li>
</ul>
<p>By understanding these details, you avoid hard-to-debug failures in authentication, cryptography, blockchain, analytics, and financial systems.</p>
]]></content:encoded></item><item><title><![CDATA[Your Node.js AWS SDK v3 App Will Crash in Production Without This maxSockets Fix]]></title><description><![CDATA[Everything was fine until it wasn’t.
Our Node.js app, powered by AWS SDK v3, started freezing during peak traffic. Requests to S3 and DynamoDB hung indefinitely, ECS tasks began restarting, and the logs were a blur of ETIMEDOUT and Socket hang up err...]]></description><link>https://blog.faizahmed.in/your-nodejs-aws-sdk-v3-app-will-crash-in-production-without-this-maxsockets-fix</link><guid isPermaLink="true">https://blog.faizahmed.in/your-nodejs-aws-sdk-v3-app-will-crash-in-production-without-this-maxsockets-fix</guid><category><![CDATA[maxSockets ]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[AWS SDK]]></category><category><![CDATA[production]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Fri, 07 Nov 2025 07:03:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/a_PDPUPuNZ8/upload/ee651a69c3559a8b3fd8cab765bd4518.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Everything was fine until it wasn’t.</p>
<p>Our Node.js app, powered by AWS SDK v3, started freezing during peak traffic. Requests to S3 and DynamoDB hung indefinitely, ECS tasks began restarting, and the logs were a blur of <code>ETIMEDOUT</code> and <code>Socket hang up</code> errors.</p>
<p>CPU looked normal. Memory wasn’t maxed out.</p>
<p>So why were our production servers collapsing?</p>
<p>It turned out the culprit wasn’t AWS, wasn’t our code! It was <strong>a hidden bottleneck deep inside the AWS SDK’s connection layer</strong>: the <code>maxSockets</code> limit.</p>
<h2 id="heading-the-real-root-how-aws-sdk-v3-actually-works">The Real Root: How AWS SDK v3 Actually Works</h2>
<p>Most developers assume the SDK just fires HTTP requests directly to AWS.</p>
<p>But that’s not how SDK v3 operates.</p>
<p>It’s built on top of a modular runtime called <strong>Smithy</strong>, which handles everything between your code and the actual network call.</p>
<p>Let’s trace what happens when you do this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">await</span> s3.send(
  <span class="hljs-keyword">new</span> PutObjectCommand(params)
);
</code></pre>
<p>Here’s the actual flow behind the scenes:</p>
<pre><code class="lang-plaintext">S3Client
   ↓
Smithy Client Runtime
   ↓
Middleware Stack (Serializer → Signer → Retryer → Logger)
   ↓
NodeHttpHandler (from @smithy/node-http-handler)
   ↓
Node.js https.Agent
   ↓
AWS Service Endpoint
</code></pre>
<p>So far, so good. But the issue lies in that <strong>NodeHttpHandler</strong> step.</p>
<p>By default, the handler creates a new <code>https.Agent</code> with these settings:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> https.Agent({
  <span class="hljs-attr">keepAlive</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">maxSockets</span>: <span class="hljs-number">50</span>,
});
</code></pre>
<p>That means for each AWS service endpoint, your app can open <strong>only 50 concurrent sockets</strong>.<br />Any additional requests will <strong>queue up inside the Agent</strong>, waiting for one of those sockets to free up.</p>
<h2 id="heading-why-the-maxsockets-limit-exists">Why the <code>maxSockets</code> Limit Exists</h2>
<p>The AWS SDK team chose this limit intentionally.</p>
<p>The Smithy runtime prioritizes <strong>stability</strong> and <strong>low resource usage</strong>, since many users run the SDK in Lambda or short-lived containers.</p>
<p>For light workloads, that’s perfectly fine.</p>
<p>But for <strong>long-running, high-throughput Node.js apps</strong> like those processing hundreds of parallel S3 uploads or batch DynamoDB reads that 50-socket cap turns into a massive bottleneck.</p>
<h2 id="heading-how-it-breaks-in-production">How It Breaks in Production</h2>
<p>When your concurrency exceeds the socket limit, here’s what happens:</p>
<ol>
<li><p>Only 50 connections to the AWS endpoint can stay open at once.</p>
</li>
<li><p>The rest of the requests get queued inside Node’s <code>https.Agent</code>.</p>
</li>
<li><p>Those queued promises occupy memory and block the event loop.</p>
</li>
<li><p>The event loop stalls, CPU spikes, requests time out, and your containers start thrashing.</p>
</li>
</ol>
<p>At first, you’ll see small slowdowns.</p>
<p>Then, as traffic spikes, latency shoots up and eventually, <strong>your app just stops responding</strong>.</p>
<p>No AWS throttling. No bad code.</p>
<p>Just a hidden cap you didn’t know existed.</p>
<h2 id="heading-diagnosing-the-issue">Diagnosing the Issue</h2>
<p>To verify that you’re running into this, inspect the client’s underlying handler:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { S3Client } <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-sdk/client-s3"</span>;

<span class="hljs-built_in">console</span>.log(
  S3Client.prototype.config?.requestHandler?.metadata?.agent?.maxSockets
);
</code></pre>
<p>If that prints <code>50</code>, you’re officially throttled.</p>
<p>You can also check live socket usage:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(agent.sockets);   <span class="hljs-comment">// Active sockets</span>
<span class="hljs-built_in">console</span>.log(agent.requests);  <span class="hljs-comment">// Queued requests</span>
</code></pre>
<p>If requests are piling up while sockets stay capped, that’s the bottleneck.</p>
<h2 id="heading-the-fix-override-smithys-default-nodehttphandler">The Fix: Override Smithy’s Default <code>NodeHttpHandler</code></h2>
<p>The good news?</p>
<p>You can completely fix this by creating a <strong>custom</strong> <code>https.Agent</code> and passing it into your AWS SDK client configuration.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> https <span class="hljs-keyword">from</span> <span class="hljs-string">"https"</span>;
<span class="hljs-keyword">import</span> { NodeHttpHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">"@smithy/node-http-handler"</span>;
<span class="hljs-keyword">import</span> { S3Client } <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-sdk/client-s3"</span>;

<span class="hljs-keyword">const</span> agent = <span class="hljs-keyword">new</span> https.Agent({
  <span class="hljs-attr">keepAlive</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">maxSockets</span>: <span class="hljs-number">1000</span>, <span class="hljs-comment">// Adjust based on your instance capacity</span>
});

<span class="hljs-keyword">const</span> s3 = <span class="hljs-keyword">new</span> S3Client({
  <span class="hljs-attr">requestHandler</span>: <span class="hljs-keyword">new</span> NodeHttpHandler({
    <span class="hljs-attr">httpsAgent</span>: agent,
  }),
});
</code></pre>
<p>This tells the Smithy runtime to use your custom transport handler, which inherits all the SDK middleware (retries, signing, etc.) but with <strong>your own socket configuration</strong>.</p>
<h2 id="heading-why-this-fix-works">Why This Fix Works</h2>
<p>When you raise <code>maxSockets</code>, you’re effectively telling Node’s connection pool:</p>
<blockquote>
<p>“Don’t serialize my AWS calls, let them happen concurrently.”</p>
</blockquote>
<p>Here’s what changes:</p>
<ul>
<li><p>Requests no longer queue up behind the 50-connection cap.</p>
</li>
<li><p>Event loop latency drops.</p>
</li>
<li><p>CPU utilization becomes stable and predictable.</p>
</li>
<li><p>Your overall throughput scales linearly with hardware resources.</p>
</li>
</ul>
<p>In our production load tests, increasing <code>maxSockets</code> from 50 → 1000 improved S3 throughput by <strong>3.5×</strong> and cut average request latency from ~2.3s to ~0.6s.</p>
<h2 id="heading-a-quick-note-on-reuse-and-environment-variables">A Quick Note on Reuse and Environment Variables</h2>
<p>To fully optimize, ensure connection reuse is enabled globally:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> AWS_NODEJS_CONNECTION_REUSE_ENABLED=<span class="hljs-number">1</span>
</code></pre>
<p>This allows the SDK to reuse existing sockets across invocations (especially useful in Lambda or Fargate).</p>
<p>And remember:</p>
<ul>
<li><p>Always reuse your <code>Agent</code> instance. Don’t create one per request.</p>
</li>
<li><p>Enable <code>keepAlive: true</code> to avoid costly connection re-establishment.</p>
</li>
<li><p>Set realistic <code>maxSockets</code> (500–1000 for EC2/ECS, ~100 for Lambda).</p>
</li>
<li><p>Monitor file descriptors with <code>lsof -i | wc -l</code> to avoid FD exhaustion.</p>
</li>
</ul>
<h2 id="heading-best-practices-for-production-environments">Best Practices for Production Environments</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Setting</strong></td><td><strong>Default</strong></td><td><strong>Recommended</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>keepAlive</code></td><td>true</td><td>✅ true</td></tr>
<tr>
<td><code>maxSockets</code></td><td>50</td><td>🔼 500–1000</td></tr>
<tr>
<td><code>reuse Agent</code></td><td>false</td><td>✅ true</td></tr>
<tr>
<td>Env Var</td><td>none</td><td><code>AWS_NODEJS_CONNECTION_REUSE_ENABLED=1</code></td></tr>
</tbody>
</table>
</div><p>Additional notes:</p>
<ul>
<li><p>For short-lived runtimes, define your agent <strong>outside</strong> the Lambda handler.</p>
</li>
<li><p>For apps using multiple AWS services (S3, SES, SNS), share the same agent.</p>
</li>
<li><p>Tune your socket count gradually — start low, benchmark, then scale up.</p>
</li>
</ul>
<h2 id="heading-why-smithy-makes-this-trickier">Why Smithy Makes This Trickier</h2>
<p>The key takeaway:</p>
<p>You’re not configuring Node directly, instead you’re configuring <strong>Smithy’s transport layer</strong>.</p>
<p>Every AWS service client (S3Client, DynamoDBClient, SNSClient) inherits from the Smithy <code>Client</code> base class, which bundles its own middleware and transport logic.</p>
<p>That’s why simply setting <code>http.globalAgent.maxSockets</code> won’t work, you need to explicitly pass your <code>https.Agent</code> to the <strong>Smithy-powered</strong> <code>NodeHttpHandler</code>.</p>
<p>Once you understand that architecture, everything clicks.</p>
<p>You’re not fighting AWS, you’re just giving Smithy permission to use your hardware fully.</p>
<h2 id="heading-the-bottom-line">The Bottom Line</h2>
<p>If your Node.js app talks to AWS services at scale, this fix isn’t optional - it’s production-critical.</p>
<p>By default, the AWS SDK v3 (through Smithy) silently limits concurrency to 50 connections per endpoint.</p>
<p>That’s enough to kill high-traffic apps.</p>
<p>The fix is simple: Override the handler with your own <code>https.Agent</code>, enable keep-alive, and scale <code>maxSockets</code> based on your workload.</p>
<p>You’ll immediately notice:</p>
<ul>
<li><p>Faster response times</p>
</li>
<li><p>Zero socket starvation</p>
</li>
<li><p>Stable CPU and memory under load</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How AWS Nitro Enclaves Prove You’re Running Secure Code: Remote Attestation Explained]]></title><description><![CDATA[We encrypt data.We store it in S3.We feel secure.
But when that data comes back for decryption - how do you know who’s asking for the key?
That’s where remote attestation walks in like a quiet bouncer checking IDs at the door.

Traditional encryption...]]></description><link>https://blog.faizahmed.in/aws-nitro-enclaves-remote-attestation</link><guid isPermaLink="true">https://blog.faizahmed.in/aws-nitro-enclaves-remote-attestation</guid><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Thu, 09 Oct 2025 12:10:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/MZPwImQUDM0/upload/68cf2a1bbce5aa249e0877daaa3bc305.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>We encrypt data.<br />We store it in S3.<br />We feel secure.</p>
<p>But when that data comes back for decryption - how do you <em>know</em> who’s asking for the key?</p>
<p>That’s where <strong>remote attestation</strong> walks in like a quiet bouncer checking IDs at the door.</p>
</blockquote>
<p>Traditional encryption protects your data at rest and in transit but not while it’s being processed.</p>
<p>In this article, we explore how <strong>AWS Nitro Enclaves</strong> and <strong>Remote Attestation</strong> bring verifiable trust to runtime environments.</p>
<p>You’ll learn how <strong>AWS KMS</strong> validates enclave identity before releasing keys, and how <strong>OpenSSL CMS</strong> can be used to encrypt data for secure decryption inside attested workloads.</p>
<p>A practical, story-driven guide to understanding <strong>confidential computing</strong> the AWS way.</p>
<h2 id="heading-encryption-isnt-enough-anymore">Encryption Isn’t Enough Anymore</h2>
<p>Most systems today proudly claim <em>“we encrypt everything.”</em><br />That’s great but encryption alone doesn’t guarantee security.</p>
<p>There are three common protection layers in cloud systems:</p>
<ol>
<li><p><strong>At rest</strong> — when data sits in a disk or database.</p>
</li>
<li><p><strong>In transit</strong> — when data moves across the network (HTTPS, TLS).</p>
</li>
<li><p><strong>In use</strong> — when data is being processed in memory.</p>
</li>
</ol>
<blockquote>
<p>The third one — “<strong>in use</strong>” is often ignored.</p>
</blockquote>
<p>If an attacker gains access to your EC2 instance or container, encryption at rest or in transit means nothing. They can just dump your memory and see decrypted data.</p>
<p>Read more — <a target="_blank" href="https://docs.aws.amazon.com/prescriptive-guidance/latest/encryption-best-practices/general-encryption-best-practices.html">AWS Encryption Best Practices</a></p>
<h2 id="heading-the-hidden-problem-runtime-trust">The Hidden Problem: Runtime Trust</h2>
<p>The missing piece is <strong>runtime trust</strong>. Can we <em>trust the environment</em> where our code is running?</p>
<p>Even if you use perfect cryptography, once the data reaches an untrusted runtime, all bets are off.<br />Imagine handing your keys to someone who “promises” they’ll only use them responsibly.<br />That’s basically how most cloud runtimes work today.</p>
<p>To solve this, the industry moved toward <a target="_blank" href="https://confidentialcomputing.io/"><strong>Confidential Computing</strong></a>, protecting data <em>even while it’s being processed</em>.</p>
<h2 id="heading-enter-aws-nitro-enclaves">Enter AWS Nitro Enclaves</h2>
<p>AWS’s answer to confidential computing is <strong>Nitro Enclaves</strong>, isolated compute environments inside your EC2 instances.</p>
<p>Here’s what makes them special:</p>
<ul>
<li><p><strong>No network access</strong></p>
</li>
<li><p><strong>No SSH or user login</strong></p>
</li>
<li><p><strong>No persistent storage</strong></p>
</li>
<li><p><strong>Hardware-backed isolation</strong></p>
</li>
</ul>
<p>They communicate only via a special socket interface called <a target="_blank" href="https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave-concepts.html#term-socket"><strong>vsock</strong></a>, a one-way door for your main instance to talk to the enclave.</p>
<p>Think of Nitro Enclaves as <strong>a locked glass box</strong> inside your EC2 instance, you can see what it does, but you can’t touch what’s inside.</p>
<h2 id="heading-remote-attestation-proof-of-identity">Remote Attestation: Proof of Identity</h2>
<p>Isolation alone isn’t enough.<br />What if an attacker replaces your enclave code with something else?<br />That’s where <strong>remote attestation</strong> steps in.</p>
<p>Attestation is a cryptographic proof, a signed statement from AWS hardware that says:</p>
<blockquote>
<p>“This code, with this hash, is running inside this verified enclave.”</p>
</blockquote>
<p>Every Nitro Enclave can generate an <strong>attestation document</strong>, which includes:</p>
<ul>
<li><p><strong>PCRs (Platform Configuration Registers)</strong> — system measurements</p>
</li>
<li><p><strong>Enclave Image Digest</strong> — hash of your code</p>
</li>
<li><p><strong>Nonce</strong> — to prevent replay attacks</p>
</li>
<li><p><strong>AWS Signature</strong> — from the Nitro Hypervisor’s root key</p>
</li>
</ul>
<p>AWS and other services can then verify this signature using the <a target="_blank" href="https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html"><strong>AWS Nitro Attestation Service</strong></a>.</p>
<p>It’s like showing your passport to AWS KMS, except the passport is signed by AWS’s own hardware.</p>
<h2 id="heading-where-aws-kms-comes-in">Where AWS KMS Comes In</h2>
<p><a target="_blank" href="https://docs.aws.amazon.com/kms/latest/developerguide/cryptographic-attestation.html">AWS Key Management Service (KMS)</a> is the vault that holds your encryption keys.<br />Normally, it’ll decrypt data for any client with permission.<br />But with Nitro Enclaves, it gets smarter.</p>
<p>When an enclave requests a decryption:</p>
<ol>
<li><p>It includes its <strong>attestation document</strong> in the request.</p>
</li>
<li><p>KMS verifies that document’s authenticity and integrity.</p>
</li>
<li><p>If everything checks out, <strong>KMS releases the decrypted key</strong>, only to that enclave.</p>
</li>
</ol>
<p>Here’s the typical flow in action:</p>
<pre><code class="lang-bash">aws kms decrypt \
  --ciphertext-blob fileb://secret.enc \
  --encryption-context <span class="hljs-string">"purpose=payments"</span> \
  --grant-tokens $(cat attestation.json | base64)
</code></pre>
<p>If the attestation hash doesn’t match what KMS expects, the operation fails.<br />No trust, no key.</p>
<h2 id="heading-using-openssl-cms-for-secure-data-exchange">Using OpenSSL CMS for Secure Data Exchange</h2>
<p>Before the enclave ever asks for a key, you might want to <strong>encrypt data for it</strong> and that’s where <code>openssl cms</code> shines.</p>
<p>CMS stands for <strong>Cryptographic Message Syntax</strong>, an envelope format that defines how to encrypt, sign, and verify data (think of it as S/MIME’s backbone).</p>
<p>You can encrypt a file for the enclave’s public certificate:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Encrypt data for the enclave</span>
openssl cms -encrypt -<span class="hljs-keyword">in</span> sensitive.json -out sensitive.enc -outform DER enclave.crt
</code></pre>
<p>Then, only the enclave — after passing attestation — can decrypt it using the key released from KMS:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Inside the enclave</span>
openssl cms -decrypt -<span class="hljs-keyword">in</span> sensitive.enc -out output.json -inkey kms-key.pem
</code></pre>
<p>CMS ensures data confidentiality; attestation ensures <strong>execution trust</strong>.</p>
<h2 id="heading-the-zero-trust-equation">The Zero-Trust Equation</h2>
<p>Let’s simplify what’s happening:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Layer</strong></td><td><strong>What It Protects</strong></td><td><strong>Example</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Encryption</strong></td><td>Data</td><td>S3 bucket, database</td></tr>
<tr>
<td><strong>TLS</strong></td><td>Transport</td><td>HTTPS, APIs</td></tr>
<tr>
<td><strong>Attestation</strong></td><td>Runtime</td><td>AWS Nitro Enclaves</td></tr>
</tbody>
</table>
</div><p>Together, they create <strong>Zero-Trust Encryption</strong> data that can <em>only</em> be decrypted in verified, isolated environments.</p>
<p>If you’re curious, this maps beautifully to <a target="_blank" href="https://csrc.nist.gov/pubs/sp/800/207/final">NIST’s Zero Trust Architecture</a> philosophy — <em>“Never trust, always verify.”</em></p>
<h2 id="heading-why-this-actually-matters">Why This Actually Matters</h2>
<p>You might be thinking, <em>“This sounds cool, but who really needs it?”</em><br />Short answer: <strong>anyone dealing with sensitive workloads.</strong></p>
<p>Real-world use cases:</p>
<ul>
<li><p><strong>Fintech</strong>: Decrypt payment data only in attested enclaves.</p>
</li>
<li><p><strong>Healthcare</strong>: Process medical records under HIPAA with runtime integrity.</p>
</li>
<li><p><strong>AI Models</strong>: Run inference securely on private data.</p>
</li>
<li><p><strong>Multi-tenant SaaS</strong>: Guarantee data isolation for every customer.</p>
</li>
</ul>
<p>For compliance nerds:</p>
<ul>
<li><p>PCI DSS → <a target="_blank" href="https://www.pcisecuritystandards.org/">https://www.pcisecuritystandards.org</a></p>
</li>
<li><p>HIPAA → <a target="_blank" href="https://www.hhs.gov/hipaa/index.html">https://www.hhs.gov/hipaa/index.html</a></p>
</li>
<li><p>GDPR → <a target="_blank" href="https://gdpr.eu/">https://gdpr.eu</a></p>
</li>
</ul>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Encryption protects your <strong>data</strong>.<br />Remote attestation protects your <strong>truth</strong>.</p>
<p>AWS Nitro Enclaves + AWS KMS + OpenSSL CMS together give you <strong>verifiable runtime security</strong> something most systems still ignore.</p>
<blockquote>
<p>In a world where “trust” is often assumed, remote attestation makes it <strong>provable</strong>.</p>
</blockquote>
<h3 id="heading-tldr">TL;DR</h3>
<ul>
<li><p><strong>Encryption ≠ Trust.</strong></p>
</li>
<li><p><strong>Remote Attestation = Verified Execution.</strong></p>
</li>
<li><p>Together, they give you <strong>Confidential Computing</strong>, AWS-style.</p>
</li>
</ul>
<h2 id="heading-sequence-diagram">Sequence Diagram</h2>
<p>Here’s a clean <strong>Mermaid diagram</strong> show casing the flow:</p>
<pre><code class="lang-mermaid">sequenceDiagram
    autonumber
    actor Dev as Developer / Service
    participant CMS as OpenSSL CMS
    participant S3 as Storage (S3/DB)
    participant EC2 as EC2 Parent Instance
    participant VS as vsock
    participant ENC as Nitro Enclave
    participant ATS as Nitro Attestation Service
    participant KMS as AWS KMS
    Dev-&gt;&gt;CMS: Encrypt data with enclave's public cert (CMS envelope)
    CMS-&gt;&gt;S3: Store encrypted blob (e.g., sensitive.enc)
    Dev-&gt;&gt;EC2: Launch EC2 with Enclave (nitro-enclaves)
    EC2--&gt;&gt;VS: Send encrypted blob via vsock
    VS--&gt;&gt;ENC: Deliver encrypted blob to enclave
    ENC-&gt;&gt;ATS: Create attestation document (PCRs, image hash, nonce)
    ENC-&gt;&gt;KMS: Request Decrypt + attach attestation doc
    KMS-&gt;&gt;ATS: Verify enclave attestation (hardware root of trust)
    ATS--&gt;&gt;KMS: OK if measurement &amp; policy match
    KMS--&gt;&gt;ENC: Return decrypted data key (not the CMK)
    ENC-&gt;&gt;ENC: Decrypt CMS envelope (process plaintext in-memory)
    Note right of ENC: No network, no SSH, no persistent storage
    ENC--&gt;&gt;EC2: Return only the necessary result via vsock
</code></pre>
<p>The mermaid diagram is actually not showcasing the sequences, please copy+paste the below content in <a target="_blank" href="https://mermaid.live/">mermaid.live</a>’s website to preview the above diagram in detail.</p>
<pre><code class="lang-plaintext">sequenceDiagram
    autonumber
    actor Dev as Developer / Service
    participant CMS as OpenSSL CMS
    participant S3 as Storage (S3/DB)
    participant EC2 as EC2 Parent Instance
    participant VS as vsock
    participant ENC as Nitro Enclave
    participant ATS as Nitro Attestation Service
    participant KMS as AWS KMS
    Dev-&gt;&gt;CMS: Encrypt data with enclave's public cert (CMS envelope)
    CMS-&gt;&gt;S3: Store encrypted blob (e.g., sensitive.enc)
    Dev-&gt;&gt;EC2: Launch EC2 with Enclave (nitro-enclaves)
    EC2--&gt;&gt;VS: Send encrypted blob via vsock
    VS--&gt;&gt;ENC: Deliver encrypted blob to enclave
    ENC-&gt;&gt;ATS: Create attestation document (PCRs, image hash, nonce)
    ENC-&gt;&gt;KMS: Request Decrypt + attach attestation doc
    KMS-&gt;&gt;ATS: Verify enclave attestation (hardware root of trust)
    ATS--&gt;&gt;KMS: OK if measurement &amp; policy match
    KMS--&gt;&gt;ENC: Return decrypted data key (not the CMK)
    ENC-&gt;&gt;ENC: Decrypt CMS envelope (process plaintext in-memory)
    Note right of ENC: No network, no SSH, no persistent storage
    ENC--&gt;&gt;EC2: Return only the necessary result via vsock
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Leading FinTech Teams: Balancing Speed, Security and Compliance]]></title><description><![CDATA[FinTech moves fast, but it operates in one of the most regulated environments in technology. This post explores how engineering leaders can structure teams, set priorities, and shape culture so that innovation doesn’t come at the cost of security or ...]]></description><link>https://blog.faizahmed.in/leading-fintech-teams-balancing-speed-security-and-compliance</link><guid isPermaLink="true">https://blog.faizahmed.in/leading-fintech-teams-balancing-speed-security-and-compliance</guid><category><![CDATA[fintech]]></category><category><![CDATA[payments]]></category><category><![CDATA[Credit Union]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Sun, 14 Sep 2025 08:30:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/EkyuhD7uwSM/upload/46d62ecf073a39b3ea617a64ded4e280.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>FinTech moves fast, but it operates in one of the most regulated environments in technology. This post explores how engineering leaders can structure teams, set priorities, and shape culture so that innovation doesn’t come at the cost of security or compliance.</p>
<h2 id="heading-the-leadership-balancing-act">The Leadership Balancing Act</h2>
<p>FinTech leaders constantly balance:</p>
<ul>
<li><p><strong>Speed:</strong> Delivering features quickly to stay competitive.</p>
</li>
<li><p><strong>Security:</strong> Protecting customer money and data.</p>
</li>
<li><p><strong>Compliance:</strong> Meeting regulatory obligations and passing audits.</p>
</li>
</ul>
<p><strong>Reality check:</strong> Skipping compliance for speed can kill deals. Skipping speed for compliance can kill adoption. Leaders must steer teams through both.</p>
<h2 id="heading-structuring-teams-for-fintech">Structuring Teams for FinTech</h2>
<ul>
<li><p><strong>Cross-Functional Squads:</strong> Blend developers, QA, and compliance specialists.</p>
</li>
<li><p><strong>Security Champions:</strong> Engineers who embed security best practices in every sprint.</p>
</li>
<li><p><strong>Dedicated Compliance Engineers:</strong> Translating regulations into technical requirements.</p>
</li>
<li><p><strong>SRE and DevOps:</strong> Ensuring uptime and reliability in money-critical systems.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Don’t silo compliance. Make it a shared responsibility across product and engineering.</p>
<h2 id="heading-processes-that-enable-not-block">Processes that Enable, Not Block</h2>
<ul>
<li><p><strong>Shift-Left Testing:</strong> Security and compliance checks early in CI/CD pipelines.</p>
</li>
<li><p><strong>Regular Audits and Reviews:</strong> Internal dry runs before regulatory audits.</p>
</li>
<li><p><strong>Incident Response Plans:</strong> Clear roles, responsibilities, and escalation paths.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Process should accelerate confidence, not slow down delivery.</p>
<h2 id="heading-culture-in-fintech-teams">Culture in FinTech Teams</h2>
<ul>
<li><p><strong>Transparency:</strong> Encourage teams to raise risks without fear.</p>
</li>
<li><p><strong>Continuous Learning:</strong> Regulations and threats evolve - teams must adapt.</p>
</li>
<li><p><strong>Shared Ownership:</strong> Security and compliance are everyone’s job, not just one team’s.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Culture is the invisible layer of resilience. It defines how teams behave under pressure.</p>
<h2 id="heading-leadership-takeaways">Leadership Takeaways</h2>
<ul>
<li><p>Leading in FinTech means balancing <strong>speed, security, and compliance</strong> at every stage.</p>
</li>
<li><p>Teams must be structured to embed compliance into daily workflows.</p>
</li>
<li><p>Processes should be lightweight but enforce <strong>audit readiness and security confidence</strong>.</p>
</li>
<li><p>Culture shapes resilience, leaders must foster transparency and shared ownership.</p>
</li>
</ul>
<h2 id="heading-series-wrap-up">Series Wrap-Up</h2>
<p>This concludes the <strong>FinTech 101 for Engineering Leaders</strong> series. We’ve explored:</p>
<ol>
<li><p><a target="_blank" href="https://blog.faizahmed.in/fintech-fundamentals-every-engineering-leader-should-know-2025-edition">Fundamentals of FinTech</a></p>
</li>
<li><p><a target="_blank" href="https://blog.faizahmed.in/core-pillars-of-fintech-payments-lending-wealth-and-beyond">Core Pillars across payments, lending, and wealth</a></p>
</li>
<li><p><a target="_blank" href="https://blog.faizahmed.in/apis-security-and-compliance-the-technical-backbone-of-fintech">APIs, security, and compliance foundations</a></p>
</li>
<li><p><a target="_blank" href="https://blog.faizahmed.in/building-scalable-and-resilient-fintech-systems">Scaling and resilience practices</a></p>
</li>
<li><p><a target="_blank" href="https://blog.faizahmed.in/trends-shaping-the-future-of-fintech">Trends shaping the future</a></p>
</li>
<li><p><a target="_blank" href="https://blog.faizahmed.in/leading-fintech-teams-balancing-speed-security-and-compliance">Leadership practices for FinTech teams</a></p>
</li>
</ol>
<p>Together, these posts form a <strong>practical playbook</strong> for engineering leaders who want to thrive in one of the most complex and fast-moving industries.</p>
]]></content:encoded></item><item><title><![CDATA[Trends Shaping the Future of FinTech]]></title><description><![CDATA[The FinTech landscape is evolving rapidly. This post highlights the key trends that will define the next wave of financial technology. Engineering leaders will learn what’s driving change, where the opportunities are, and what risks to consider when ...]]></description><link>https://blog.faizahmed.in/trends-shaping-the-future-of-fintech</link><guid isPermaLink="true">https://blog.faizahmed.in/trends-shaping-the-future-of-fintech</guid><category><![CDATA[fintech]]></category><category><![CDATA[Credit Union]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Sat, 13 Sep 2025 11:30:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/91AQt9p4Mo8/upload/c624f3185e910b374b8b9b1b3ea865a9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The FinTech landscape is evolving rapidly. This post highlights the key trends that will define the next wave of financial technology. Engineering leaders will learn what’s driving change, where the opportunities are, and what risks to consider when adapting teams and systems for the future.</p>
<h2 id="heading-embedded-finance">Embedded Finance</h2>
<p>Financial services are increasingly offered inside non-financial platforms.</p>
<ul>
<li><p>Retailers offering instant credit at checkout.</p>
</li>
<li><p>Ride-hailing apps providing driver banking services.</p>
</li>
<li><p>Platforms embedding insurance and lending directly into workflows.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> APIs make this possible, but leaders must ensure <strong>governance and compliance</strong> scale with integration.</p>
<h2 id="heading-ai-and-machine-learning">AI and Machine Learning</h2>
<p>AI is powering smarter, faster financial decisions.</p>
<ul>
<li><p><strong>Fraud Detection:</strong> Real-time anomaly detection.</p>
</li>
<li><p><strong>Personalization:</strong> Tailored financial products and credit offers.</p>
</li>
<li><p><strong>Automation:</strong> Reducing manual compliance checks and underwriting.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> AI models are only as good as the data. Leaders must ensure <strong>quality, fairness, and auditability</strong>.</p>
<h2 id="heading-blockchain-and-digital-assets">Blockchain and Digital Assets</h2>
<ul>
<li><p><strong>Stablecoins:</strong> Faster, cheaper cross-border transactions.</p>
</li>
<li><p><strong>Custody Solutions:</strong> Secure storage for digital assets.</p>
</li>
<li><p><strong>Tokenization:</strong> Turning real-world assets into tradable tokens.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Blockchain brings opportunities but also volatility. Adoption must be measured against regulatory readiness.</p>
<h2 id="heading-central-bank-digital-currencies-cbdcs">Central Bank Digital Currencies (CBDCs)</h2>
<p>Governments are exploring their own digital currencies.</p>
<ul>
<li><p>Potential to streamline payments and settlements.</p>
</li>
<li><p>Raises questions around privacy, security, and integration.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Leaders must prepare for regulatory-driven change that can reshape payment infrastructure.</p>
<h2 id="heading-the-regulatory-wave">The Regulatory Wave</h2>
<p>As innovation grows, so does oversight. Expect:</p>
<ul>
<li><p>Stricter AML/KYC rules.</p>
</li>
<li><p>More focus on consumer protection.</p>
</li>
<li><p>Closer scrutiny of digital assets and embedded finance.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Anticipating regulation early allows teams to adapt faster and avoid costly rework.</p>
<h2 id="heading-leadership-takeaways">Leadership Takeaways</h2>
<ul>
<li><p>Embedded finance and APIs will blur lines between industries.</p>
</li>
<li><p>AI will improve fraud detection and personalization, but requires governance.</p>
</li>
<li><p>Blockchain and CBDCs present opportunity, but with high regulatory risk.</p>
</li>
<li><p>The regulatory wave is inevitable - prepare ahead of time.</p>
</li>
</ul>
<h2 id="heading-coming-next">Coming Next</h2>
<p>In the final post of this series, we’ll cover <strong>Leading FinTech Teams: Balancing Speed, Security, and Compliance</strong>, with practical advice for structuring teams and cultures in regulated environments.</p>
]]></content:encoded></item><item><title><![CDATA[Building Scalable and Resilient FinTech Systems]]></title><description><![CDATA[FinTech systems are mission-critical: a single outage can mean lost money, lost trust, and even regulatory consequences. This post explores how to design platforms that scale with demand, recover gracefully from failure, and maintain compliance while...]]></description><link>https://blog.faizahmed.in/building-scalable-and-resilient-fintech-systems</link><guid isPermaLink="true">https://blog.faizahmed.in/building-scalable-and-resilient-fintech-systems</guid><category><![CDATA[fintech]]></category><category><![CDATA[payments]]></category><category><![CDATA[Credit Union]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Sat, 13 Sep 2025 04:30:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/QoT1Z9j-Bzk/upload/84195c3e282ca66b34e56ec03a87bcdf.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>FinTech systems are mission-critical: a single outage can mean lost money, lost trust, and even regulatory consequences. This post explores how to design platforms that scale with demand, recover gracefully from failure, and maintain compliance while delivering on speed and innovation.</p>
<h2 id="heading-designing-for-reliability">Designing for Reliability</h2>
<ul>
<li><p><strong>High Availability (HA):</strong> Multi-zone deployments, failover strategies, and database replication.</p>
</li>
<li><p><strong>Disaster Recovery (DR):</strong> Backups, recovery point objectives (RPO), and recovery time objectives (RTO).</p>
</li>
<li><p><strong>Monitoring and Observability:</strong> Logs, metrics, and distributed tracing (OpenTelemetry).</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Reliability is not a one-time project but a continuous practice built into every release cycle.</p>
<h2 id="heading-architectures-that-scale">Architectures That Scale</h2>
<ul>
<li><p><strong>Event-Driven Systems:</strong> Ideal for transaction-heavy workloads.</p>
</li>
<li><p><strong>Microservices:</strong> Modular design for faster iteration and scaling individual components.</p>
</li>
<li><p><strong>Hybrid and Cloud-Native Deployments:</strong> Cloud gives agility, but hybrid is often needed for regulatory reasons.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Choose architectures that balance <strong>throughput, cost, and compliance</strong>.</p>
<h2 id="heading-balancing-scale-with-compliance">Balancing Scale with Compliance</h2>
<ul>
<li><p><strong>Data Residency:</strong> Some regulations require data to stay within geographic boundaries.</p>
</li>
<li><p><strong>Auditability:</strong> Every transaction must be traceable.</p>
</li>
<li><p><strong>Performance Testing Under Constraints:</strong> Scale testing while maintaining PCI-DSS and KYC/AML requirements.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Scaling without compliance is a false economy. Growth must stay audit-ready.</p>
<h2 id="heading-resilience-in-practice">Resilience in Practice</h2>
<ul>
<li><p><strong>Chaos Testing:</strong> Injecting controlled failures to test recovery.</p>
</li>
<li><p><strong>Rate Limiting and Circuit Breakers:</strong> Protecting systems under stress.</p>
</li>
<li><p><strong>Resilient APIs:</strong> Ensuring idempotency and graceful degradation.</p>
</li>
</ul>
<p><strong>Leadership takeaway:</strong> Build for failure as the default assumption, not the exception.</p>
<h2 id="heading-leadership-takeaways">Leadership Takeaways</h2>
<ul>
<li><p>Reliability comes from culture, not just tools.</p>
</li>
<li><p>Architecture choices define the ceiling of scalability.</p>
</li>
<li><p>Compliance must be baked into scaling strategies.</p>
</li>
<li><p>Resilience is about expecting failure and engineering around it.</p>
</li>
</ul>
<h2 id="heading-coming-next">Coming Next</h2>
<p>In the next post, we’ll explore <strong>Trends Shaping the Future of FinTech</strong>, from embedded finance and AI to blockchain and digital assets.</p>
]]></content:encoded></item><item><title><![CDATA[APIs, Security and Compliance: The Technical Backbone of FinTech]]></title><description><![CDATA[APIs are how financial systems connect, but without security and compliance they can’t be trusted. This post looks at how APIs power modern finance, why security needs to be built in from day one, and how compliance standards like PCI-DSS, KYC, and A...]]></description><link>https://blog.faizahmed.in/apis-security-and-compliance-the-technical-backbone-of-fintech</link><guid isPermaLink="true">https://blog.faizahmed.in/apis-security-and-compliance-the-technical-backbone-of-fintech</guid><category><![CDATA[fintech]]></category><category><![CDATA[Credit Union]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Fri, 12 Sep 2025 13:47:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/tg7xChYyE08/upload/d9724f798cacf2a7e5026ac5364eb136.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>APIs are how financial systems connect, but without security and compliance they can’t be trusted. This post looks at how APIs power modern finance, why security needs to be built in from day one, and how compliance standards like PCI-DSS, KYC, and AML shape the way engineering leaders design systems.</p>
<h2 id="heading-apis-the-glue-of-modern-finance">APIs: The Glue of Modern Finance</h2>
<ul>
<li><p><strong>Open Banking APIs</strong>: Standardized access to bank data (PSD2, FDX in the US).</p>
</li>
<li><p><strong>Card Network APIs</strong>: Issuance, processing, and settlement (Visa, Mastercard).</p>
</li>
<li><p><strong>FinTech Platform APIs</strong>: Stripe, Plaid, Adyen, and others enable rapid product development.</p>
</li>
</ul>
<p><strong>Leadership takeaway</strong>: APIs are not just features. They are <strong>contracts between financial institutions</strong>, with uptime, reliability, and security expectations baked in.</p>
<h2 id="heading-security-the-first-line-of-trust">Security: The First Line of Trust</h2>
<p>Security isn’t optional in FinTech, it is the product. Common layers include:</p>
<ul>
<li><p><strong>Encryption</strong>: Symmetric (AES) for speed, asymmetric (RSA/EC) for signing and exchange.</p>
</li>
<li><p><strong>Tokenization</strong>: Protecting sensitive payment and card data.</p>
</li>
<li><p><strong>Confidential Computing</strong>: Isolating workloads using hardware enclaves (AWS Nitro, Intel SGX).</p>
</li>
<li><p><strong>PII Handling</strong>: Protecting sensitive customer data with strict access controls.</p>
</li>
</ul>
<p><strong>Leadership takeaway</strong>: Security controls must be integrated into CI/CD pipelines, not bolted on after launch.</p>
<h2 id="heading-compliance-building-within-boundaries">Compliance: Building Within Boundaries</h2>
<p>Compliance ensures systems can operate legally and at scale:</p>
<ul>
<li><p><strong>PCI-DSS</strong>: Required for handling cardholder data, with annual audits.</p>
</li>
<li><p><strong>KYC/AML</strong>: Identity checks, sanctions screening, fraud detection.</p>
</li>
<li><p><strong>VAPT &amp; Audits</strong>: Regular penetration testing and vulnerability assessments.</p>
</li>
<li><p><strong>Reporting Requirements</strong>: SARs, CTRs, and ongoing monitoring in US/Canada.</p>
</li>
</ul>
<p><strong>Leadership takeaway</strong>: Compliance isn’t the blocker to speed, it’s the <strong>license to operate</strong>. Teams that embed compliance early ship faster in the long run.</p>
<h2 id="heading-why-this-matters-for-leaders">Why This Matters for Leaders</h2>
<ul>
<li><p>APIs are where most integrations succeed or fail.</p>
</li>
<li><p>Security isn’t a checkbox, it defines customer trust.</p>
</li>
<li><p>Compliance isn’t optional, it’s a competitive advantage if done right.</p>
</li>
</ul>
<p>Engineering leaders must align product speed with <strong>regulatory resilience</strong> and guide teams to build with both in mind.</p>
<h2 id="heading-leadership-takeaways">Leadership Takeaways</h2>
<ul>
<li><p>APIs are the lifeline of FinTech, reliability matters as much as functionality.</p>
</li>
<li><p>Security is non-negotiable and should be treated as part of product design.</p>
</li>
<li><p>Compliance frameworks like PCI-DSS and KYC/AML dictate how systems scale.</p>
</li>
</ul>
<h2 id="heading-coming-next">Coming Next</h2>
<p>👉 In the next post, we’ll cover <strong>Building Scalable and Resilient FinTech Systems</strong>, where we dive into the architectures and practices that make financial platforms robust.</p>
]]></content:encoded></item><item><title><![CDATA[Core Pillars of FinTech: Payments, Lending, Wealth and Beyond]]></title><description><![CDATA[This post explores the core verticals of FinTech and how they shape modern financial products. From digital payments to wealth management and compliance automation, engineering leaders will see what makes each domain unique, where the biggest technic...]]></description><link>https://blog.faizahmed.in/core-pillars-of-fintech-payments-lending-wealth-and-beyond</link><guid isPermaLink="true">https://blog.faizahmed.in/core-pillars-of-fintech-payments-lending-wealth-and-beyond</guid><category><![CDATA[fintech]]></category><category><![CDATA[payments]]></category><category><![CDATA[Credit Union]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Thu, 11 Sep 2025 16:30:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/8lnbXtxFGZw/upload/ec483005289feba5fd50b7f43e4566a4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post explores the <strong>core verticals of FinTech</strong> and how they shape modern financial products. From digital payments to wealth management and compliance automation, engineering leaders will see what makes each domain unique, where the biggest technical challenges lie, and how to align teams to deliver in these areas.</p>
<h2 id="heading-payments-the-foundation-of-fintech">Payments: The Foundation of FinTech</h2>
<p>Payments are the backbone of every FinTech product. The landscape includes:</p>
<ul>
<li><p><strong>Card Networks</strong>: Visa, Mastercard, Discover, Amex.</p>
</li>
<li><p><strong>Gateways and Processors</strong>: Stripe, Adyen, PayPal.</p>
</li>
<li><p><strong>Rails</strong>: ACH, RTP, FedNow, wires, and checks.</p>
</li>
<li><p><strong>Payment Orchestration</strong>: Routing logic across gateways for cost and speed optimization.</p>
</li>
</ul>
<p><strong>Leadership takeaway</strong>: Choosing the right rails and gateways affects <strong>latency, cost, and compliance obligations</strong>. Leaders must guide these trade-offs early.</p>
<h2 id="heading-lending-and-credit-innovation">Lending and Credit Innovation</h2>
<p>Lending has evolved rapidly with digital-first models:</p>
<ul>
<li><p><strong>BNPL (Buy Now Pay Later)</strong>: Embedded into e-commerce flows.</p>
</li>
<li><p><strong>Alternative Credit Scoring</strong>: Using data beyond FICO scores.</p>
</li>
<li><p><strong>Instant Loan Approvals</strong>: API-driven decision engines.</p>
</li>
</ul>
<p><strong>Leadership takeaway</strong>: Engineering teams must design for <strong>risk modeling, data privacy, and fast decision APIs</strong>, while staying aligned with lending regulations.</p>
<h2 id="heading-wealthtech-and-investments">WealthTech and Investments</h2>
<p>WealthTech platforms are democratizing investing:</p>
<ul>
<li><p><strong>Robo-Advisors</strong>: Automated portfolio management.</p>
</li>
<li><p><strong>APIs for Brokerage</strong>: Seamless access to trading and wealth accounts.</p>
</li>
<li><p><strong>Low-Cost Investing</strong>: Fractional shares and micro-investments.</p>
</li>
</ul>
<p><strong>Leadership takeaway</strong>: Building these systems requires <strong>secure integrations with brokers and custodians</strong> and careful handling of personal financial data.</p>
<h2 id="heading-insurtech-and-regtech">InsurTech and RegTech</h2>
<ul>
<li><p><strong>InsurTech</strong>: Embedded insurance at the point of sale, usage-based policies, automated claims.</p>
</li>
<li><p><strong>RegTech</strong>: Compliance automation for KYC/AML, fraud detection, sanctions screening, and reporting.</p>
</li>
</ul>
<p><strong>Leadership takeaway</strong>: These domains are <strong>compliance-heavy</strong>. Leaders must balance user-friendly experiences with <strong>audit trails, monitoring, and regulatory integrations</strong>.</p>
<h2 id="heading-emerging-frontiers">Emerging Frontiers</h2>
<ul>
<li><p><strong>Crypto and DeFi</strong>: Stablecoins, custody solutions, tokenized assets.</p>
</li>
<li><p><strong>Embedded Finance</strong>: Non-financial platforms offering financial services.</p>
</li>
<li><p><strong>Banking-as-a-Service (BaaS)</strong>: Plug-and-play financial products through APIs.</p>
</li>
</ul>
<p><strong>Leadership takeaway</strong>: Experimentation is fine, but leaders must filter hype from sustainable value, especially in regulated markets.</p>
<h2 id="heading-leadership-takeaways">Leadership Takeaways</h2>
<ul>
<li><p>Payments remain the <strong>core pillar</strong>, influencing every other vertical.</p>
</li>
<li><p>Lending, wealth, insurance, and compliance all demand <strong>domain fluency + technical execution</strong>.</p>
</li>
<li><p>Emerging areas like DeFi and embedded finance present opportunities but require <strong>measured risk-taking</strong>.</p>
</li>
</ul>
<h2 id="heading-coming-next">Coming Next</h2>
<p>In the next post, we’ll explore <strong>APIs, Security and Compliance: The Technical Backbone of FinTech</strong>, focusing on the engineering choices that make financial systems safe, reliable, and scalable.</p>
]]></content:encoded></item><item><title><![CDATA[FinTech Fundamentals Every Engineering Leader Should Know (2025 Edition)]]></title><description><![CDATA[This post introduces the essential building blocks of FinTech for engineering leaders. You’ll learn how financial institutions differ, how money moves across key payment rails, the must-know jargon of the industry, and why compliance, security, and K...]]></description><link>https://blog.faizahmed.in/fintech-fundamentals-every-engineering-leader-should-know-2025-edition</link><guid isPermaLink="true">https://blog.faizahmed.in/fintech-fundamentals-every-engineering-leader-should-know-2025-edition</guid><category><![CDATA[fintech]]></category><category><![CDATA[payments]]></category><category><![CDATA[Credit Union]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Thu, 11 Sep 2025 12:33:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/k0Jo8m6DO6k/upload/a844f0e72b00ea55cb4981c5a4bd7781.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post introduces the essential building blocks of FinTech for engineering leaders. You’ll learn how financial institutions differ, how money moves across key payment rails, the must-know jargon of the industry, and why compliance, security, and KYC are non-negotiable in modern finance.</p>
<h2 id="heading-why-fintech-matters-for-engineering-leaders">Why FinTech Matters for Engineering Leaders</h2>
<p>FinTech isn’t just about apps that move money - it’s where <strong>business, regulation, and technology converge</strong>. Customers, investors, and regulators now expect engineering leaders to speak fluently about <strong>payments, compliance, and tech</strong>.</p>
<p>In North America, FinTech has grown into a massive ecosystem with banks, credit unions, neobanks, payment processors, and startups working side by side. For engineering leaders, success in this space requires <strong>mastery of the fundamentals</strong> - from understanding money movement to building compliant, secure systems that scale.</p>
<h2 id="heading-institutions-youll-work-with">Institutions You’ll Work With</h2>
<ul>
<li><p><strong>Banks:</strong> For-profit, broad financial services, highly regulated.</p>
</li>
<li><p><strong>Credit Unions:</strong> Non-profit, member-owned, community-focused.</p>
</li>
<li><p><strong>Financial Institutions (FI):</strong> The umbrella term covering banks, credit unions, insurers, and investment firms.</p>
</li>
</ul>
<p>Why this matters → The <strong>partnership model</strong> differs across institutions. Engineering leaders must design integrations (APIs, onboarding flows, compliance checks) that <strong>adapt to each type</strong>.</p>
<h2 id="heading-payment-rails-amp-why-they-matter">Payment Rails &amp; Why They Matter</h2>
<p>Every FinTech product relies on <strong>rails</strong> - the networks that move money. Here’s what you should know:</p>
<ul>
<li><p><strong>ACH:</strong> Batch system, cheap, but takes 1–2 days.</p>
</li>
<li><p><strong>RTP (The Clearing House):</strong> Instant, ISO 20022, growing for gig payouts.</p>
</li>
<li><p><strong>FedNow:</strong> US government-backed instant payments rail.</p>
</li>
<li><p><strong>Wire Transfers:</strong> Instant but costly, used for large-value payments.</p>
</li>
<li><p><strong>Checks:</strong> Legacy, still used in B2B, but fading.</p>
</li>
</ul>
<p>Leadership takeaway: Choose rails based on <strong>speed, cost, and compliance trade-offs</strong>. A payroll app doesn’t need the same rails as a high-value corporate transfer.</p>
<h2 id="heading-key-jargon-you-must-know">Key Jargon You Must Know</h2>
<p>FinTech has its own language. Leaders should be fluent in:</p>
<ul>
<li><p><strong>Clearing vs. Settlement</strong> → Message vs. actual money movement.</p>
</li>
<li><p><strong>KYC/AML</strong> → Identity checks and anti-fraud measures.</p>
</li>
<li><p><strong>Payment Orchestration</strong> → Smart routing across gateways.</p>
</li>
<li><p><strong>Tokenization</strong> → Securing sensitive card data.</p>
</li>
<li><p><strong>Interchange Fee / Chargebacks</strong> → The economics of card payments.</p>
</li>
</ul>
<p>Not knowing these terms makes it harder to guide teams or earn credibility with partners.</p>
<h2 id="heading-security-amp-compliance-non-negotiable">Security &amp; Compliance: Non-Negotiable</h2>
<p>In FinTech, <strong>trust = uptime + compliance + security</strong>. The typical journey looks like this:</p>
<ol>
<li><p><strong>Internal Testing</strong> → Smoke, unit, integration, performance.</p>
</li>
<li><p><strong>VAPT (Vulnerability Assessment &amp; Penetration Testing)</strong> → Simulated attacks.</p>
</li>
<li><p><strong>PCI DSS Certification</strong> → Annual audits to process card data.</p>
</li>
<li><p><strong>Renewals &amp; Reporting</strong> → Continuous monitoring, regulatory filings.</p>
</li>
</ol>
<p>As a leader, you must set the tone: <strong>security is not a feature, it’s the foundation</strong>.</p>
<h2 id="heading-customer-onboarding-amp-kyc-in-practice">Customer Onboarding &amp; KYC in Practice</h2>
<p>Engineering leaders must understand what’s under the hood of onboarding flows:</p>
<ul>
<li><p><strong>Database checks</strong> (SSN/TIN verification, watchlists).</p>
</li>
<li><p><strong>ID verification</strong> (government IDs, selfie/liveness checks).</p>
</li>
<li><p><strong>Fraud reports</strong> (phone, email, address lookups).</p>
</li>
<li><p><strong>OFAC/AML screening</strong> (sanctions, PEP checks, adverse media).</p>
</li>
<li><p><strong>Credit checks &amp; financial risk scoring.</strong></p>
</li>
</ul>
<p>Every integration decision - vendor choice, flow design, retries - affects compliance <strong>and customer trust</strong>.</p>
<h2 id="heading-b2c-vs-b2b-fintech-the-leadership-lens">B2C vs B2B FinTech: The Leadership Lens</h2>
<ul>
<li><p><strong>Customer-first FinTech (B2C):</strong> Focused on UX, adoption, and trust. (Ex: Cash App, Chime)</p>
</li>
<li><p><strong>Enterprise-first FinTech (B2B/B2B2C):</strong> Focused on compliance, resilience, and scalability. (Ex: Plaid, Adyen)</p>
</li>
</ul>
<p>Leaders need to set priorities differently. In B2C, small friction kills adoption. In B2B, missing compliance kills the deal.</p>
<h2 id="heading-leadership-takeaways">Leadership Takeaways</h2>
<ul>
<li><p>FinTech is where <strong>tech, money, and regulation collide</strong>.</p>
</li>
<li><p>Learn the <strong>rails and jargon</strong> - they guide technical trade-offs.</p>
</li>
<li><p>Security and compliance are <strong>non-negotiable foundations</strong>.</p>
</li>
<li><p>Onboarding isn’t just UX - it’s <strong>compliance by design</strong>.</p>
</li>
<li><p>Know whether you’re building <strong>B2C (speed/UX)</strong> or <strong>B2B (compliance/scale)</strong> systems.</p>
</li>
</ul>
<h2 id="heading-coming-next">Coming Next</h2>
<p>In the next post of this series, we’ll explore <strong>Core Pillars of FinTech: Payments, Lending, Wealth &amp; Beyond</strong>, breaking down the key verticals and how engineering leaders should approach them.</p>
]]></content:encoded></item><item><title><![CDATA[PCI DSS 4.0: Client-Side Attack Vectors and What Developers Must Do]]></title><description><![CDATA[Payment form security has largely focused on backend systems, until now.
PCI DSS 4.0 draws much-needed attention to client-side risks, such as Magecart and script tampering.
In this post, you'll see why the front-end has become a battleground, what t...]]></description><link>https://blog.faizahmed.in/pci-dss-40-client-side-attack-vectors-and-what-developers-must-do</link><guid isPermaLink="true">https://blog.faizahmed.in/pci-dss-40-client-side-attack-vectors-and-what-developers-must-do</guid><category><![CDATA[devsecurity]]></category><category><![CDATA[PCI DSS]]></category><category><![CDATA[websecurity]]></category><category><![CDATA[payments]]></category><category><![CDATA[compliance ]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Wed, 27 Aug 2025 07:12:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Q59HmzK38eQ/upload/86d9a9e0669eda19d193527b95dd528c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Payment form security has largely focused on backend systems, until now.</p>
<p>PCI DSS 4.0 draws much-needed attention to <strong>client-side risks</strong>, such as Magecart and script tampering.</p>
<p>In this post, you'll see why the front-end has become a battleground, what the new requirements demand, and how to implement them effectively using visuals and clear guidance.</p>
<h2 id="heading-the-new-client-side-attack-landscape">The New Client-Side Attack Landscape</h2>
<p>Traditional server-only security controls are bypassed by attacks that inject malicious JavaScript into payment pages.</p>
<p>Hackers exploiting client-side scripts directly intercept users’ card data undetectable by most defenses.</p>
<p>This vulnerability is especially critical as it's the gateway for <strong>Magecart-style attacks</strong>.</p>
<h2 id="heading-pci-dss-40-timeline-why-now">PCI DSS 4.0 Timeline: Why Now?</h2>
<p>PCI DSS v3.2.1 officially retired on <strong>March 31, 2024</strong>, but organizations have until <strong>March 31, 2025</strong> to implement all the new v4.0 requirements, including client-side ones.</p>
<h2 id="heading-deep-dive-what-requirements-643-amp-1161-actually-mean">Deep Dive: What Requirements 6.4.3 &amp; 11.6.1 Actually Mean</h2>
<h3 id="heading-requirement-643-script-management"><strong>Requirement 6.4.3 — Script Management</strong></h3>
<p>All scripts executed in the browser on payment pages must be:</p>
<ul>
<li><p><strong>Authorized</strong> (explicitly approved)</p>
</li>
<li><p><strong>Integrity-verified</strong> (e.g., using SRI or CSP)</p>
</li>
<li><p><strong>Catalogued</strong> in an inventory with valid business justification</p>
</li>
</ul>
<h3 id="heading-requirement-1161-changetamper-detection"><strong>Requirement 11.6.1 — Change/Tamper Detection</strong></h3>
<p>Payment pages must be continuously monitored for unauthorized script or header changes. Any deviation must trigger alerts and incident processes.</p>
<h2 id="heading-visualizing-client-side-protections">Visualizing Client-Side Protections</h2>
<p>Here's a focused diagram that maps the sequence of protections needed around payment forms:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756278809075/3e8ff2e8-5d39-4dc5-946d-4c395e4ee2c0.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-implementation-techniques-amp-tools">Implementation Techniques &amp; Tools</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Control</strong></td><td><strong>How to Implement / Tooling</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Script Inventory</strong></td><td>Maintain live registry with justification (e.g., Stripe.js, analytics)</td></tr>
<tr>
<td><strong>Authorization + Integrity</strong></td><td>Enforce CSP rules, use SRI hashes, and vet third-party code</td></tr>
<tr>
<td><strong>Tamper Detection</strong></td><td>Deploy monitoring solutions like Feroot, Imperva, Source Defense</td></tr>
</tbody>
</table>
</div><h2 id="heading-common-pitfalls-and-how-to-avoid-them">Common Pitfalls (And How to Avoid Them)</h2>
<ul>
<li><p>Statically defined inventories that aren't updated in real time</p>
</li>
<li><p>Overly restrictive CSPs that break legitimate UX</p>
</li>
<li><p>No alerting workflows or incident response when tampering occurs</p>
</li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Securing the backend is no longer enough. PCI DSS 4.0 mandates strong frontend controls to guard against evolving threats like script skimming. Right tools, processes, and vigilance are now required to stay audit-ready and protect your users.</p>
]]></content:encoded></item><item><title><![CDATA[COPPA vs HIPAA vs PCI DSS: What Developers Actually Need to Know]]></title><description><![CDATA[Stop reading legal PDFs. Start building systems that pass audits.
As engineers, compliance often feels like a legal minefield.
COPPA, HIPAA, and PCI DSS all demand strict security, but each has its own rules, penalties, and best practices.
In this po...]]></description><link>https://blog.faizahmed.in/coppa-vs-hipaa-vs-pci-dss</link><guid isPermaLink="true">https://blog.faizahmed.in/coppa-vs-hipaa-vs-pci-dss</guid><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Thu, 14 Aug 2025 10:28:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/veNb0DDegzE/upload/368652cc590f1cc66b7062c3480c3ec4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Stop reading legal PDFs. Start building systems that pass audits.</em></p>
<p>As engineers, compliance often feels like a legal minefield.</p>
<p>COPPA, HIPAA, and PCI DSS all demand strict security, but each has its own rules, penalties, and best practices.</p>
<p>In this post, we’ll break these frameworks down into <strong>actionable development steps</strong>, show where they overlap, and provide visual guides so you can architect compliance into your systems from day one.</p>
<h3 id="heading-1-quick-definitions-for-developers"><strong>1. Quick Definitions for Developers</strong></h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Standard</strong></td><td><strong>Covers</strong></td><td><strong>Common in</strong></td><td><strong>Data Type</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>COPPA</strong></td><td>Protects data of children under 13</td><td>Games, EdTech, Kid Social</td><td>Name, email, location</td></tr>
<tr>
<td><strong>HIPAA</strong></td><td>Protects PHI (Protected Health Information)</td><td>Telehealth, EMR, Healthcare SaaS</td><td>Medical history, billing</td></tr>
<tr>
<td><strong>PCI DSS</strong></td><td>Protects cardholder data</td><td>E-commerce, Wallets, Payment APIs</td><td>PAN, CVV, expiry date</td></tr>
</tbody>
</table>
</div><h3 id="heading-2-core-implementation-requirements"><strong>2. Core Implementation Requirements</strong></h3>
<p><strong>COPPA</strong></p>
<ul>
<li><p>Age-gating &amp; parental consent flows</p>
</li>
<li><p>Minimal data collection</p>
</li>
<li><p>Clear deletion and parental dashboard access</p>
</li>
</ul>
<p><strong>HIPAA</strong></p>
<ul>
<li><p>Encryption at rest &amp; in transit</p>
</li>
<li><p>Role-based access control (RBAC)</p>
</li>
<li><p>Immutable audit logs</p>
</li>
</ul>
<p><strong>PCI DSS</strong></p>
<ul>
<li><p>Tokenization of card data</p>
</li>
<li><p>Segmentation of the Cardholder Data Environment (CDE)</p>
</li>
<li><p>Secure key management &amp; rotation</p>
</li>
</ul>
<h3 id="heading-3-overlaps-amp-differences"><strong>3. Overlaps &amp; Differences</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755167451316/6e8a10c9-bb1f-41f9-9c7b-726a80fc9165.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-4-the-hardening-path-for-compliant-data-flows"><strong>4. The “Hardening Path” for Compliant Data Flows</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755167654862/9710ed77-85f0-4f6c-83f3-7230613ca212.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-lets-wrap-up"><strong>Let’s wrap up…</strong></h2>
<p>Compliance isn’t a roadblock.</p>
<p>Compliance is a <strong>design constraint that makes systems more secure</strong>.</p>
<p>By aligning your architecture early with COPPA, HIPAA, or PCI DSS, you’ll avoid expensive retrofits, reduce breach risks, and pass audits with confidence.</p>
<p>Our next blogs will dive deeper into <strong>AWS architectures for each framework</strong> so you can go from checklists to deployable infrastructure.</p>
]]></content:encoded></item><item><title><![CDATA[Secrets Sprawl in Node.js Projects: Detection, Prevention & Secure Deployment (2025)]]></title><description><![CDATA[If a credential can be found in your code base, CI logs, or Slack archive — it’s not just a secret; it’s a risk.
By 2025, leaked credentials have reached 23.8 million across public repos, with 35% of private repos also containing sensitive data like ...]]></description><link>https://blog.faizahmed.in/secrets-sprawl-in-nodejs-projects-detection-prevention-and-secure-deployment-2025</link><guid isPermaLink="true">https://blog.faizahmed.in/secrets-sprawl-in-nodejs-projects-detection-prevention-and-secure-deployment-2025</guid><category><![CDATA[Node.js]]></category><category><![CDATA[secrets management]]></category><category><![CDATA[GitGuardian]]></category><category><![CDATA[DevSecOps]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[Backend Engineering]]></category><category><![CDATA[cloud security]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[#securitybestpractices ]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Tue, 05 Aug 2025 08:07:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/lsbtg9mJU14/upload/1818077473a9c15315121bd2f31cde7f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If a credential can be found in your code base, CI logs, or Slack archive — it’s not just a secret; it’s a risk.</p>
<p>By 2025, leaked credentials have reached 23.8 million across public repos, with 35% of private repos also containing sensitive data like AWS keys or DB passwords.</p>
<p>Despite scanning tools and push protections, 70% of leaked secrets remain valid days after detection, leaving your systems exposed.</p>
<p>Secrets sprawl happens when developers take the “easy way” ie. hardcoding tokens in <code>.env</code> files, Slack chats, CI logs, or even internal documentation. These crumbs spread across systems, expanding the attack surface at every access point.</p>
<h2 id="heading-how-secrets-leak-in-nodejs-projects">How Secrets Leak in Node.js Projects?</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Source</strong></td><td><strong>How Sprawl Happens</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>.env</code> files checked in</td><td>Sudden leak through Git, build artifacts</td></tr>
<tr>
<td>Code or config</td><td>API keys or secrets hardcoded for quick use</td></tr>
<tr>
<td>CI/CD logs</td><td>Secrets inadvertently printed or stored</td></tr>
<tr>
<td>Collaboration tools</td><td>Pasted or screenshot tokens in Slack, Jira, etc.</td></tr>
</tbody>
</table>
</div><p>These patterns are common and often silent — until it’s too late.</p>
<h2 id="heading-detection-tools-what-really-works">Detection Tools: What Really Works?</h2>
<p>Tool Comparison from GitGuardian’s 2023 Study —</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Tool</strong></td><td><strong>Precision</strong></td><td><strong>Recall</strong></td></tr>
</thead>
<tbody>
<tr>
<td>GitHub Scanner</td><td>~75%</td><td>Moderate</td></tr>
<tr>
<td>Gitleaks</td><td>~46%</td><td>~88%</td></tr>
<tr>
<td>SpectralOps</td><td>Moderate</td><td>~67%</td></tr>
</tbody>
</table>
</div><p>Precision vs recall matters. Gitleaks finds most leaks; GitHub scanner has fewer false positives; SpectralOps balances both with enterprise features.</p>
<p>Common tools:</p>
<ul>
<li><p><strong>Gitleaks</strong>: CLI-based scanner for Git history</p>
</li>
<li><p><strong>Detect‑Secrets</strong>: Baseline first; scan only new commits → reduces noise</p>
</li>
<li><p><strong>GitHub Advanced Secret Scanning</strong>: Real-time push protection for GitHub Enterprise users</p>
</li>
</ul>
<h2 id="heading-mitigation-workflow-from-detection-to-prevention">Mitigation Workflow: From Detection to Prevention</h2>
<blockquote>
<h3 id="heading-detection-prioritization-remediation"><strong>Detection → Prioritization → Remediation</strong></h3>
</blockquote>
<ul>
<li><p><strong>Detect</strong> using CI integration (e.g., Gitleaks, GitHub push protection)</p>
</li>
<li><p><strong>Prioritize</strong> using risk-based scoring (e.g., RiskHarvester helps flag secrets tied to valuable assets like DB hosts)</p>
</li>
<li><p><strong>Mitigate</strong> by rotating and revoking leaked keys not just deleting Git history</p>
</li>
<li><p><strong>Prevent</strong> via secrets injection: use tools like AWS Secrets Manager, Vault, Doppler, or SOPS to avoid storing secrets in code</p>
</li>
</ul>
<h2 id="heading-developer-workflows-to-stop-sprawl">Developer Workflows to Stop Sprawl</h2>
<ul>
<li><p>Integrate scanner in pre-commit hooks or CI pipelines</p>
</li>
<li><p>Use baselines to ignore pre-existing secrets and prevent new ones from entering code</p>
</li>
<li><p>Enforce push-protection: GitHub will block secret-containing commits before they land</p>
</li>
<li><p>Train developers: never hardcode credentials in code, config, or docs</p>
</li>
</ul>
<h2 id="heading-visual-workflow-how-to-stop-sprawl">Visual Workflow: How To Stop Sprawl</h2>
<pre><code class="lang-mermaid">graph LR
  dev[Developer] --&gt; local[Codebase]
  local --&gt; scanner{Run secret scanner}
  scanner --&gt;|Pass| ci[CI Pipeline]
  scanner --&gt;|Leak found| alert[Rollback &amp; Fix]
  ci --&gt; deploy[Safe Deployment]
</code></pre>
<p><strong>Commit → scan → fail/build-block → rotate secret → merge clean → deploy safely.</strong></p>
<h3 id="heading-real-world-cases-amp-impact">Real-World Cases &amp; Impact</h3>
<p>GitGuardian’s research shows only <strong>2.6% of secrets are revoked within one hour</strong> of detection, while most remain valid after 5 days making them “zombie tokens” that risk ongoing exploitation.</p>
<p>GitHub (public repos) notified over <strong>1.9M secret leaks</strong> in 2022 alone, and GitHub’s secret scanning now automatically flags many patterns but teams still need to remediate actively.</p>
<h2 id="heading-how-to-prevent-secret-sprawl-across-staging-amp-production">How to Prevent Secret Sprawl Across Staging &amp; Production</h2>
<h3 id="heading-1-centralize-secrets-dont-duplicate-them">1. Centralize Secrets, Don’t Duplicate Them</h3>
<ul>
<li><p>Use secret management systems such as <strong>AWS Secrets Manager</strong>, <strong>HashiCorp Vault</strong>, <strong>Doppler</strong>, or <strong>SOPS</strong> instead of <code>.env</code> files. These systems support lifecycle operations like rotation, audit, and scoped access control.</p>
</li>
<li><p><strong>Avoid storing secrets in code, images, or multiple env files</strong>; centralization keeps secrets discoverable and manageable.</p>
</li>
</ul>
<h3 id="heading-2-inject-secrets-at-runtime-into-the-process">2. Inject Secrets at Runtime Into the Process</h3>
<ul>
<li><p>Never bake secrets into Docker images or bundles. Use <strong>runtime injection</strong> such as:</p>
<ul>
<li><p><code>doppler run npm start</code></p>
</li>
<li><p>Environment variables set by CI or orchestrator (like AWS ECS task definitions or Kubernetes Secrets).</p>
</li>
</ul>
</li>
<li><p>Developers should use the same injection mechanism locally as in CI and production to avoid drift and accidental commits.</p>
</li>
</ul>
<h3 id="heading-3-enforce-least-privilege-amp-scoped-access">3. Enforce Least Privilege &amp; Scoped Access</h3>
<ul>
<li><p>Set strict IAM policies that grant staging access only to staging secrets, and production access only to production secrets. No cross-environment IAM roles.</p>
</li>
<li><p>For teams using Vault or Conjur, apply declarative policies so each environment only sees its own secrets.</p>
</li>
</ul>
<h3 id="heading-4-integrate-secret-scanning-in-cicd-pipelines">4. Integrate Secret Scanning in CI/CD Pipelines</h3>
<ul>
<li><p>Every commit or merge request should trigger a scan using tools like <strong>Gitleaks</strong>, <strong>SpectralOps</strong>, or <strong>GitHub Advanced Secret Scanning</strong>.</p>
</li>
<li><p>Block the merge or push if a secret is detected (regardless of environment), and require rotation.</p>
</li>
<li><p>Use <strong>baselines</strong> to ignore previously-known and rotated secrets; block only new additions.</p>
</li>
</ul>
<h3 id="heading-5-rotate-and-revoke-leaked-secrets-immediately">5. Rotate and Revoke Leaked Secrets Immediately</h3>
<ul>
<li><p>If a secret ever leaks (in staging logs, PR comments, Docker image tags), don’t just delete it.</p>
</li>
<li><p>Implement scheduled rotation (30–90 days) or event-based rotation whenever roles change.</p>
</li>
</ul>
<h3 id="heading-6-maintain-auditability-amp-team-training">6. Maintain Auditability &amp; Team Training</h3>
<ul>
<li><p>Enable <strong>audit logs</strong> in secrets storage systems or CI/CD pipeline that record who accessed what, when, and from where.</p>
</li>
<li><p>Regularly train developers and DevOps on these workflows:</p>
<ul>
<li><p>Never commit secrets</p>
</li>
<li><p>Use the approved injection method</p>
</li>
<li><p>Escalate and rotate leaked credentials immediately</p>
</li>
<li><p>Validate push triggers before release pipelines activate</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-summary-staging-amp-production-secrets-protection-table">Summary: Staging &amp; Production Secrets Protection Table</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Environment</strong></td><td><strong>Access Level</strong></td><td><strong>Injection Method</strong></td><td><strong>Rotation Frequency</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Local/Dev</td><td>Developer access only</td><td>Doppler CLI or <code>.env</code> (ignored in Git)</td><td>No rotation needed</td></tr>
<tr>
<td>Staging</td><td>CI role or staging IAM only</td><td>Secrets Manager or Vault via CI runtime</td><td>On leak or scheduled</td></tr>
<tr>
<td>Production</td><td>Production IAM + CI only</td><td>Secrets injected via orchestrator runtime</td><td>Strict rotation cycle</td></tr>
</tbody>
</table>
</div><h3 id="heading-developer-flow-preventing-secrets-sprawl">Developer Flow: Preventing Secrets Sprawl</h3>
<pre><code class="lang-mermaid">graph LR
  Dev[Developer] --&gt; Local["Local Dev (.env excluded)"]
  Local --&gt; PR{Pull Request}
  PR --&gt; Scanner[Secret Scanner in CI]
  Scanner --&gt;|Pass| Merge[Merge to main]
  Scanner --&gt;|Fail| Alert[Fix &amp; Revoke Secret]
  Merge --&gt; Deploy[CI Injects Secrets &amp; Deploys]
</code></pre>
<p>Unified flow ensures <strong>no secret ever lives in Git</strong>, whether staging or production.</p>
<h3 id="heading-why-it-works">Why it Works</h3>
<ul>
<li><p>Central secret vaults: consistent reference without duplication</p>
</li>
<li><p>Runtime injection: no secret persistence in code or images</p>
</li>
<li><p>CI-enforced scanning: blocks new leaks early</p>
</li>
<li><p>Strict access boundaries: no cross-environment exposure</p>
</li>
<li><p>Immediate rotation and audit logs: reduce risk window</p>
</li>
</ul>
<h2 id="heading-tldr-real-steps-you-can-take-today">TL;DR: Real Steps You Can Take Today</h2>
<ul>
<li><p>GitGuardian reports <strong>23.8 million</strong> secrets leaked publicly in 2024. A 25% increase from the prior year, with <strong>70% remaining active</strong> after detection.</p>
</li>
<li><p>Secrets often spread via <code>.env</code> files, hardcoded config, CI logs, or team chat tools.</p>
</li>
<li><p>Use tools like <strong>Gitleaks</strong>, <strong>GitHub push protection</strong>, and <strong>RiskHarvester</strong> to detect and prioritize credential removal.</p>
</li>
<li><p>Prevent leakage by centralizing secrets using <strong>Vault, AWS Secrets Manager, Doppler, or SOPS</strong>, and injecting them at runtime not baking into code or images.</p>
</li>
<li><p>Enforce strict environment boundaries: staging secrets isolated from production, with rotated credentials and least-privilege access controls.</p>
</li>
</ul>
<h2 id="heading-want-customized-fixes">Want Customized Fixes?</h2>
<p>Drop a comment with your stack:</p>
<blockquote>
<p><em>"We’re using GitHub, Node.js, AWS Lambda, and Docker - how do we prevent secret sprawl across staging and prod?"</em></p>
</blockquote>
<p>I’ll help build a tailored strategy for scanning, rotating, and preventing sprawl in your environment.</p>
]]></content:encoded></item><item><title><![CDATA[Zero-Downtime Deployments in Node.js: Real Strategies, Real Examples]]></title><description><![CDATA[“It’s 3 AM. The system’s live. You push an update and suddenly traffic falls off a cliff.”
That’s the moment teams stop being heroes. Zero-downtime deployments let you ride out updates in production—without the drama.

This post gives you actionable ...]]></description><link>https://blog.faizahmed.in/zero-downtime-deployments-in-nodejs</link><guid isPermaLink="true">https://blog.faizahmed.in/zero-downtime-deployments-in-nodejs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[Zero Downtime]]></category><category><![CDATA[Devops]]></category><category><![CDATA[SRE]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[canary release]]></category><category><![CDATA[Blue/Green deployment]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[deployment strategies]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Tue, 29 Jul 2025 07:53:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/WGYHD7VK_uU/upload/a8d4ae660cd361a58178b2cc1971f812.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>“It’s 3 AM. The system’s live. You push an update and suddenly traffic falls off a cliff.”</p>
<p>That’s the moment teams stop being heroes. Zero-downtime deployments let you ride out updates in production—<em>without the drama</em>.</p>
</blockquote>
<p>This post gives you actionable insights, backed by examples and sources - no distractions, just substance.</p>
<h2 id="heading-why-downtime-still-hurts">Why Downtime Still Hurts</h2>
<p>Even seconds of downtime can cost teams user trust or revenue. In Node.js, crashes often come from abrupt shutdowns, unhandled errors, or long-running connections being cut mid-flight. Ensuring high availability is now table stakes in production environments.</p>
<h2 id="heading-what-goes-wrong">What Goes Wrong?</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Failure Scenario</strong></td><td><strong>Impact on Users</strong></td><td><strong>Fix Strategy</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Terminating the server mid-connection</td><td>Dropped WebSocket / HTTP requests</td><td>Implement graceful shutdown</td></tr>
<tr>
<td>Database migrations that aren’t backward-compatible</td><td>Crashes or bad data</td><td>Expand → migrate → contract approach</td></tr>
<tr>
<td>Faulty load balancer config (e.g. missing health checks, no draining)</td><td>Routing to dead nodes — 502 responses</td><td>Configure LB health checks and draining</td></tr>
</tbody>
</table>
</div><p>Graceful shutdown is key. Node apps must stop accepting new traffic but finish in-flight requests in a controlled manner.</p>
<h2 id="heading-proven-deployment-strategies">Proven Deployment Strategies</h2>
<h3 id="heading-bluegreen-deployments">Blue–Green Deployments</h3>
<p>Two parallel environments (Blue = live, Green = staging). once Green is healthy, flip traffic instantly.</p>
<ul>
<li><p><strong>Pros:</strong> Instant rollback, reliable release</p>
</li>
<li><p><strong>Cons:</strong> Twice the infrastructure cost; databasing across environments is tricky</p>
</li>
</ul>
<h3 id="heading-canary-releases">Canary Releases</h3>
<p>Roll out new version to a fraction of users (e.g., 10%), monitor, then expand.</p>
<ul>
<li><p><strong>Pros:</strong> Safe rollouts, anomaly detection</p>
</li>
<li><p><strong>Cons:</strong> Requires feature flags, can be complex to orchestrate</p>
</li>
</ul>
<h3 id="heading-rolling-updates">Rolling Updates</h3>
<p>Replace nodes one by one behind your load balancer - maintaining availability.</p>
<ul>
<li><p><strong>Pros:</strong> Efficient use of resources</p>
</li>
<li><p><strong>Cons:</strong> Possible mixed-version traffic unless health checks and probing are solid</p>
</li>
</ul>
<h3 id="heading-feature-flags-dark-launches">Feature Flags / Dark Launches</h3>
<p>Deploy code in production, but only activate features via runtime toggles.</p>
<ul>
<li><p><strong>Pros:</strong> Feature control, experiment safely</p>
</li>
<li><p><strong>Cons:</strong> Requires disciplined flag hygiene and oversight. Often paired with Canary to minimise user exposure</p>
</li>
</ul>
<h2 id="heading-nodejs-best-practices">Node.js Best Practices</h2>
<h3 id="heading-graceful-shutdown-example">Graceful Shutdown Example</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> server = app.listen(PORT);
<span class="hljs-keyword">let</span> shuttingDown = <span class="hljs-literal">false</span>;

process.on(<span class="hljs-string">'SIGTERM'</span>, <span class="hljs-function">() =&gt;</span> {
  shuttingDown = <span class="hljs-literal">true</span>;
  server.close(<span class="hljs-function">() =&gt;</span> process.exit(<span class="hljs-number">0</span>));
});

app.use(<span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (shuttingDown) {
    res.set(<span class="hljs-string">'Connection'</span>, <span class="hljs-string">'close'</span>);
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">503</span>).send(<span class="hljs-string">'Server shutting down'</span>);
  }
  next();
});
</code></pre>
<ul>
<li><p>Signals stop accepting requests</p>
</li>
<li><p>Closes open connections cleanly before exit</p>
</li>
</ul>
<h3 id="heading-auto-reloads-with-pm2">Auto Reloads with PM2</h3>
<pre><code class="lang-bash">pm2 start app.js --name api -i max
pm2 gracefulReload api
</code></pre>
<p>Cluster mode ensures rolling restarts with no downtime as long as at least one worker stays live.</p>
<h2 id="heading-infrastructure-essentials">Infrastructure Essentials</h2>
<ul>
<li><p><strong>Load Balancers</strong> (NGINX, ALB, Traefik): support health checks and can gracefully drain traffic</p>
</li>
<li><p><strong>Kubernetes</strong>: use readiness/liveness probes and terminationGracePeriodSeconds to ensure safe pod removal</p>
</li>
<li><p><strong>Monitoring &amp; Rollback Triggers</strong>: Prometheus, Datadog, or CloudWatch can detect latency spikes or error rates and auto-trigger rollbacks or alerts</p>
</li>
</ul>
<h2 id="heading-deployment-flow">Deployment Flow</h2>
<pre><code class="lang-mermaid">graph LR
  CI["CI Pipeline (build/test)"]
  CI --&gt; Build["Build + Docker Image"]
  Build --&gt; Push["Push to Registry"]
  Push --&gt; Canary["Deploy Canary (~10%)"]
  Canary --&gt; HealthCheck{"Healthy?"}
  HealthCheck --&gt;|Yes| Rollout["Scale to 100%"]
  HealthCheck --&gt;|No| Rollback["Rollback to Previous Version"]
</code></pre>
<h2 id="heading-what-strategy-works-for-you">What Strategy Works for You?</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Team Size</strong></td><td><strong>Cost Sensitivity</strong></td><td><strong>Risk Tolerance</strong></td><td><strong>Recommendation</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Startup (1–5)</td><td>High</td><td>Moderate</td><td>Rolling updates + PM2</td></tr>
<tr>
<td>SMB (5–50)</td><td>Moderate</td><td>Moderate</td><td>Canary + feature flags</td></tr>
<tr>
<td>Enterprise (&gt;50)</td><td>Lower priority</td><td>Low</td><td>Blue-Green with CI/CD and InfrastructureAudits</td></tr>
</tbody>
</table>
</div><h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Zero-downtime isn’t a buzzword - it’s a competitive advantage.</p>
<p>For Node.js shops:</p>
<ul>
<li><p>Always implement graceful shutdown</p>
</li>
<li><p>Use load balancers with draining enabled</p>
</li>
<li><p>Automate your deployment with structured canary or blue-green release patterns</p>
</li>
<li><p>Monitor performance and rollback on performance regressions</p>
</li>
</ul>
<p>Deployments should be invisible to users not anxiety-inducing. Need help specifying a CI/CD pipeline or Kubernetes rollout script? I’d be happy to co-design it with you.</p>
]]></content:encoded></item><item><title><![CDATA[Master Docker in Real Projects: Compose + CI/CD + Best Practices]]></title><description><![CDATA[With real-world Docker Compose setups, GitHub Actions CI pipelines, and practical command recipes.

Everything you need to master Docker to Compose & CI/CD pipelines — from dev to deployment — in one place.

Docker Fundamentals
Whether you're debuggi...]]></description><link>https://blog.faizahmed.in/master-docker-compose-cicd</link><guid isPermaLink="true">https://blog.faizahmed.in/master-docker-compose-cicd</guid><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[CI/CD]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[Docker compose]]></category><category><![CDATA[Backend Engineering]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[software development]]></category><category><![CDATA[containers]]></category><category><![CDATA[webdev]]></category><category><![CDATA[phpmyadmin]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Wed, 23 Jul 2025 08:52:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/HSACbYjZsqQ/upload/6998f2d5998e3826cbec21201bb895fd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With real-world Docker Compose setups, GitHub Actions CI pipelines, and practical command recipes.</p>
<blockquote>
<p>Everything you need to master Docker to Compose &amp; CI/CD pipelines — from dev to deployment — in one place.</p>
</blockquote>
<h1 id="heading-docker-fundamentals">Docker Fundamentals</h1>
<p>Whether you're debugging a container, cleaning up images, or building complex multi-stage setups, this is your one-stop command reference.</p>
<h2 id="heading-getting-started-with-docker">Getting Started with Docker</h2>
<h3 id="heading-check-docker-version">Check Docker Version</h3>
<pre><code class="lang-bash">docker --version
docker version
</code></pre>
<h3 id="heading-check-docker-info">Check Docker Info</h3>
<pre><code class="lang-bash">docker info
</code></pre>
<h2 id="heading-working-with-images">Working with Images</h2>
<h3 id="heading-pull-an-image">Pull an Image</h3>
<pre><code class="lang-bash">docker pull ubuntu:22.04
</code></pre>
<h3 id="heading-list-images">List Images</h3>
<pre><code class="lang-bash">docker images
</code></pre>
<h3 id="heading-remove-image">Remove Image</h3>
<pre><code class="lang-bash">docker rmi ubuntu:22.04
</code></pre>
<h3 id="heading-build-image-from-dockerfile">Build Image from Dockerfile</h3>
<pre><code class="lang-bash">docker build -t my-app-image .
</code></pre>
<h3 id="heading-tag-image">Tag Image</h3>
<pre><code class="lang-bash">docker tag my-app-image myrepo/my-app-image:latest
</code></pre>
<h3 id="heading-push-image-to-registry">Push Image to Registry</h3>
<pre><code class="lang-bash">docker push myrepo/my-app-image:latest
</code></pre>
<h2 id="heading-working-with-containers">Working with Containers</h2>
<h3 id="heading-run-container">Run Container</h3>
<pre><code class="lang-bash">docker run -it ubuntu /bin/bash
docker run --name mycontainer -d nginx
</code></pre>
<h3 id="heading-list-running-containers">List Running Containers</h3>
<pre><code class="lang-bash">docker ps
</code></pre>
<h3 id="heading-list-all-containers-including-stopped">List All Containers (including stopped)</h3>
<pre><code class="lang-bash">docker ps -a
</code></pre>
<h3 id="heading-stop-container">Stop Container</h3>
<pre><code class="lang-bash">docker stop mycontainer
</code></pre>
<h3 id="heading-start-container">Start Container</h3>
<pre><code class="lang-bash">docker start mycontainer
</code></pre>
<h3 id="heading-remove-container">Remove Container</h3>
<pre><code class="lang-bash">docker rm mycontainer
</code></pre>
<h2 id="heading-debugging-amp-logs">Debugging &amp; Logs</h2>
<h3 id="heading-view-logs-of-a-container">View Logs of a Container</h3>
<pre><code class="lang-bash">docker logs mycontainer
</code></pre>
<h3 id="heading-attach-to-a-running-container">Attach to a Running Container</h3>
<pre><code class="lang-bash">docker attach mycontainer
</code></pre>
<h3 id="heading-exec-into-a-running-container">Exec into a Running Container</h3>
<pre><code class="lang-bash">docker <span class="hljs-built_in">exec</span> -it mycontainer /bin/bash
</code></pre>
<h2 id="heading-cleaning-up">Cleaning Up</h2>
<h3 id="heading-remove-all-stopped-containers">Remove All Stopped Containers</h3>
<pre><code class="lang-bash">docker container prune
</code></pre>
<h3 id="heading-remove-all-unused-images">Remove All Unused Images</h3>
<pre><code class="lang-bash">docker image prune
</code></pre>
<h3 id="heading-remove-everything-careful">Remove Everything (Careful!)</h3>
<pre><code class="lang-bash">docker system prune -a
</code></pre>
<h2 id="heading-networking">Networking</h2>
<h3 id="heading-list-networks">List Networks</h3>
<pre><code class="lang-bash">docker network ls
</code></pre>
<h3 id="heading-create-network">Create Network</h3>
<pre><code class="lang-bash">docker network create my-network
</code></pre>
<h3 id="heading-run-container-with-network">Run Container with Network</h3>
<pre><code class="lang-bash">docker run --network=my-network nginx
</code></pre>
<h2 id="heading-volumes-amp-persistence">Volumes &amp; Persistence</h2>
<h3 id="heading-list-volumes">List Volumes</h3>
<pre><code class="lang-bash">docker volume ls
</code></pre>
<h3 id="heading-create-volume">Create Volume</h3>
<pre><code class="lang-bash">docker volume create my-volume
</code></pre>
<h3 id="heading-run-with-volume">Run with Volume</h3>
<pre><code class="lang-bash">docker run -v my-volume:/app/data nginx
</code></pre>
<h3 id="heading-bind-mount-local-directory">Bind Mount Local Directory</h3>
<pre><code class="lang-bash">docker run -v $(<span class="hljs-built_in">pwd</span>)/config:/app/config nginx
</code></pre>
<h2 id="heading-advanced-usage">Advanced Usage</h2>
<h3 id="heading-multi-stage-build-dockerfile">Multi-Stage Build (Dockerfile)</h3>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span> AS builder
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install &amp;&amp; npm run build</span>

<span class="hljs-keyword">FROM</span> nginx:alpine
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/dist /usr/share/nginx/html</span>
</code></pre>
<h3 id="heading-build-with-build-args">Build with Build Args</h3>
<pre><code class="lang-bash">docker build --build-arg VERSION=1.0 -t myapp .
</code></pre>
<h2 id="heading-monitoring-amp-stats">Monitoring &amp; Stats</h2>
<h3 id="heading-view-container-stats">View Container Stats</h3>
<pre><code class="lang-bash">docker stats
</code></pre>
<h3 id="heading-inspect-container">Inspect Container</h3>
<pre><code class="lang-bash">docker inspect mycontainer
</code></pre>
<h2 id="heading-tldr-most-useful-everyday-commands">TL;DR: Most Useful Everyday Commands</h2>
<pre><code class="lang-bash">docker ps -a              <span class="hljs-comment"># List all containers</span>
docker images             <span class="hljs-comment"># List all images</span>
docker logs &lt;id&gt;          <span class="hljs-comment"># Logs from container</span>
docker <span class="hljs-built_in">exec</span> -it &lt;id&gt; bash <span class="hljs-comment"># Shell into container</span>
docker rm &lt;id&gt;            <span class="hljs-comment"># Remove container</span>
docker rmi &lt;id&gt;           <span class="hljs-comment"># Remove image</span>
</code></pre>
<h1 id="heading-from-docker-to-compose">From Docker to Compose</h1>
<blockquote>
<p>Now we are going for Scaling Multi-Container Apps…!</p>
</blockquote>
<h2 id="heading-what-is-docker-compose">What is Docker Compose?</h2>
<p>Docker Compose is a tool for defining and running multi-container Docker applications. You define services, networks, and volumes in a single YAML file (<code>docker-compose.yml</code>).</p>
<p>It simplifies local development, staging, testing, and even production deployments for small- to mid-size apps.</p>
<h2 id="heading-basic-docker-composeyml-structure">Basic <code>docker-compose.yml</code> Structure</h2>
<pre><code class="lang-yaml"><span class="hljs-string">docker-compose.yml</span>

<span class="hljs-attr">version:</span> <span class="hljs-string">'3.8'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">NODE_ENV=development</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/app</span>
  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">redis:alpine</span>
</code></pre>
<h3 id="heading-key-options">Key Options —</h3>
<ul>
<li><p><strong>build</strong>: Context for Dockerfile</p>
</li>
<li><p><strong>ports</strong>: Map host:container ports</p>
</li>
<li><p><strong>environment</strong>: Inject ENV vars</p>
</li>
<li><p><strong>volumes</strong>: Sync host and container files</p>
</li>
</ul>
<h2 id="heading-real-world-docker-compose-examples">Real-World Docker Compose Examples</h2>
<h3 id="heading-1-nodejs-app-mongodb">1. <strong>Node.js App + MongoDB</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.8'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">backend:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">./backend</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4000:4000"</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">mongo</span>
  <span class="hljs-attr">mongo:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mongo</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">mongo_data:/data/db</span>
<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">mongo_data:</span>
</code></pre>
<h3 id="heading-2-nextjs-redis-mysql-nginx-phpmyadmin">2. <strong>Next.js + Redis + MySQL + Nginx + PHPMyAdmin</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.9'</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">./app</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db</span>

  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mysql:8.0</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MYSQL_ROOT_PASSWORD:</span> <span class="hljs-string">root</span>
      <span class="hljs-attr">MYSQL_DATABASE:</span> <span class="hljs-string">myapp</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">mysql_data:/var/lib/mysql</span>

  <span class="hljs-attr">phpmyadmin:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">phpmyadmin/phpmyadmin</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8081:80"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">PMA_HOST:</span> <span class="hljs-string">db</span>
      <span class="hljs-attr">MYSQL_ROOT_PASSWORD:</span> <span class="hljs-string">root</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db</span>

  <span class="hljs-attr">nginx:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8080:80"</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">mysql_data:</span>
</code></pre>
<h2 id="heading-useful-commands">Useful Commands</h2>
<pre><code class="lang-bash"><span class="hljs-comment"># Start all services</span>
docker-compose up

<span class="hljs-comment"># Rebuild on changes</span>
docker-compose up --build

<span class="hljs-comment"># Start in background (detached mode)</span>
docker-compose up -d

<span class="hljs-comment"># Stop all services</span>
docker-compose down

<span class="hljs-comment"># List running containers</span>
docker-compose ps

<span class="hljs-comment"># Run a one-off command</span>
docker-compose <span class="hljs-built_in">exec</span> web sh
</code></pre>
<h1 id="heading-cicd-with-docker">CI/CD with Docker</h1>
<h2 id="heading-using-github-actions-example">Using GitHub Actions Example</h2>
<p>Let’s automate builds + tests with GitHub Actions.</p>
<h3 id="heading-githubworkflowsdockeryml">📁 <code>.github/workflows/docker.yml</code></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Docker</span> <span class="hljs-string">CI</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Docker</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">docker/setup-buildx-action@v2</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Docker</span> <span class="hljs-string">image</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">docker</span> <span class="hljs-string">build</span> <span class="hljs-string">-t</span> <span class="hljs-string">myapp</span> <span class="hljs-string">.</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">tests</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">docker</span> <span class="hljs-string">run</span> <span class="hljs-string">myapp</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>
</code></pre>
<h1 id="heading-cicd-with-docker-compose">CI/CD with Docker Compose</h1>
<h2 id="heading-in-github-actions">In GitHub Actions</h2>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Compose</span> <span class="hljs-string">CI</span>
<span class="hljs-attr">on:</span> [<span class="hljs-string">push</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">services:</span>
      <span class="hljs-attr">docker:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">docker:20.10.16</span>

    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Compose</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        sudo curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
        sudo chmod +x /usr/local/bin/docker-compose
</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Integration</span> <span class="hljs-string">Tests</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        docker-compose up -d
        docker-compose exec web npm test</span>
</code></pre>
<h2 id="heading-bonus-secrets-in-docker-compose">Bonus: Secrets in Docker Compose</h2>
<pre><code class="lang-yaml">  <span class="hljs-attr">environment:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PASSWORD=${DB_PASSWORD}</span>
</code></pre>
<p>Set in <code>.env</code> file or CI/CD secrets manager.</p>
<h2 id="heading-best-practices-recap">Best Practices Recap</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Area</strong></td><td><strong>Tip</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Build Caching</strong></td><td>Use <code>.dockerignore</code> + multistage builds</td></tr>
<tr>
<td><strong>Secrets</strong></td><td>Avoid hardcoding in YAML, use ENV or secrets manager</td></tr>
<tr>
<td><strong>Clean Up</strong></td><td><code>docker-compose down -v</code> to remove volumes too</td></tr>
<tr>
<td><strong>Logs</strong></td><td>Use <code>docker-compose logs -f</code> for debugging</td></tr>
<tr>
<td><strong>Healthchecks</strong></td><td>Add to services for better orchestration</td></tr>
</tbody>
</table>
</div>]]></content:encoded></item><item><title><![CDATA[API Observability: Logs, Traces, Metrics with OpenTelemetry]]></title><description><![CDATA[Observability isn't just about logs anymore.
In 2025, understanding how your API behaves under load, in production, across services is critical.
That’s where OpenTelemetry shines.

Unified logs, traces, and metrics

Framework-agnostic setup

Vendor-n...]]></description><link>https://blog.faizahmed.in/api-observability-logs-traces-metrics-with-opentelemetry</link><guid isPermaLink="true">https://blog.faizahmed.in/api-observability-logs-traces-metrics-with-opentelemetry</guid><category><![CDATA[Node.js]]></category><category><![CDATA[OpenTelemetry]]></category><category><![CDATA[observability]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Backend Development]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[Devops]]></category><category><![CDATA[jaeger]]></category><category><![CDATA[#prometheus]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[distributed systems]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[logging]]></category><category><![CDATA[tracing]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Tue, 15 Jul 2025 11:30:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/9xawGnFXfhs/upload/829a1f0594863632cd3a1f2d5825dd37.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Observability isn't just about logs anymore.</strong></p>
<p>In 2025, understanding how your API <em>behaves</em> under load, in production, across services is critical.</p>
<p>That’s where <strong>OpenTelemetry</strong> shines.</p>
<ul>
<li><p>Unified logs, traces, and metrics</p>
</li>
<li><p>Framework-agnostic setup</p>
</li>
<li><p>Vendor-neutral exporting (Prometheus, Jaeger, Datadog...)</p>
</li>
</ul>
<h2 id="heading-why-observability-matters">Why Observability Matters</h2>
<p>Your API might return a 200 OK. But is it slow? Is it retrying upstream services? Did something silently fail inside a nested call? Observability helps answer these questions.</p>
<p>Unlike simple logging or error tracking, observability enables a <em>holistic understanding</em> of your system's behavior, across multiple dimensions:</p>
<ul>
<li><p><strong>Logs</strong>: Human-readable event history</p>
</li>
<li><p><strong>Traces</strong>: Distributed timing across systems</p>
</li>
<li><p><strong>Metrics</strong>: Numeric signals to track system health</p>
</li>
</ul>
<h2 id="heading-introducing-opentelemetry">Introducing OpenTelemetry</h2>
<p><a target="_blank" href="https://opentelemetry.io/">OpenTelemetry</a> is an open standard for collecting telemetry data (logs, traces, metrics) from your application, and exporting them to any backend (e.g. Jaeger, Prometheus, New Relic, AWS X-Ray).</p>
<p>It’s supported across all major languages, and backed by CNCF.</p>
<h2 id="heading-core-concepts">Core Concepts</h2>
<ul>
<li><p><strong>Tracer</strong>: Creates spans (units of work) with timing context</p>
</li>
<li><p><strong>Span</strong>: A single operation with a start &amp; end time, metadata, and optional parent span</p>
</li>
<li><p><strong>Context Propagation</strong>: Tracks request across services</p>
</li>
<li><p><strong>Exporter</strong>: Sends data to backends</p>
</li>
</ul>
<h2 id="heading-setup-opentelemetry-in-nodejs">Setup OpenTelemetry in Node.js</h2>
<h3 id="heading-1-install-required-packages">1. Install Required Packages</h3>
<pre><code class="lang-bash">npm install @opentelemetry/api \
  @opentelemetry/sdk-node \
  @opentelemetry/instrumentation-http \
  @opentelemetry/instrumentation-express \
  @opentelemetry/exporter-trace-otlp-http
</code></pre>
<h3 id="heading-2-configure-sdk">2. Configure SDK</h3>
<pre><code class="lang-javascript"><span class="hljs-comment">// tracing.js</span>
<span class="hljs-keyword">const</span> { NodeSDK } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@opentelemetry/sdk-node'</span>);
<span class="hljs-keyword">const</span> { OTLPTraceExporter } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@opentelemetry/exporter-trace-otlp-http'</span>);
<span class="hljs-keyword">const</span> { ExpressInstrumentation } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@opentelemetry/instrumentation-express'</span>);
<span class="hljs-keyword">const</span> { HttpInstrumentation } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@opentelemetry/instrumentation-http'</span>);

<span class="hljs-keyword">const</span> sdk = <span class="hljs-keyword">new</span> NodeSDK({
  <span class="hljs-attr">traceExporter</span>: <span class="hljs-keyword">new</span> OTLPTraceExporter({
    <span class="hljs-attr">url</span>: <span class="hljs-string">'http://localhost:4318/v1/traces'</span>,
  }),
  <span class="hljs-attr">instrumentations</span>: [<span class="hljs-keyword">new</span> HttpInstrumentation(), <span class="hljs-keyword">new</span> ExpressInstrumentation()],
});

sdk.start();
</code></pre>
<h3 id="heading-3-start-tracing-early">3. Start Tracing Early</h3>
<pre><code class="lang-javascript"><span class="hljs-comment">// index.js</span>
<span class="hljs-built_in">require</span>(<span class="hljs-string">'./tracing'</span>);

<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();

app.get(<span class="hljs-string">'/hello'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">'Hello, observability!'</span>);
});

app.listen(<span class="hljs-number">3000</span>);
</code></pre>
<h2 id="heading-visualizing-traces">Visualizing Traces</h2>
<pre><code class="lang-mermaid">graph TD
A[Client Request] --&gt; B[Express Route Handler]
B --&gt; C[Middleware Span]
C --&gt; D[External HTTP Request Span]
D --&gt; E[Database Query Span]
</code></pre>
<p>Each span can include custom metadata (e.g. <code>user.id</code>, <code>status</code>, <code>retryCount</code>).</p>
<h2 id="heading-exporting-to-a-backend">Exporting to a Backend</h2>
<ul>
<li><p>Use <strong>Jaeger</strong> or <strong>Tempo</strong> for tracing</p>
</li>
<li><p><strong>Prometheus + Grafana</strong> for metrics</p>
</li>
<li><p>Combine logs via <strong>OTel Log API</strong> or sidecars</p>
</li>
</ul>
<p>You can also stream all to observability platforms like:</p>
<ul>
<li><p>Grafana Cloud</p>
</li>
<li><p>Honeycomb</p>
</li>
<li><p>Datadog</p>
</li>
<li><p>AWS X-Ray / CloudWatch</p>
</li>
</ul>
<h2 id="heading-pro-tips">Pro Tips</h2>
<ul>
<li><p>Sample aggressively in dev, reduce volume in prod</p>
</li>
<li><p>Use semantic conventions (<code>http.route</code>, <code>db.statement</code>, etc.)</p>
</li>
<li><p>Avoid sensitive data in span attributes</p>
</li>
<li><p>Always isolate observability config (e.g. <code>tracing.js</code>)</p>
</li>
<li><p>Propagate context in HTTP headers for distributed systems</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>OpenTelemetry gives you the power to see into your API's behavior without vendor lock-in. Whether you're debugging latency or investigating failed requests, logs alone aren't enough.</p>
<p>Start with tracing, and evolve into full observability.</p>
]]></content:encoded></item><item><title><![CDATA[How to Architect Multi-Tenant SaaS Backends in 2025 (PostgreSQL + Node.js)]]></title><description><![CDATA[In the world of SaaS, designing for scale starts with multi-tenancy done right.
Why Multi-Tenant Architecture Matters
SaaS is all about shared infrastructure. Whether you’re serving 5 customers or 5,000, you need to:

Isolate customer data securely

...]]></description><link>https://blog.faizahmed.in/architect-multi-tenant-saas-backends-in-2025-postgresql-nodejs</link><guid isPermaLink="true">https://blog.faizahmed.in/architect-multi-tenant-saas-backends-in-2025-postgresql-nodejs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[#multitenancy]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[tech leadership]]></category><category><![CDATA[devtools]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Express]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Fri, 11 Jul 2025 11:30:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/IrWAiBKmqcM/upload/dd4a393940f25567cd8a3f6e067b54e3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the world of SaaS, designing for scale starts with multi-tenancy done right.</p>
<h2 id="heading-why-multi-tenant-architecture-matters">Why Multi-Tenant Architecture Matters</h2>
<p>SaaS is all about shared infrastructure. Whether you’re serving 5 customers or 5,000, you need to:</p>
<ul>
<li><p>Isolate customer data securely</p>
</li>
<li><p>Scale efficiently without ballooning costs</p>
</li>
<li><p>Keep deployments sane</p>
</li>
</ul>
<p>Multi-tenancy lets you do all of that - <em>when designed correctly</em>.</p>
<h2 id="heading-multi-tenancy-models-which-one">Multi-Tenancy Models: Which One?</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Model</strong></td><td><strong>Description</strong></td><td><strong>Pros</strong></td><td><strong>Cons</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Shared DB, Shared Tables</strong></td><td>All tenants share the same schema</td><td>Easiest to manage</td><td>Risk of accidental leakage, hard to scale</td></tr>
<tr>
<td><strong>Shared DB, Separate Schemas</strong></td><td>One DB, one schema per tenant</td><td>Good balance of isolation and performance</td><td>Slightly more infra overhead</td></tr>
<tr>
<td><strong>Separate DB per Tenant</strong></td><td>One DB per tenant</td><td>Max isolation and flexibility</td><td>Complex infra, costly at scale</td></tr>
</tbody>
</table>
</div><p><strong>We’ll focus on schema-based multi-tenancy</strong>, it’s a sweet spot for SaaS products targeting SMBs to mid-market.</p>
<h2 id="heading-postgresql-multi-tenancy-strategy">PostgreSQL Multi-Tenancy Strategy</h2>
<h3 id="heading-schema-per-tenant">Schema-Per-Tenant</h3>
<p>Each customer gets their own schema:</p>
<pre><code class="lang-pgsql"><span class="hljs-built_in">public</span>.users        → shared
tenant_abc.orders   → tenant-specific
tenant_xyz.orders   → tenant-specific
</code></pre>
<h3 id="heading-security-with-rls">Security with RLS</h3>
<p>Use <strong>Row-Level Security (RLS)</strong> in public/shared tables:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">POLICY</span> tenant_isolation
  <span class="hljs-keyword">ON</span> <span class="hljs-built_in">public</span>.users
  <span class="hljs-keyword">USING</span> (tenant_id = current_setting(<span class="hljs-string">'app.current_tenant'</span>));
</code></pre>
<p>And set this in every DB connection via middleware:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SET</span> app.current_tenant = <span class="hljs-string">'tenant_abc'</span>;
</code></pre>
<h3 id="heading-connection-pooling-tips">Connection Pooling Tips —</h3>
<ul>
<li><p>Use <strong>pgbouncer</strong> in transaction pooling mode</p>
</li>
<li><p>For AWS, consider <strong>RDS Proxy</strong> with connection pinning (for long sessions)</p>
</li>
</ul>
<h2 id="heading-backend-design-with-nodejs">Backend Design with Node.js</h2>
<h3 id="heading-middleware-based-tenant-resolution">Middleware-Based Tenant Resolution</h3>
<pre><code class="lang-javascript">app.use(<span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> subdomain = req.hostname.split(<span class="hljs-string">'.'</span>)[<span class="hljs-number">0</span>];
  req.tenantId = resolveTenant(subdomain); <span class="hljs-comment">// resolve to `tenant_abc`</span>
  next();
});
</code></pre>
<h3 id="heading-request-scoped-db-access">Request-Scoped DB Access</h3>
<p>Using a <code>dbForTenant(tenantId)</code> helper to inject the right connection:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> db = dbForTenant(req.tenantId);
<span class="hljs-keyword">const</span> orders = <span class="hljs-keyword">await</span> db(<span class="hljs-string">'orders'</span>).select(<span class="hljs-string">'*'</span>);
</code></pre>
<p>Or with ORMs like Prisma or MikroORM:</p>
<ul>
<li><p>Generate client instances dynamically</p>
</li>
<li><p>Use schema switch or connection pool keying</p>
</li>
</ul>
<h2 id="heading-infra-amp-operational-considerations">Infra &amp; Operational Considerations</h2>
<ul>
<li><p><strong>Migrations per schema</strong> using tools like <a target="_blank" href="https://github.com/salsita/node-pg-migrate">node-pg-migrate</a></p>
</li>
<li><p><strong>Backup strategies</strong> to snapshot all schemas</p>
</li>
<li><p><strong>Rate limiting</strong> by tenant key</p>
</li>
<li><p><strong>Audit logging</strong> per tenant using middleware hooks</p>
</li>
</ul>
<h2 id="heading-end-to-end-flow">End-to-End Flow</h2>
<pre><code class="lang-mermaid">flowchart TD
  A[Incoming Request: customer.abc.app.com] --&gt; B[Middleware Resolves Tenant ID]
  B --&gt; C[Set DB schema search_path to tenant_abc]
  C --&gt; D[Run Queries against tenant_abc.orders]
  D --&gt; E[Send Response Back]
</code></pre>
<h2 id="heading-real-world-lessons">Real-World Lessons</h2>
<ul>
<li><p>Avoid hardcoding tenant logic — use context-injected data</p>
</li>
<li><p>Monitor schema bloat over time</p>
</li>
<li><p>Start with fewer shared tables — easier to migrate than split later</p>
</li>
<li><p>Automate tenant provisioning (create schema + seed data)</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Multi-tenancy isn’t a feature, it’s a <em>foundation</em>.</p>
<p>With PostgreSQL schemas and clean Node.js abstractions, you can scale from one tenant to thousands <em>without rewriting your stack</em>.</p>
<h3 id="heading-got-questions-about-your-saas-backend">Got questions about your SaaS backend?</h3>
<p>Drop a comment or DM me.</p>
<p>Always happy to help troubleshoot, scale, or design multi-tenant platforms with you.</p>
]]></content:encoded></item><item><title><![CDATA[Packaging Node.js Libraries the Right Way: ESM, CommonJS, and Bundlers in 2025]]></title><description><![CDATA[Why Packaging Still Breaks in 2025?
Despite being two decades into Node.js, publishing a library that works across CommonJS (CJS), ECMAScript Modules (ESM), TypeScript, and bundlers is still confusing.
Why? Because:

Node.js evolved from CJS to ESM g...]]></description><link>https://blog.faizahmed.in/packaging-nodejs-libraries-in-2025</link><guid isPermaLink="true">https://blog.faizahmed.in/packaging-nodejs-libraries-in-2025</guid><category><![CDATA[Node.js]]></category><category><![CDATA[npm]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[esm]]></category><category><![CDATA[commonjs]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[rollup]]></category><category><![CDATA[tsup]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[devtools]]></category><category><![CDATA[Backend Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[npm packages]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Wed, 09 Jul 2025 11:30:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xKggi7Gtfs4/upload/9e89d14b81a95146b13b2573ea1a18e0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-why-packaging-still-breaks-in-2025">Why Packaging Still Breaks in 2025?</h2>
<p>Despite being two decades into Node.js, <strong>publishing a library that works across CommonJS (CJS), ECMAScript Modules (ESM), TypeScript, and bundlers is still confusing</strong>.</p>
<p>Why? Because:</p>
<ul>
<li><p>Node.js evolved from CJS to ESM gradually</p>
</li>
<li><p>Bundlers (Vite, Webpack, Rollup) behave differently</p>
</li>
<li><p>Developers mix TS, <code>.mjs</code>, <code>.cjs</code>, and <code>.js</code> in weird ways</p>
</li>
<li><p>Package consumers have wildly different expectations</p>
</li>
</ul>
<p>So let’s break it down - cleanly and practically - to ship libraries that don’t break.</p>
<h2 id="heading-commonjs-vs-esm-know-the-difference">CommonJS vs ESM: Know the Difference</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Feature</strong></td><td><strong>CommonJS</strong></td><td><strong>ECMAScript Modules (ESM)</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Import syntax</td><td><code>require()</code></td><td><code>import</code> / <code>export</code></td></tr>
<tr>
<td>Export syntax</td><td><code>module.exports</code> / <code>exports</code></td><td><code>export default</code> / <code>export</code></td></tr>
<tr>
<td>File extension</td><td><code>.js</code> (default)</td><td><code>.mjs</code> or <code>"type": "module"</code></td></tr>
<tr>
<td>Synchronous</td><td>Yes</td><td>No (top-level <code>await</code> allowed)</td></tr>
<tr>
<td>Used in</td><td>Legacy Node.js, most tooling</td><td>Modern projects, Deno, browser-native</td></tr>
</tbody>
</table>
</div><p><strong>Interop Problems</strong>:</p>
<ul>
<li><p>ESM can't <code>require()</code> CJS modules directly if they use <code>module.exports</code></p>
</li>
<li><p>CJS can't use <code>import</code> unless you compile it</p>
</li>
<li><p>Some bundlers resolve <code>"main"</code> and <code>"module"</code> fields inconsistently</p>
</li>
</ul>
<h2 id="heading-packagejson-fields-that-matter">package.json Fields That Matter</h2>
<p>Your <code>package.json</code> tells the outside world <strong>how to load your code</strong>. Here's what matters:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"my-lib"</span>,
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"dist/index.cjs"</span>,      <span class="hljs-comment">// CommonJS entry</span>
  <span class="hljs-attr">"module"</span>: <span class="hljs-string">"dist/index.esm.js"</span>, <span class="hljs-comment">// ESM entry for bundlers</span>
  <span class="hljs-attr">"exports"</span>: {
    <span class="hljs-attr">"."</span>: {
      <span class="hljs-attr">"require"</span>: <span class="hljs-string">"./dist/index.cjs"</span>,
      <span class="hljs-attr">"import"</span>: <span class="hljs-string">"./dist/index.esm.js"</span>
    }
  },
  <span class="hljs-attr">"types"</span>: <span class="hljs-string">"dist/index.d.ts"</span>
}
</code></pre>
<h3 id="heading-field-breakdown">Field Breakdown:</h3>
<ul>
<li><p><code>"main"</code>: default for Node (CommonJS)</p>
</li>
<li><p><code>"module"</code>: used by bundlers like Rollup, Vite (ESM)</p>
</li>
<li><p><code>"exports"</code>: modern standard to declare per-import conditions</p>
</li>
<li><p><code>"types"</code>: entry point for TypeScript declarations</p>
</li>
</ul>
<h2 id="heading-dual-publishing-support-cjs-esm">Dual Publishing: Support CJS + ESM</h2>
<p>If you want your library to work in <strong>both ecosystems</strong>, publish both builds.</p>
<h3 id="heading-example">📂 Example:</h3>
<pre><code class="lang-bash">/dist
  ├── index.cjs      ← CommonJS
  ├── index.esm.js   ← ES Module
  ├── index.d.ts     ← TypeScript types
</code></pre>
<p>Use a bundler like <code>tsup</code>, <code>rollup</code>, or <code>unbuild</code> to output both formats cleanly:</p>
<pre><code class="lang-bash">tsup src/index.ts --format cjs,esm --dts
</code></pre>
<h2 id="heading-should-you-pre-bundle">Should You Pre-Bundle?</h2>
<p>✅ Pre-bundling is helpful when:</p>
<ul>
<li><p>You ship multiple files</p>
</li>
<li><p>You use dependencies that might break interop</p>
</li>
<li><p>You want fast cold-starts or browser builds</p>
</li>
</ul>
<p>❌ Don’t pre-bundle if:</p>
<ul>
<li><p>Your library is dead simple (1 file)</p>
</li>
<li><p>You want tree-shaking to be fully consumer-controlled</p>
</li>
</ul>
<p>👉 If unsure, use <a target="_blank" href="https://tsup.egoist.dev/">tsup</a> — it’s fast, ESM-aware, and minimal config.</p>
<h2 id="heading-testing-your-package-as-a-consumer">Testing Your Package as a Consumer</h2>
<p>Test it <strong>as your users would</strong>:</p>
<ul>
<li><p>Import into ESM-only projects</p>
</li>
<li><p>Require from legacy CommonJS</p>
</li>
<li><p>Build with <code>vite</code>, <code>webpack</code>, <code>rollup</code>, and even <code>node --experimental-loader</code></p>
</li>
</ul>
<p>Use real-world tools like:</p>
<pre><code class="lang-bash">npm init vite@latest
npm i your-lib
<span class="hljs-comment"># Try importing and running</span>
</code></pre>
<h2 id="heading-common-pitfalls">Common Pitfalls</h2>
<ul>
<li><p>❌ Top-level <code>await</code> in ESM without proper Node flag</p>
</li>
<li><p>❌ Missing <code>"exports"</code> field breaks bundlers</p>
</li>
<li><p>❌ Default exports in CJS confuse ESM importers</p>
</li>
<li><p>❌ Mixing <code>.js</code>, <code>.ts</code>, <code>.mjs</code>, <code>.cjs</code> without clear build strategy</p>
</li>
</ul>
<h2 id="heading-bonus-when-to-use-cjs-and-mjs">Bonus: When to Use <code>.cjs</code> and <code>.mjs</code></h2>
<p>If your <code>package.json</code> does <strong>not</strong> specify <code>"type": "module"</code>:</p>
<ul>
<li><p>Use <code>.cjs</code> for CommonJS</p>
</li>
<li><p>Use <code>.mjs</code> for ESM</p>
</li>
</ul>
<p>If it <strong>does</strong> specify:</p>
<pre><code class="lang-json"><span class="hljs-string">"type"</span>: <span class="hljs-string">"module"</span>
</code></pre>
<p>Then:</p>
<ul>
<li><p>Use <code>.js</code> for ESM</p>
</li>
<li><p>Use <code>.cjs</code> for legacy interop (only when needed)</p>
</li>
</ul>
<h2 id="heading-packaging-workflow">Packaging Workflow</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752130833999/29da699e-9728-42ec-b90d-9bdbcb65874f.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-summary-ship-libraries-that-just-work">Summary: Ship Libraries That Just Work</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>What to do</strong></td><td><strong>Why it matters</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Build both CJS + ESM</td><td>Maximum compatibility</td></tr>
<tr>
<td>Use <code>exports</code> in <code>package.json</code></td><td>Precise interop, future-proofing</td></tr>
<tr>
<td>Include <code>.d.ts</code> types</td><td>TypeScript + IntelliSense friendly</td></tr>
<tr>
<td>Test across environments</td><td>No surprises for consumers</td></tr>
</tbody>
</table>
</div><h2 id="heading-want-examples-or-a-minimal-boilerplate">Want Examples or a Minimal Boilerplate?</h2>
<p>I’ve skipped code/config samples here intentionally.</p>
<p><strong>Got questions or stuck on a setup?</strong> Drop them in the comments — happy to help with real-world fixes.</p>
]]></content:encoded></item><item><title><![CDATA[A Practical Guide to AWS API Gateway]]></title><description><![CDATA[This guide walks you through everything from routing and stage management to Lambda proxies, VPC links, and CORS.
What Is AWS API Gateway?
AWS API Gateway is a fully managed service that allows you to define, secure, monitor, and route HTTP(S) reques...]]></description><link>https://blog.faizahmed.in/aws-api-gateway</link><guid isPermaLink="true">https://blog.faizahmed.in/aws-api-gateway</guid><category><![CDATA[vpc-link]]></category><category><![CDATA[AWS]]></category><category><![CDATA[API Gateway]]></category><category><![CDATA[serverless]]></category><category><![CDATA[Devops]]></category><category><![CDATA[lambda]]></category><category><![CDATA[ScalableSolutions]]></category><category><![CDATA[cloud architecture]]></category><dc:creator><![CDATA[Faiz Ahmed Farooqui]]></dc:creator><pubDate>Wed, 02 Jul 2025 11:30:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756648000304/43d50a3e-74a9-4aa1-bd79-3bb46c6b9323.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This guide walks you through everything from routing and stage management to Lambda proxies, VPC links, and CORS.</p>
<h2 id="heading-what-is-aws-api-gateway">What Is AWS API Gateway?</h2>
<p><strong>AWS API Gateway</strong> is a fully managed service that allows you to define, secure, monitor, and route HTTP(S) requests to backend services like:</p>
<ul>
<li><p>AWS Lambda functions</p>
</li>
<li><p>Internal microservices over VPC links</p>
</li>
<li><p>External URLs or legacy systems</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751441774744/f7575b49-4a06-4f6b-bc94-a57a5e3e2320.png" alt class="image--center mx-auto" /></p>
<p>You can think of it as:</p>
<blockquote>
<p><em>A programmable front door to your APIs, with built-in security, throttling, routing, logging, and environment separation.</em></p>
</blockquote>
<h2 id="heading-understanding-resources">Understanding Resources</h2>
<p>In API Gateway, <strong>resources</strong> are like RESTful paths. Think of it as a skeleton for your API Gateway’s Route Paths:</p>
<ul>
<li><p><code>/users</code></p>
</li>
<li><p><code>/users/{userId}</code></p>
</li>
<li><p><code>/orders/{orderId}</code></p>
</li>
</ul>
<p>Each resource can have multiple <strong>HTTP methods</strong> (GET, POST, PUT, etc.) defined independently.</p>
<p>Resources allow you to:</p>
<ul>
<li><p>Define <strong>proxy paths</strong> (<code>{proxy+}</code>) for catch-all routing</p>
</li>
<li><p>Attach different integrations (Lambda, VPC, mock) to different methods</p>
</li>
<li><p>Enable features like CORS per route</p>
</li>
</ul>
<h2 id="heading-stages-dev-staging-uat-prod">Stages: dev, staging, uat, prod</h2>
<p><strong>Stages</strong> are deployable versions of your API:</p>
<ul>
<li><p>Each stage is tied to a <strong>deployment snapshot</strong></p>
</li>
<li><p>URLs are prefixed with the stage name:<br />  <a target="_blank" href="https://abc123.execute-api.us-east-1.amazonaws.com/dev"><code>https://abc123.execute-api.us-east-1.amazonaws.com/dev</code></a></p>
</li>
</ul>
<p>Stages let you:</p>
<ul>
<li><p>Deploy to <strong>isolated environments</strong> (dev, prod)</p>
</li>
<li><p>Attach different <strong>logging levels</strong></p>
</li>
<li><p>Configure <strong>stage variables</strong> for dynamic routing</p>
</li>
</ul>
<h2 id="heading-authorizers">Authorizers</h2>
<p>Authorizers let you <strong>secure endpoints</strong> by validating tokens or identity headers before passing requests to your backend.</p>
<h3 id="heading-types">Types:</h3>
<ul>
<li><p><strong>Lambda Authorizer</strong>: custom logic (e.g., JWT parsing, RBAC)</p>
</li>
<li><p><strong>Cognito Authorizer</strong>: native integration with AWS Cognito user pools</p>
</li>
</ul>
<p>You can attach authorizers to:</p>
<ul>
<li><p>Specific HTTP methods</p>
</li>
<li><p>All routes under a resource (via inheritance)</p>
</li>
</ul>
<p>This helps enforce identity-aware access control without modifying backend code.</p>
<h2 id="heading-api-keys">API Keys</h2>
<p>API Keys in API Gateway are <strong>identifiers for clients</strong> — used mainly for:</p>
<ul>
<li><p><strong>Throttling individual clients</strong></p>
</li>
<li><p><strong>Monitoring usage per key</strong></p>
</li>
<li><p><strong>Enforcing quota limits</strong></p>
</li>
</ul>
<p>They are <strong>not a security mechanism</strong> (like OAuth or JWT). They're passed via the <code>x-api-key</code> header and must be tied to a <strong>Usage Plan</strong> to take effect.</p>
<h2 id="heading-usage-plans">Usage Plans</h2>
<p><strong>Usage Plans</strong> define:</p>
<ul>
<li><p>Rate limits (requests per second)</p>
</li>
<li><p>Burst limits (temporary spikes)</p>
</li>
<li><p>Quota (total requests per day/week/month)</p>
</li>
</ul>
<p>You associate:</p>
<ul>
<li><p>One or more <strong>API Keys</strong></p>
</li>
<li><p>One or more <strong>stages</strong></p>
</li>
<li><p>Optional throttling and quota</p>
</li>
</ul>
<p>This lets you manage free-tier access, premium clients, or internal vs public access from a single control plane.</p>
<h2 id="heading-binding-lambda-to-resource-routes-proxy-vs-non-proxy">Binding Lambda to Resource Routes (Proxy vs Non-Proxy)</h2>
<h3 id="heading-proxy-integration-awsproxy">Proxy Integration (AWS_PROXY)</h3>
<ul>
<li><p>Sends the <strong>entire HTTP request as-is</strong> to your Lambda function</p>
</li>
<li><p>Lambda receives event with:</p>
<ul>
<li>Headers, method, path, body, etc.</li>
</ul>
</li>
</ul>
<blockquote>
<p>Ideal for backend-for-frontend use cases or single-function APIs</p>
</blockquote>
<h3 id="heading-non-proxy-integration">Non-Proxy Integration</h3>
<ul>
<li><p>You must <strong>manually map</strong> request fields (method, body, query, etc.)</p>
</li>
<li><p>Allows <strong>fine-grained control</strong> but adds complexity</p>
</li>
</ul>
<blockquote>
<p>Use this when your backend expects a specific format or when integrating with legacy systems.</p>
</blockquote>
<h2 id="heading-binding-vpc-link-to-resource-routes-proxy-vs-non-proxy">Binding VPC Link to Resource Routes (Proxy vs Non-Proxy)</h2>
<p>Use <strong>VPC Link</strong> to expose internal services running in private subnets (behind an NLB).</p>
<h3 id="heading-proxy-integration-httpproxy">Proxy Integration (HTTP_PROXY)</h3>
<ul>
<li><p>Entire request is forwarded “as-is” to your internal service</p>
</li>
<li><p>Simplest option, minimal config</p>
</li>
</ul>
<h3 id="heading-non-proxy-integration-1">Non-Proxy Integration</h3>
<ul>
<li><p>Define request templates to transform data before forwarding</p>
</li>
<li><p>Useful when bridging REST to legacy backends that expect specific formats</p>
</li>
</ul>
<h2 id="heading-enabling-cors-cross-origin-resource-sharing">Enabling CORS (Cross-Origin Resource Sharing)</h2>
<p>CORS is required when:</p>
<ul>
<li>Your frontend (e.g., hosted on <a target="_blank" href="http://example.com"><code>example.com</code></a>) accesses an API on a different domain (e.g., <a target="_blank" href="http://api.example.com"><code>api.example.com</code></a>)</li>
</ul>
<h3 id="heading-to-enable-cors">To enable CORS:</h3>
<ul>
<li><p>Add an <strong>OPTIONS</strong> method to each route</p>
</li>
<li><p>Return headers like:</p>
<ul>
<li><p><code>Access-Control-Allow-Origin</code></p>
</li>
<li><p><code>Access-Control-Allow-Methods</code></p>
</li>
<li><p><code>Access-Control-Allow-Headers</code></p>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>You can enable this manually or use the console's <strong>Enable CORS</strong> feature — but make sure your responses <strong>return these headers dynamically</strong>, especially in error cases.</p>
</blockquote>
<h2 id="heading-stage-variables">Stage Variables</h2>
<p><strong>Stage Variables</strong> act like environment variables for API Gateway.</p>
<p>Use them to:</p>
<ul>
<li><p>Switch between Lambda aliases (<code>dev</code>, <code>prod</code>)</p>
</li>
<li><p>Configure endpoint URLs (for VPC links)</p>
</li>
<li><p>Inject environment-specific logic into integrations</p>
</li>
</ul>
<p>They’re accessible in:</p>
<ul>
<li><p>Integration targets as <code>${stageVariables.&lt;name&gt;}</code></p>
</li>
<li><p>Mapping Values as <code>stageVariables.&lt;name&gt;</code></p>
</li>
</ul>
<h2 id="heading-using-stage-variables-for-proxy-vpc-link-routing">Using Stage Variables for Proxy+ VPC Link Routing</h2>
<p>One powerful trick:</p>
<blockquote>
<p>Use a <strong>proxy route</strong> like <code>/internal/{proxy+}</code> and bind it to different <strong>VPC endpoints</strong> using stage variables.</p>
</blockquote>
<p>Example:</p>
<pre><code class="lang-plaintext">Integration URI: http://${stageVariables.vpcEndpoint}/{proxy}
</code></pre>
<p>Each stage (<code>dev</code>, <code>prod</code>, etc.) defines a different <code>vpcEndpoint</code> variable.</p>
<p>This makes things —</p>
<ul>
<li><p>Clean</p>
</li>
<li><p>Environment-aware</p>
</li>
<li><p>No code duplication</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>AWS API Gateway is incredibly flexible but only if you understand how its building blocks work together.</p>
<p>This post covers:</p>
<ul>
<li><p>Routing with Lambda or VPC</p>
</li>
<li><p>Security with Authorizers and API keys</p>
</li>
<li><p>Flexibility with Stage Variables</p>
</li>
<li><p>Browser Integration via CORS</p>
</li>
</ul>
<p>Want to see this in action with real configs or Terraform/CDK examples? <strong>Drop your questions in the comments — let’s build it out together.</strong></p>
]]></content:encoded></item></channel></rss>