<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nandan Kumar</title>
    <description>The latest articles on DEV Community by Nandan Kumar (@sirius93).</description>
    <link>https://dev.to/sirius93</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F175617%2Fffbce4aa-7b34-4bd8-837d-41da6d24ddeb.jpeg</url>
      <title>DEV Community: Nandan Kumar</title>
      <link>https://dev.to/sirius93</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sirius93"/>
    <language>en</language>
    <item>
      <title>Bulk-Adding Google Workspace Domain Aliases Without Service-Account Keys</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Sun, 16 Aug 2026 06:35:00 +0000</pubDate>
      <link>https://dev.to/sirius93/bulk-adding-google-workspace-domain-aliases-without-service-account-keys-234e</link>
      <guid>https://dev.to/sirius93/bulk-adding-google-workspace-domain-aliases-without-service-account-keys-234e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqu1vfaue9q5d5v2ypuo1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqu1vfaue9q5d5v2ypuo1.png" alt="Bulk-Adding Google Workspace Domain Aliases Banner" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Managing one domain in Google Workspace is straightforward. Managing ten, twenty, or more domains across several brands is where the process starts to hurt.&lt;/p&gt;

&lt;p&gt;For every domain, an administrator has to prove ownership, publish a DNS record, wait for propagation, return to Google Workspace, add the domain, configure mail routing, and verify the result. None of those steps is especially difficult. The problem is repetition — and repetition is where small mistakes become outages.&lt;/p&gt;

&lt;p&gt;I recently needed a better workflow for attaching a collection of brand domains to one Google Workspace account. The domains used GoDaddy DNS, and the intended behavior was simple: every existing Workspace user should receive the same username on every brand domain.&lt;/p&gt;

&lt;p&gt;That made the domains good candidates for Google Workspace user alias domains. It also made the process a good candidate for automation. The result is a CSV-driven Python CLI with three commands — &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt;, &lt;code&gt;verify&lt;/code&gt; — that connects the Google Workspace Admin SDK, the Site Verification API, and the GoDaddy DNS API, authenticates with OAuth instead of a service-account key, and treats every write as something to check for before doing. Code's open source at &lt;a href="https://github.com/sirius93/google-workspace-domain-alias-provisioner" rel="noopener noreferrer"&gt;github.com/sirius93/google-workspace-domain-alias-provisioner&lt;/a&gt;, link at the bottom too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with doing it manually
&lt;/h2&gt;

&lt;p&gt;The manual workflow usually looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open the Google Admin console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a domain alias.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy Google's verification token.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the DNS provider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the TXT record.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for DNS propagation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ask Google to verify the domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure MX records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeat for the next domain. Do this once and it feels harmless. Do it twenty times and several risks appear:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A domain is skipped.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A TXT value is copied incorrectly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The wrong parent domain is selected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Existing MX records are replaced accidentally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The spreadsheet and the real configuration drift apart.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nobody has a reliable record of what succeeded. The goal was not merely to make the process faster. The goal was to make it repeatable, inspectable, and safe to rerun.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Domain alias or secondary domain?
&lt;/h2&gt;

&lt;p&gt;Before automating anything, it is important to choose the correct Google Workspace domain model.&lt;/p&gt;

&lt;p&gt;A user alias domain gives existing users and groups equivalent addresses on another domain. If &lt;code&gt;brand.example&lt;/code&gt; is an alias of &lt;code&gt;company.example&lt;/code&gt;, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alex@company.example -&amp;gt; alex@brand.example
sales@company.example -&amp;gt; sales@brand.example

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

&lt;/div&gt;



&lt;p&gt;Both addresses point to the existing account or group. Users continue signing in with their primary address, and there is no separate mailbox.&lt;/p&gt;

&lt;p&gt;A secondary domain is different. It is intended for a separate collection of users whose primary accounts live on that domain. Secondary-domain users normally require their own Workspace licences.&lt;/p&gt;

&lt;p&gt;The automation described here is specifically for user alias domains. That keeps the mapping predictable: one parent Workspace domain, many brand domains, and matching usernames across all of them.&lt;/p&gt;

&lt;p&gt;Google documents the distinction and current domain limits in its multiple-domain guidance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning the domain list into a source of truth
&lt;/h2&gt;

&lt;p&gt;The first useful improvement is surprisingly mundane: put the desired state in a CSV file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;dns&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;provider&lt;/span&gt;
&lt;span class="k"&gt;brand&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;one&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;domain&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;company&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;godaddy&lt;/span&gt;
&lt;span class="k"&gt;brand&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;two&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;domain&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;company&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;godaddy&lt;/span&gt;
&lt;span class="k"&gt;brand&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;three&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;domain&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;company&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;godaddy&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This file becomes the input to every operation. It is easy to review, version, split into pilot batches, and compare with the actual environment.&lt;/p&gt;

&lt;p&gt;The script validates that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Every domain is unique.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every row has a parent domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The requested type is &lt;code&gt;domain_alias&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The DNS provider is supported. That validation catches boring mistakes before an API call turns them into an operational problem.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The automation flow
&lt;/h2&gt;

&lt;p&gt;The workflow connects three APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Google Site Verification API&lt;/strong&gt; generates a DNS verification token and confirms ownership.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GoDaddy Domains API v3&lt;/strong&gt; reads the zone and creates missing TXT or MX records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Google Workspace Admin SDK Directory API&lt;/strong&gt; creates and inspects domain aliases. For each CSV row, the tool performs this sequence:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Check whether the domain is already verified.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check whether the Workspace alias already exists.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read the current GoDaddy DNS records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Request a Google verification token when needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the token as an apex TXT record if it is missing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Poll public DNS until the token is visible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ask Google to verify ownership.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the Workspace domain alias under the configured parent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optionally add Google MX when — and only when — the domain has no MX records. The read-before-write behavior is essential. A rerun should continue an incomplete job, not create duplicates.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why the CLI has plan, apply, and verify
&lt;/h2&gt;

&lt;p&gt;Infrastructure tools are much easier to trust when inspection and mutation are separate operations.&lt;/p&gt;

&lt;p&gt;The CLI exposes three commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;python workspace_domain_aliases.py plan domains.csv
python workspace_domain_aliases.py apply domains.csv
python workspace_domain_aliases.py verify domains.csv

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  plan
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;plan&lt;/code&gt; is read-only. It checks Google and GoDaddy, then prints a compact summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DOMAIN DNS VERIFIED WORKSPACE MX
brand-one.example ok no missing 0
brand-two.example ok yes missing 5
brand-three.example ok no present 1

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

&lt;/div&gt;



&lt;p&gt;This answers the questions that matter before a change: Can the tool access the DNS zone? Does Google already recognize the owner? Does the Workspace alias already exist? Is mail already configured?&lt;/p&gt;

&lt;h3&gt;
  
  
  apply
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;apply&lt;/code&gt; creates missing ownership records, verifies domains, and creates missing Workspace aliases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;python workspace_domain_aliases.py apply domains.csv

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

&lt;/div&gt;



&lt;p&gt;DNS propagation is asynchronous, so the tool polls for the verification TXT record. If the record does not become visible before the timeout, the run reports the domain as pending. Nothing needs to be rolled back: wait, then rerun the same command.&lt;/p&gt;

&lt;h3&gt;
  
  
  verify
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;verify&lt;/code&gt; reconciles the CSV with the live state, checking ownership, alias presence, parent-domain association, and whether the domain has at least one MX record. A non-zero exit code makes it usable in a shell script or a future CI workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication without service-account keys
&lt;/h2&gt;

&lt;p&gt;My first instinct was to use a service account with domain-wide delegation. That is a common design for unattended Workspace administration, but the organization enforced this policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iam.managed.disableServiceAccountKeyCreation

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

&lt;/div&gt;



&lt;p&gt;That was not a problem to work around. It was a useful security boundary.&lt;/p&gt;

&lt;p&gt;Long-lived service-account JSON keys are portable credentials. If one leaks into a repository, backup, support ticket, or developer machine, it can be abused until revoked. Google recommends avoiding service-account keys when a safer authentication method fits the workload.&lt;/p&gt;

&lt;p&gt;For a locally operated administrative tool, interactive OAuth is a good match. The application uses an OAuth 2.0 Desktop app client and asks the administrator to authorize two scopes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.googleapis.com/auth/admin.directory.domain
https://www.googleapis.com/auth/siteverification

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

&lt;/div&gt;



&lt;p&gt;The Python flow is conceptually simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google_auth_oauthlib.flow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InstalledAppFlow&lt;/span&gt;

&lt;span class="n"&gt;scopes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/admin.directory.domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/siteverification&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InstalledAppFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_client_secrets_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oauth-client.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_local_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The first run opens a browser. The administrator signs in and approves access. The resulting refresh token is stored locally and reused on later runs.&lt;/p&gt;

&lt;p&gt;That token must still be treated like a password. It should never be committed, pasted into an issue, or stored beside public example files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to Google Workspace
&lt;/h2&gt;

&lt;p&gt;Once OAuth credentials are available, the Python client can construct both Google services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient.discovery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;

&lt;span class="n"&gt;directory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;directory_v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cache_discovery&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;siteVerification&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cache_discovery&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Creating a domain alias uses the Directory API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;domainAliases&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domainAliasName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brand.example&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parentDomainName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company.example&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Before inserting, the tool lists existing aliases and compares the live parent domain with the CSV. If an alias already exists under a different parent, the script stops instead of guessing how to repair it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating domain verification
&lt;/h2&gt;

&lt;p&gt;Google ownership verification has two distinct operations: obtain a token and verify the resource.&lt;/p&gt;

&lt;p&gt;The request describes an internet domain and asks for DNS verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;webResource&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;identifier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brand.example&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INET_DOMAIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verificationMethod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DNS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The token is published as an apex TXT record. Once it is visible in public DNS, the tool asks Google to verify the resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;webResource&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;verificationMethod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DNS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;identifier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brand.example&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INET_DOMAIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Keeping these steps separate makes DNS propagation explicit. A successful API response from the DNS provider does not mean every resolver can see the record immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding records through GoDaddy safely
&lt;/h2&gt;

&lt;p&gt;GoDaddy's v3 DNS API supports individual record creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /v3/domains/zones/{zone}/dns-records

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

&lt;/div&gt;



&lt;p&gt;A verification record looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TXT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google-site-verification=..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ttl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;The safe pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;List existing records of the relevant type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Normalize record names and values.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the record only when an exact match is absent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never replace the entire DNS zone. This matters because DNS APIs differ. Some "set records" methods replace every record with a matching name and type. Others can replace the whole zone. A provisioning tool should use the narrowest operation available.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GoDaddy also warns that record creation is not inherently idempotent: blindly replaying a failed POST can create duplicates. Reading the zone before each write significantly reduces that risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MX changes are opt-in
&lt;/h2&gt;

&lt;p&gt;Verification TXT records are additive and relatively low-risk. MX records are different: they control where inbound email is delivered.&lt;/p&gt;

&lt;p&gt;The tool therefore does not touch MX during the default &lt;code&gt;apply&lt;/code&gt; operation. Mail configuration requires an explicit flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;python workspace_domain_aliases.py apply domains.csv --add-google-mx

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

&lt;/div&gt;



&lt;p&gt;Even with the flag, the tool adds &lt;code&gt;smtp.google.com&lt;/code&gt; only when the domain has zero MX records. If any MX record already exists, it prints a warning and leaves the configuration untouched.&lt;/p&gt;

&lt;p&gt;That conservative decision handles several real-world cases: the domain already uses Google's older five-record MX configuration, the domain uses Google's newer single-record configuration, another mail provider is still active, or the domain has intentional routing or migration records.&lt;/p&gt;

&lt;p&gt;Automation should not interpret "different from my preferred configuration" as "safe to overwrite."&lt;/p&gt;

&lt;p&gt;SPF, DKIM, and DMARC are also intentionally outside the first version. Each requires knowledge of the organization's complete mail-sending environment. For example, adding a second SPF record can make SPF invalid rather than more secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making reruns safe
&lt;/h2&gt;

&lt;p&gt;Bulk administrative work rarely completes in one flawless pass. DNS might be slow, one domain might belong to another account, an API can rate-limit a request, or an administrator might pause the rollout.&lt;/p&gt;

&lt;p&gt;The tool is designed so that rerunning &lt;code&gt;apply&lt;/code&gt; is normal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Existing TXT token: skip creation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Already verified domain: skip verification.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Existing Workspace alias with correct parent: skip insertion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Existing MX records: leave unchanged.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pending DNS: retry later. This is the practical meaning of idempotency for the workflow. The entire run may involve APIs whose individual POST operations are not idempotent, but the program checks the current state before deciding whether a write is necessary.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A rollout strategy that minimizes surprises
&lt;/h2&gt;

&lt;p&gt;For a real environment, I would use the following sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Export or back up every DNS zone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put one non-critical domain in a pilot CSV.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;plan&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inspect any existing MX records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;apply&lt;/code&gt; without the MX flag.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm the alias in the Google Admin console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test receiving mail after the approved MX change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm outbound authentication separately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Expand to small batches.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;verify&lt;/code&gt; and keep the output with the change record. The pilot is not ceremony. It confirms that the OAuth project, administrator privileges, Workspace tenant, GoDaddy account, authoritative nameservers, and API behavior all match the assumptions in the code.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I would add next
&lt;/h2&gt;

&lt;p&gt;The first release solves one narrow problem well. Natural next steps include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DNS-provider adapters for Cloudflare, Route 53, TransIP, and others&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secondary-domain support&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JSON output for CI and audit systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unit tests with mocked API responses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explicit DNS backup and rollback workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SPF, DKIM, and DMARC validation without automatic enforcement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A keyless workload-identity design for approved unattended execution Any future provider should preserve the same safety contract: read before write, create only what is missing, never replace a zone implicitly, and require explicit intent for destructive actions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;p&gt;Three ideas mattered more than the API calls themselves.&lt;/p&gt;

&lt;p&gt;First, model the desired state before writing code. The CSV is small, but it turns an informal checklist into something reviewable and repeatable.&lt;/p&gt;

&lt;p&gt;Second, treat security controls as design input. A blocked service-account key did not require disabling an organization policy. It led to an authentication flow better suited to a human-operated CLI.&lt;/p&gt;

&lt;p&gt;Third, make dangerous behavior inconvenient. The tool can automate MX creation, but only through an explicit flag, and it refuses to modify existing mail routing. A little friction is valuable when a command can affect production email.&lt;/p&gt;

&lt;p&gt;That's usually the sweet spot for internal automation. I've open sourced it — if you're wiring up your own multi-brand Workspace setup, the CLI and the client wrappers are there to fork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/sirius93/ggwdap" rel="noopener noreferrer"&gt;github.com/sirius93/ggwdap&lt;/a&gt; — plan/apply/verify CLI, CSV schema, and the Google + GoDaddy client wrappers described above.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google Workspace Admin SDK Directory API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google Site Verification API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google Directory API Python quickstart&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google Workspace multiple-domain guidance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GoDaddy DNS API guide&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google Cloud authentication guidance&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This project is not affiliated with or endorsed by Google or GoDaddy. DNS and mail-routing changes can disrupt production services; test with a non-critical domain and keep current DNS backups.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>googleworkspace</category>
      <category>python</category>
      <category>dns</category>
      <category>godaddy</category>
    </item>
    <item>
      <title>How I Moved My Google AI Studio App to GitHub CI</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Sun, 12 Jul 2026 05:59:58 +0000</pubDate>
      <link>https://dev.to/sirius93/how-i-moved-my-google-ai-studio-app-to-github-ci-2a4k</link>
      <guid>https://dev.to/sirius93/how-i-moved-my-google-ai-studio-app-to-github-ci-2a4k</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frmyx89bodvb62f1cju59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frmyx89bodvb62f1cju59.png" alt="Blog banner" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kitaaben started life in Google AI Studio. No shame in that — it's genuinely the fastest way I know to get a full-stack app with Firebase wired up and live. But "fast to start" and "where your code should live forever" are two different things.&lt;/p&gt;

&lt;p&gt;I wanted GitHub to be the source of truth. Real commit history. Real pull requests. A deploy pipeline I could reason about without a proprietary UI sitting in the middle. The service was already live on Cloud Run, so the plan was simple: point it at the GitHub repo and be done by lunch.&lt;/p&gt;

&lt;p&gt;It took the whole afternoon. Just not the afternoon I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plan that should have worked
&lt;/h2&gt;

&lt;p&gt;Cloud Run has a built-in feature for exactly this — &lt;em&gt;Continuously deploy from a repository&lt;/em&gt;. Connect a GitHub repo, pick a branch, and it auto-generates a Cloud Build trigger that builds and deploys on every push. No YAML. Click, connect, push.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snag 1.&lt;/strong&gt; The very first build failed, but for a boring reason. My repo had two lockfiles — &lt;code&gt;package-lock.json&lt;/code&gt; and a stray &lt;code&gt;pnpm-lock.yaml&lt;/code&gt; that got committed by accident at some point. Google's Buildpacks saw the pnpm lockfile and built with pnpm. And recent pnpm versions block dependency install scripts by default as a supply-chain protection. So esbuild's postinstall script (the one that downloads its platform binary) never ran, and the build died before it even reached my code.&lt;/p&gt;

&lt;p&gt;Fix: delete the stray lockfile. The project was never pnpm-based anyway. Quick, forgettable, not the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error that refused to explain itself
&lt;/h2&gt;

&lt;p&gt;Next push got further — all the way to the deploy step — and then this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: (gcloud.run.services.update)
spec.template.metadata.annotations[run.googleapis.com/sources]:
Source annotation has sources that are not referenced by a container.

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

&lt;/div&gt;



&lt;p&gt;Read that sentence a few times. Nothing in it tells you what to do. No stack trace, no file, no line number. Just a Cloud Run validator rejecting a deploy against its own service definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong theory #1: strip the annotation
&lt;/h3&gt;

&lt;p&gt;The obvious read — some stale annotation is sitting on the live service, left over from however AI Studio originally provisioned it. So just remove it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud run services update kitaaben &lt;span class="nt"&gt;--region&lt;/span&gt; us-west1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--remove-annotations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;run.googleapis.com/sources

ERROR: unrecognized arguments: &lt;span class="nt"&gt;--remove-annotations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;run.googleapis.com/sources
&lt;span class="o"&gt;(&lt;/span&gt;did you mean &lt;span class="s1"&gt;'--remove-env-vars'&lt;/span&gt;?&lt;span class="o"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Turns out &lt;code&gt;gcloud run services update&lt;/code&gt; has no generic "edit any annotation" flag. Most of Cloud Run's raw Knative-style metadata isn't meant to be hand-edited through convenience flags — precisely to stop people from doing what I was about to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong theory #2: ditch Buildpacks entirely
&lt;/h3&gt;

&lt;p&gt;New theory. The &lt;code&gt;run.googleapis.com/sources&lt;/code&gt; annotation is part of Cloud Run's source-deploy tracking — it maps source paths to containers so the console can show "this revision was built from commit X." A plain prebuilt Docker image needs none of that. So: write a Dockerfile, sidestep Buildpacks and its source-tracking machinery completely.&lt;/p&gt;

&lt;p&gt;I wrote a multi-stage Dockerfile. Built it locally, ran it, confirmed it booted and served traffic. Pushed.&lt;/p&gt;

&lt;p&gt;And this is where it got genuinely confusing. The build went green. The live site didn't change.&lt;/p&gt;

&lt;p&gt;The build log explained why. Adding a Dockerfile made Cloud Build quietly switch strategies. Instead of the four-step Buildpacks template it had been using (build → pull → push → deploy), it fell back to Google's generic "there's a Dockerfile here, build and push an image" default, which has &lt;strong&gt;no deploy step at all&lt;/strong&gt;. Green build. No deploy. Nobody tells you.&lt;/p&gt;

&lt;p&gt;Fix for this part: a handwritten &lt;code&gt;cloudbuild.yaml&lt;/code&gt; with three explicit steps. &lt;code&gt;docker build&lt;/code&gt;, &lt;code&gt;docker push&lt;/code&gt;, &lt;code&gt;gcloud run deploy --image&lt;/code&gt;. Three commands, in order, in the repo. No more guessing what the pipeline does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Same error, completely different deploy method
&lt;/h3&gt;

&lt;p&gt;With a real deploy step finally in place, I pushed again — and got the &lt;em&gt;exact same annotation error&lt;/em&gt;. This time from a plain &lt;code&gt;gcloud run deploy --image&lt;/code&gt; call that had nothing to do with source builds or Buildpacks at all.&lt;/p&gt;

&lt;p&gt;That was the moment the real shape of the problem clicked. It was never about &lt;em&gt;how&lt;/em&gt; I was deploying. Buildpacks, Dockerfile, source-based, image-based — didn't matter. Something was wrong with the live service's own persisted state, and every deploy method was stepping on the same landmine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exporting the body
&lt;/h2&gt;

&lt;p&gt;Time to look at what the service actually thought it was running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud run services describe kitaaben &lt;span class="nt"&gt;--region&lt;/span&gt; us-west1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; service.yaml

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

&lt;/div&gt;



&lt;p&gt;Buried in that YAML: the container's &lt;code&gt;image&lt;/code&gt; field wasn't a real image reference. It was the literal string &lt;code&gt;scratch&lt;/code&gt; — Cloud Run's internal placeholder for services that resolve their actual container indirectly, through that same &lt;code&gt;sources&lt;/code&gt; annotation I'd been fighting all afternoon.&lt;/p&gt;

&lt;p&gt;The service was &lt;em&gt;born&lt;/em&gt; this way. AI Studio provisioned it through its own source-deploy pipeline on day one, and it had been carrying that placeholder ever since. A ghost container, quietly broken since birth, and it only mattered the day I tried to deploy any other way.&lt;/p&gt;

&lt;p&gt;So every attempt was failing one of two ways: either the deploy tried to update the sources annotation and Cloud Run rejected the mismatch, or the leftover &lt;code&gt;scratch&lt;/code&gt; placeholder had nothing left to resolve against.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual fix
&lt;/h2&gt;

&lt;p&gt;Fix both in one edit, apply atomically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# in service.yaml:&lt;/span&gt;
&lt;span class="c"&gt;# 1. delete the run.googleapis.com/sources annotation line&lt;/span&gt;
&lt;span class="c"&gt;# 2. change image: scratch to the real image path&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud run services replace service.yaml
Applying new configuration to Cloud Run service &lt;span class="o"&gt;[&lt;/span&gt;kitaaben]...
Deploying... &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That was it. Not a flag. Not a build strategy. Not a YAML rewrite of the pipeline. A genuinely stuck field in the service's own state that no amount of retrying — from any deploy method — was ever going to clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're doing this migration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If AI Studio (or any source-deploy tool) originally provisioned your Cloud Run service, don't assume a clean deploy method inherits a clean service. &lt;code&gt;describe --format export&lt;/code&gt; and &lt;em&gt;read the spec&lt;/em&gt; before you spend a day guessing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;run.googleapis.com/sources&lt;/code&gt; and &lt;code&gt;image: scratch&lt;/code&gt; travel together. See one, check for the other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cloud Build silently changes strategy based on what's in your repo. Dockerfile present vs absent produces different pipelines. If a build goes green but nothing deploys, check whether the deploy step still exists.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not every Cloud Run field has a convenience flag. When &lt;code&gt;--remove-X&lt;/code&gt; doesn't exist, the fallback is always: export the spec, hand-edit, &lt;code&gt;services replace&lt;/code&gt;. Haven't touched the pipeline since. It's a Cloud Build trigger watching &lt;code&gt;main&lt;/code&gt;, building a Docker image, deploying it. No mystery annotations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub is the source of truth now. AI Studio is where &lt;a href="https://kitaaben.com" rel="noopener noreferrer"&gt;Kitaaben&lt;/a&gt; was born, not where it lives.&lt;/p&gt;

&lt;p&gt;That’s all, folks! I hope you found this helpful. If you enjoyed this, check out more articles on my Blog, &lt;a href="https://blog.nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;https://blog.nandan.dev/&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to comment, email me at &lt;a href="mailto:connect@nandan.dev"&gt;&lt;strong&gt;connect@nandan.dev&lt;/strong&gt;&lt;/a&gt;, or connect with me on Twitter, Instagram, or GitHub. Don’t forget to subscribe to my newsletter for regular updates on JavaScript topics!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/_sirius93_" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://www.instagram.com/nandandotdev" rel="noopener noreferrer"&gt;&lt;strong&gt;Instagram&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/sirius93" rel="noopener noreferrer"&gt;&lt;strong&gt;Github&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>googleaistudio</category>
      <category>cicd</category>
      <category>book</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Give it a read.</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Thu, 25 Jun 2026 05:13:21 +0000</pubDate>
      <link>https://dev.to/sirius93/give-it-a-read-3ndl</link>
      <guid>https://dev.to/sirius93/give-it-a-read-3ndl</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/sirius93/i-got-tired-of-goodreads-so-i-built-kitaabencom-1dp3" class="crayons-story__hidden-navigation-link"&gt;I Got Tired of Goodreads. So I Built Kitaaben.com&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/sirius93" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F175617%2Fffbce4aa-7b34-4bd8-837d-41da6d24ddeb.jpeg" alt="sirius93 profile" class="crayons-avatar__image" width="720" height="720"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/sirius93" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nandan Kumar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nandan Kumar
                
              
              &lt;div id="story-author-preview-content-3981556" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/sirius93" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F175617%2Fffbce4aa-7b34-4bd8-837d-41da6d24ddeb.jpeg" class="crayons-avatar__image" alt="" width="720" height="720"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nandan Kumar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/sirius93/i-got-tired-of-goodreads-so-i-built-kitaabencom-1dp3" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/sirius93/i-got-tired-of-goodreads-so-i-built-kitaabencom-1dp3" id="article-link-3981556"&gt;
          I Got Tired of Goodreads. So I Built Kitaaben.com
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/books"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;books&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/bookreview"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;bookreview&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/bookstagram"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;bookstagram&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/sirius93/i-got-tired-of-goodreads-so-i-built-kitaabencom-1dp3#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>I Got Tired of Goodreads. So I Built Kitaaben.com</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Wed, 24 Jun 2026 15:57:55 +0000</pubDate>
      <link>https://dev.to/sirius93/i-got-tired-of-goodreads-so-i-built-kitaabencom-1dp3</link>
      <guid>https://dev.to/sirius93/i-got-tired-of-goodreads-so-i-built-kitaabencom-1dp3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuxtx25gz6p8lecrv17po.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuxtx25gz6p8lecrv17po.png" alt="Kitaaben Banner" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I read a lot. Not as much as I'd like to, but enough that I needed a place to keep track of it all.&lt;/p&gt;

&lt;p&gt;For years, Goodreads was that place. Then Amazon bought it in 2013 and... nothing happened. No meaningful update. No design refresh. No new features worth talking about. Just a bloated, slow, social-feed-first app that cared more about what your friends were reading than what you actually wanted from a reading tracker.&lt;/p&gt;

&lt;p&gt;So I stopped waiting and built my own. It's called &lt;a href="https://kitaaben.com" rel="noopener noreferrer"&gt;Kitaaben&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Even Is Kitaaben?
&lt;/h2&gt;

&lt;p&gt;Kitaaben is a private bookshelf tracker. That's it, honestly.&lt;/p&gt;

&lt;p&gt;You sign in with Google, add your books, and start tracking. No followers, no public profile, no algorithm telling you what to read next. Just your books, organized the way you want them.&lt;/p&gt;

&lt;p&gt;I built it as a React SPA, backed by Firestore for real-time sync and Google OAuth for auth. It works as a PWA too, so you can install it directly on your phone or desktop and it behaves like a native app. Even logs notes offline and syncs when you're back online.&lt;/p&gt;

&lt;p&gt;The stack is deliberately simple. I didn't want to over-engineer a reading tracker.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj6bq8x6dwv39gaw0bn33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj6bq8x6dwv39gaw0bn33.png" alt="Kitaaben feature banner" width="799" height="422"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Features That Actually Matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reading Progress Tracking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can log how many pages you've read, mark a book as "Reading", "To Read", or "Completed", and watch your progress bar move. Small thing, weirdly satisfying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yearly Reading Goals&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set a target at the start of the year. Kitaaben shows you a clean gauge of where you stand. No gamification, no streaks, no push notifications guilt-tripping you into reading. Just the number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI Review Assistant&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one is my favourite. You know how you finish a book, you have all these raw thoughts, and then you sit down to write a review, and your mind goes blank?&lt;/p&gt;

&lt;p&gt;Kitaaben fixes that. You log rough chapter notes as you read. When you're done, one click and the AI turns your messy notes into a proper, readable review. It uses Gemini under the hood, server-side, so nothing weird is happening with your data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beautiful Bookshelf UI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can pick colour palettes for your book spines, attach local photos, or link directly to publisher dust jackets. Your shelf actually looks good. Goodreads shelf looks like it was designed in 2009 because it was.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloud Sync + Offline PWA&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything syncs in real-time to Firestore. Works across your phone, tablet, and desktop. Add notes on a flight, they'll sync when you land.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your Data Is Yours&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No corporate sponsors. No ads. No one is analysing your reading habits to sell you something. The free tier is genuinely free, forever. If you want AI reviews and premium themes, there's an Elite plan, but the core is completely free.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kitaaben vs Goodreads — Let's Be Honest
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Goodreads&lt;/th&gt;
&lt;th&gt;Kitaaben&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Last major update&lt;/td&gt;
&lt;td&gt;Years ago&lt;/td&gt;
&lt;td&gt;Actively built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Social feed&lt;/td&gt;
&lt;td&gt;Yes, and you can't remove it&lt;/td&gt;
&lt;td&gt;No. Just your books.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ads&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data privacy&lt;/td&gt;
&lt;td&gt;Amazon owns everything&lt;/td&gt;
&lt;td&gt;Private, encrypted, yours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI reviews&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline support&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (PWA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design&lt;/td&gt;
&lt;td&gt;Dated&lt;/td&gt;
&lt;td&gt;Clean, warm, actually nice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free tier&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Goodreads isn't bad because it was badly built. It's bad because it stopped being built. Amazon has no incentive to fix it. It already has 150 million users who have nowhere to go.&lt;/p&gt;

&lt;p&gt;Kitaaben is built for readers who are tired of that and just want something that works, looks good, and doesn't treat your reading life as a data point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Is It For?
&lt;/h2&gt;

&lt;p&gt;Honestly? People like me.&lt;/p&gt;

&lt;p&gt;If you read regularly and want a clean private place to track it, Kitaaben will feel like a breath of fresh air. If you rely on Goodreads mainly for its social features — seeing what friends are reading, community reviews, that sort of thing — Kitaaben isn't trying to replace that. It's not a social network. It never will be.&lt;/p&gt;

&lt;p&gt;It's a personal reading sanctuary. That's the whole point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kitaaben.com" rel="noopener noreferrer"&gt;kitaaben.com&lt;/a&gt; — Sign in with Google, takes about 60 seconds to set up.&lt;/p&gt;

&lt;p&gt;If you have feedback, brutal or otherwise, reach me at &lt;a href="mailto:namastay@kitaaben.com"&gt;namastay@kitaaben.com&lt;/a&gt; or find me on Twitter. I'm building this in the open and I actually read every message.&lt;/p&gt;

&lt;p&gt;Thanks, Nandan&lt;/p&gt;

</description>
      <category>books</category>
      <category>bookreview</category>
      <category>bookstagram</category>
    </item>
    <item>
      <title>How to Convert Nested Objects to Flat Objects in JavaScript.</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Wed, 04 Mar 2026 00:59:34 +0000</pubDate>
      <link>https://dev.to/sirius93/how-to-convert-nested-objects-to-flat-objects-in-javascript-4ld1</link>
      <guid>https://dev.to/sirius93/how-to-convert-nested-objects-to-flat-objects-in-javascript-4ld1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcf5xrd3qtgxlc09mdhh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcf5xrd3qtgxlc09mdhh4.png" alt="How to Convert Nested Objects to Flat Objects in JavaScript" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am sure you have been asked this question in one of the interviews.&lt;/p&gt;

&lt;p&gt;It looks simple at first. But the interviewer is actually checking whether you understand recursion, objects, and how JavaScript treats arrays.&lt;/p&gt;

&lt;p&gt;So basically, you are given a nested object, and you are expected to simplify it by removing the nesting and printing it in a non-nested format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here is a simple input and output :
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;b_c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;b_d_e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Now look at the solution :
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Loop through every key in the object&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// Skip properties coming from prototype chain&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// If prefix exists, append current key using _&lt;/span&gt;
        &lt;span class="c1"&gt;// Otherwise just use the key itself&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;objKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`\({prefix}_\){key}`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// If value is an object and not null,&lt;/span&gt;
        &lt;span class="c1"&gt;// recursively flatten it&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// If it's a primitive value, assign it directly&lt;/span&gt;
            &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;objKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Return the accumulated result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Let us test it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// {a: 1, b_c: 2, b_d_e_0: 1, b_d_e_1: 2 }&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now observe something interesting.&lt;/p&gt;

&lt;p&gt;It flattens the nested object correctly.&lt;/p&gt;

&lt;p&gt;But it also destructures the array and adds indexes as keys.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why do you think that happened?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;As you know, everything in JavaScript is an object. Even &lt;strong&gt;Arrays&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So when we check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Arrays also satisfy that condition.&lt;/p&gt;

&lt;p&gt;That means our recursive call is going inside arrays as well, and treating indexes (0, 1) like object keys.&lt;/p&gt;

&lt;p&gt;That is why we are getting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;b_d_e_0: 1
b_d_e_1: 2

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;So, how do we fix it?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Simple.&lt;/p&gt;

&lt;p&gt;We add one more condition to ignore Arrays.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// Ensure we are working only with object's own properties&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;objKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`\({prefix}_\){key}`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// Only recurse if:&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. value is an object&lt;/span&gt;
        &lt;span class="c1"&gt;// 2. value is not null&lt;/span&gt;
        &lt;span class="c1"&gt;// 3. value is NOT an array&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Directly assign primitives and arrays&lt;/span&gt;
            &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;objKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Testing Both Cases&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// { a: 1, b_c: 2, b_d_e: [1, 2] }&lt;/span&gt;
&lt;span class="c1"&gt;// { a: 1, b_c: 2, b_d_e: 3 }&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That is it.&lt;/p&gt;

&lt;p&gt;A simple recursion problem. But it tests whether you truly understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How objects work&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How arrays behave internally&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How recursion accumulates results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And how small edge cases can break your logic&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why interviewers love this question.&lt;/p&gt;

&lt;p&gt;That’s all, folks! I hope you found this helpful. If you enjoyed this, check out more articles on my Blog, &lt;a href="https://blog.nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;https://blog.nandan.dev/&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to comment, email me at &lt;a href="mailto:connect@nandan.dev"&gt;&lt;strong&gt;connect@nandan.dev&lt;/strong&gt;&lt;/a&gt;, or connect with me on Twitter, Instagram, or GitHub. Don’t forget to subscribe to my newsletter for regular updates on JavaScript topics!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/_sirius93_" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://www.instagram.com/nandandotdev" rel="noopener noreferrer"&gt;&lt;strong&gt;Instagram&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/sirius93" rel="noopener noreferrer"&gt;&lt;strong&gt;Github&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>objects</category>
      <category>json</category>
      <category>jsinterview</category>
    </item>
    <item>
      <title>Built something over last weekend, thought I will share with my dev community. https://bailoutt.com</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Tue, 23 Sep 2025 16:11:02 +0000</pubDate>
      <link>https://dev.to/sirius93/built-something-over-last-weekend-thought-i-will-share-with-my-dev-community-1aki</link>
      <guid>https://dev.to/sirius93/built-something-over-last-weekend-thought-i-will-share-with-my-dev-community-1aki</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://bailoutt.com/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fframerusercontent.com%2Fassets%2Fbx2KHyDlhJQAxsQUXODShrsUz28.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://bailoutt.com/" rel="noopener noreferrer" class="c-link"&gt;
            BailOutt |  Escape Awkward Situations | AI Fake Call Service | Social Anxiety Solution
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Never get trapped in awkward situations again. Get real, believable phone calls from real numbers when you need them most. BailOutt’s AI-powered fake call service helps you escape bad dates, long meetings, and social anxiety with ease.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fframerusercontent.com%2Fimages%2FR7ZW2oxiKgijD3384CiQcMp0KN4.jpg"&gt;
          bailoutt.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>HTML Pitfalls and Gotchas Every Developer Should Avoid</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Fri, 05 Sep 2025 04:23:19 +0000</pubDate>
      <link>https://dev.to/sirius93/html-pitfalls-and-gotchas-every-developer-should-avoid-51em</link>
      <guid>https://dev.to/sirius93/html-pitfalls-and-gotchas-every-developer-should-avoid-51em</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hwe05rdp4iuytc1xcq5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hwe05rdp4iuytc1xcq5.png" alt="HTML pitfalls Banner" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;HTML is easy, said every developer ever.&lt;/p&gt;

&lt;p&gt;HTML is deceptively simple. You open a tag, you close a tag, and something appears on screen. But beneath that simplicity lurk gotchas that can silently break your layout, wreck your SEO, or make your site look unprofessional when shared on social media.&lt;/p&gt;

&lt;p&gt;In this world of frameworks and libraries, its easy to miss some of the basic stuff that needs to be taken care of in your webpage, because your frameworks boilerplate code adds up most of the stuff and takes care of the basics. But its always good to know some of them that are important or arent added by your framework by default.&lt;/p&gt;

&lt;p&gt;Here are the critical HTML pitfalls to avoid and how to fix them immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Forgetting the DOCTYPE Declaration
&lt;/h2&gt;

&lt;p&gt;Skipping the doctype triggers something called as the &lt;strong&gt;quirks mode&lt;/strong&gt; , where browsers start behaving like it's 1999. This results in inconsistent CSS rendering and unpredictable JavaScript behaviour across browsers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always declare it at the very top of your HTML file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Missing the Character Encoding Meta Tag
&lt;/h2&gt;

&lt;p&gt;Without proper character encoding, special characters turn into garbled symbols i.e. (). This breaks accented letters, smart quotes, and emojis, and it makes your webpage content look broken and unprofessional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Place this inside the&lt;/strong&gt; &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta charset="UTF-8"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Forgetting the Viewport Meta Tag
&lt;/h2&gt;

&lt;p&gt;Has it ever happened to you? Your page zooms awkwardly when users double-tap on mobile; Thats because you're missing the viewport meta tag. Without it, your site isn't truly responsive, regardless of your CSS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add this to your&lt;/strong&gt; &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; &lt;strong&gt;section&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Skipping Open Graph Meta Tags
&lt;/h2&gt;

&lt;p&gt;Ever shared your blog on LinkedIn, Twitter, or Facebook only to see a bare URL with no preview card? That's because Open Graph tags are missing. These tags control how your content appears when shared on social media.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add these OG tags for rich sharing previews:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta property="og:title" content="My Blog Title"&amp;gt;
&amp;lt;meta property="og:description" content="A quick guide to avoiding HTML gotchas."&amp;gt;
&amp;lt;meta property="og:image" content="https://example.com/preview.png"&amp;gt;
&amp;lt;meta property="og:url" content="https://example.com/blog"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Also consider adding Twitter Card tags for optimal Twitter/X previews.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Ignoring Content Security Policy
&lt;/h2&gt;

&lt;p&gt;Content Security Policy (CSP) helps prevent cross-site scripting (XSS) attacks and other code injection vulnerabilities. It tells browsers which scripts, styles, and resources are allowed to run on your page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with a basic CSP meta tag:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline';"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is a starting point. Adjust the policy based on your specific needs and third-party resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Improper Nesting of Elements
&lt;/h2&gt;

&lt;p&gt;Browsers are forgiving, but invalid HTML nesting leads to unpredictable rendering and can break JavaScript that relies on the DOM structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common nesting mistakes:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- Inline elements shouldn't contain block elements --&amp;gt;
&amp;lt;span&amp;gt;&amp;lt;div&amp;gt;Wrong!&amp;lt;/div&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;!-- Paragraphs can't contain divs --&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;div&amp;gt;Also wrong!&amp;lt;/div&amp;gt;&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Follow these nesting rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inline elements (&lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt;) should &lt;strong&gt;never&lt;/strong&gt; contain block-level elements (&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; Tags should only wrap inline content, never block-level elements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When in doubt, check the HTML specification for content models&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;These six HTML gotchas might seem minor, but they're the difference between a professional website and one that breaks in subtle, embarrassing ways. Take five minutes to audit your HTML templatesyour future self (and your users) will thank you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick checklist for your next project:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DOCTYPE declaration at the top&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UTF-8 character encoding&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Viewport meta tag for mobile&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open Graph tags for social sharing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Basic Content Security Policy&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Valid HTML nesting&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember: browsers are forgiving, but that doesn't mean we should be sloppy. Clean HTML is the foundation of a reliable web experience.&lt;/p&gt;

&lt;p&gt;Thats all, folks! I hope you found this helpful. If you enjoyed this, check out more articles on my Blog, &lt;a href="https://blog.nandan.dev/" rel="noopener noreferrer"&gt;https://blog.nandan.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to comment, email me at &lt;a href="http://mailto:connect@nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;connect@nandan.dev&lt;/strong&gt;&lt;/a&gt;, or connect with me on Twitter, Instagram, or GitHub. Dont forget to subscribe to my newsletter for regular updates on JavaScript topics!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/_sirius93_" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://www.instagram.com/nandandotdev" rel="noopener noreferrer"&gt;&lt;strong&gt;Instagram&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/sirius93" rel="noopener noreferrer"&gt;&lt;strong&gt;Github&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>metatags</category>
      <category>gotchas</category>
    </item>
    <item>
      <title>Accessibility in Front End Development</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Sat, 09 Aug 2025 16:57:50 +0000</pubDate>
      <link>https://dev.to/sirius93/accessibility-in-front-end-development-3l9i</link>
      <guid>https://dev.to/sirius93/accessibility-in-front-end-development-3l9i</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq08tgra31o0z10u8zsr5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq08tgra31o0z10u8zsr5.png" alt="a11y Heading image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me start with this: Accessibility is not a buzzword; its a crucial feature that every web application should have.&lt;/p&gt;

&lt;p&gt;It should not be mandated by the government, but rather it should be taken as the responsibility of every developer to make their websites accessible to all users.&lt;/p&gt;

&lt;p&gt;Making a website accessible will not just help you avoid government penalties, it will also give you a wider user base. Accessibility is all about making your website accessible to everyone.&lt;/p&gt;

&lt;p&gt;On that note, lets get this blog started -&lt;/p&gt;

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

&lt;p&gt;Accessibility is ensuring that your web application is usable by people with disabilities, including people with&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Visual impairment (Blindness, Colour Blindness) -&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hearing loss&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Motor Impairment ( Difficulty using the mouse)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cognitive limitation (Dyslexia, Attention Disorder)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accessibility Core Principle (POUR)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Principle&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;P&lt;/strong&gt;erceivable&lt;/td&gt;
&lt;td&gt;Content must be perceivable by all (e.g., alt text, captions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;O&lt;/strong&gt;perable&lt;/td&gt;
&lt;td&gt;All functionality must work via keyboard or assistive devices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;U&lt;/strong&gt;nderstandable&lt;/td&gt;
&lt;td&gt;Content should be readable and predictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;R&lt;/strong&gt;obust&lt;/td&gt;
&lt;td&gt;Must work with various assistive technologies&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How can people with disabilities use the applications
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Accessibility Need&lt;/th&gt;
&lt;th&gt;Requirement / Support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Visual Impairment&lt;/td&gt;
&lt;td&gt;Should be usable with screen readers, VoiceOver, and font scaling support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hearing Loss&lt;/td&gt;
&lt;td&gt;Closed captions for video and audio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motor Impairment&lt;/td&gt;
&lt;td&gt;Keyboard accessibility support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cognitive Limitation&lt;/td&gt;
&lt;td&gt;Simple language&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Accessibility Standards
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web Content Accessibility Guidelines (WCAG)
&lt;/h3&gt;

&lt;p&gt;Defines Accessibility levels as &lt;strong&gt;A&lt;/strong&gt; , &lt;strong&gt;AA&lt;/strong&gt; &amp;amp; &lt;strong&gt;AAA&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Read More: &lt;a href="https://www.w3.org/WAI/standards-guidelines/wcag/" rel="noopener noreferrer"&gt;https://www.w3.org/WAI/standards-guidelines/wcag/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Accessible Rich Internet Applications (ARIA)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Adds accessibility support to non-semantic elements.&lt;/p&gt;

&lt;p&gt;Read More: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-label" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Americans with Disabilities Act (ADA)
&lt;/h3&gt;

&lt;p&gt;Legal mandate in the US for accessible digital content&lt;/p&gt;

&lt;p&gt;Read More: &lt;a href="https://www.ada.gov/" rel="noopener noreferrer"&gt;https://www.ada.gov/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AA&lt;/strong&gt; is the most commonly required level of compliance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to make a web application Accessible
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Semantic Tags
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use tags like &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; , &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid using div and span all over your application&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Support Keyboard Accessibility
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Actions should be usable via. keyboard, including opening modals, dropdowns, and toasts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;tabindex&lt;/code&gt; to align focus correctly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use focus, keydown event listeners correctly&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use Aria Roles when needed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use aria-label, aria-hidden, role="dialog", etc., when native semantics are not sufficient&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://w3c.github.io/aria/" rel="noopener noreferrer"&gt;https://w3c.github.io/aria/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Focus Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Trap focus in modals&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Restore focus when modals close&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visibly indicate focus using focus-visible or outline&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Maintain Colour Contrast
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;4.5: 1 for normal text in &lt;strong&gt;AA&lt;/strong&gt; Standard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;7:2 for &lt;strong&gt;AAA&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check contrast: &lt;a href="https://webaim.org/resources/contrastchecker/" rel="noopener noreferrer"&gt;https://webaim.org/resources/contrastchecker/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using Images and Other Media
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use alt text for images&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add transcripts/captions for videos/audio&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid autoplaying media files. i.e. Video&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tools for Accessibility Testing
&lt;/h2&gt;

&lt;p&gt;The tools listed below are some of the most common tools for testing accessibility in a web application.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;axe DevTools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chrome extension to audit a11y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lighthouse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in browser audits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVDA / VoiceOver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Screen reader testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tab key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Test keyboard navigation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;WCAG &lt;strong&gt;AAA is harder to implement, and most website are expected to maintain AA compliant.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I hope by the end of this post, you have a good idea of why accessibility is important and how you can add accessibility support to your application.&lt;/p&gt;

&lt;p&gt;Thats all, folks! I hope you found this helpful. If you enjoyed this, check out more articles on my Blog, &lt;a href="https://blog.nandan.dev/" rel="noopener noreferrer"&gt;https://blog.nandan.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to comment, email me at &lt;a href="http://mailto:connect@nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;connect@nandan.dev&lt;/strong&gt;&lt;/a&gt;, or connect with me on Twitter, Instagram, or GitHub. Dont forget to subscribe to my newsletter for regular updates on JavaScript topics!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/_sirius93_" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://www.instagram.com/nandandotdev" rel="noopener noreferrer"&gt;&lt;strong&gt;Instagram&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/sirius93" rel="noopener noreferrer"&gt;&lt;strong&gt;Github&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>aria</category>
    </item>
    <item>
      <title>Instagram Front-End System Design</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Fri, 01 Aug 2025 05:19:41 +0000</pubDate>
      <link>https://dev.to/sirius93/instagram-front-end-system-design-4fd6</link>
      <guid>https://dev.to/sirius93/instagram-front-end-system-design-4fd6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t9m7sc2pdmcuvyx7qbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t9m7sc2pdmcuvyx7qbt.png" alt="Banner" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me start by putting one thing forward: System design is not about writing a memorised answer on paper. Its more about thinking, planning and strategising.&lt;/p&gt;

&lt;p&gt;Hence, there is no correct answer, but a correct Framework. Understanding and approaching system design with the RADIO &lt;strong&gt;RADIO framework&lt;/strong&gt; (Requirements, Architecture, Design, Implementation, Optimisation) helps you streamline your answer.&lt;/p&gt;

&lt;p&gt;Additionally, you can treat this blog as a framework for everything that requires building a feed, i.e. Twitter, Facebook, or Orkut, for that matter.&lt;/p&gt;

&lt;p&gt;But everything beyond that is dependent on your knowledge and understanding of the system.&lt;/p&gt;

&lt;p&gt;Designing a platform like Instagram might sound overwhelming at first, but breaking it down using the &lt;strong&gt;RADIO framework&lt;/strong&gt; (Requirements, Architecture, Design, Implementation, Optimisation) makes it manageable and actually kinda fun. I recently took a shot at this, and here's what I came up with, plus a few tweaks and lessons learned along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔧 Functional Requirements:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;News Feed that shows text, images, and videos&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User info (photo, username) on each post&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Like, comment, and share buttons&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pagination or infinite scroll&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lazy loading for media&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚫 Non-Functional Requirements:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Fully responsive (mobile + desktop)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Accessibility compliant&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optimised for performance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supports A/B testing for features&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Localisation and translation support&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bonus points if it supports offline mode and has a fallback UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Start by defining your core building blocks:&lt;/p&gt;

&lt;h3&gt;
  
  
  📂 Components:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostCard
├── TextContent
├── ImageGallery / VideoPlayer
├── UserHeader (avatar + name)
├── PostActions (like/comment/share)

Feed
└── PostCard[]

CommentSection
LikeButton
ShareButton

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

&lt;/div&gt;



&lt;p&gt;Each of these is reusable and maintainable the key to scalable front-end architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design
&lt;/h2&gt;

&lt;p&gt;Keep your data models clean and normalised. Here's what worked well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Post = {
  id: string;
  text?: string;
  images?: string[];
  video?: string;
  userId: string;
  comments: Comment[];
  likes: Like[];
  createdAt: string;
};

type User = {
  id: string;
  name: string;
  username: string;
  avatar: string;
  postIds: string[];
};

type Like = {
  userId: string;
  postId: string;
};

type Comment = {
  id: string;
  userId: string;
  postId: string;
  text: string;
  createdAt: string;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Avoid embedding full &lt;code&gt;User&lt;/code&gt; objects inside posts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use IDs to reference everything&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Support multiple images per post (carousel style)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Here's how the main flow looks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feed Page&lt;/strong&gt; : loads posts using a custom hook (&lt;code&gt;useFetchPosts&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Loop through &lt;code&gt;PostCard&lt;/code&gt; components to render UI&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use libraries like React Query or SWR for cache + fetch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Defer comment loading until user clicks "View all comments"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Suggested folder structure:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/components
  /Post
    PostCard.tsx
    PostMedia.tsx
    PostFooter.tsx
  /User
  /Comment
/hooks
  useFetchPosts.ts
/pages
  index.tsx // News Feed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Optimization
&lt;/h2&gt;

&lt;p&gt;Performance matters more than ever. Here's what to prioritise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;📸 Lazy load images and videos&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔄 Use &lt;code&gt;IntersectionObserver&lt;/code&gt; for infinite scroll&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🏔 PWA support (with service workers)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📑 Bundle splitting + code-splitting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🚀 Skeleton loaders while fetching&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🌍 Localised text using &lt;code&gt;react-i18next&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📉 Memoise unchanging components with &lt;code&gt;React.memo&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Designing Instagram from the front-end isnt about copying their UI; it's about building a thoughtful system that can scale, perform, and feel native on any device. Use the RADIO framework to break down the chaos into manageable parts, and you'll be surprised how fast it comes together.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Polyfills of JS Array Methods - Flat, Map, Filter &amp; Reduce</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Tue, 13 May 2025 14:03:11 +0000</pubDate>
      <link>https://dev.to/sirius93/polyfills-of-js-array-methods-flat-map-filter-reduce-1165</link>
      <guid>https://dev.to/sirius93/polyfills-of-js-array-methods-flat-map-filter-reduce-1165</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l51sgxmabzt3rhvdasj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l51sgxmabzt3rhvdasj.png" alt="Array polyfills - Banner Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alright, confession time, Ive been writing JavaScript for a while now, but theres something oddly satisfying about going back to the basics. You know, like trying to bake your bread from scratch even though you live next to a bakery. Thats exactly what this post is about: baking our versions of some classic array methods.&lt;/p&gt;

&lt;p&gt;Want to understand what polyfills are? &lt;a href="https://dev.to/sirius93/polyfills-for-call-apply-and-bind-lets-build-them-from-scratch-15g7"&gt;I have covered them in my blog Polyfills for Call, Apply &amp;amp; Bind.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why? Because it's fun. Because it's educational. And because the next time someone throws a technical interview curveball at you, like "Can you write your own &lt;code&gt;reduce&lt;/code&gt;" and youll casually say, "Sure, do you want it with comments or without?"&lt;/p&gt;

&lt;p&gt;Today, were building polyfills for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;flat()&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;map()&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;filter()&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;reduce()&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lets roll.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;flat()&lt;/code&gt; A Pancake Stack for Arrays
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if(!Array.prototype.myFlat){
    Array.prototype.myFlat = function(depth){
        if(depth &amp;lt; 0) return this;

        const flatten = function(arr, depth){
            return arr.reduce((acc,ele)=&amp;gt;{
                if(Array.isArray(ele) &amp;amp;&amp;amp; depth &amp;gt; 0){
                    acc = acc.concat(flatten(ele,depth -1));
                }else{
                    acc.push(ele);
                }
                return acc;
            },[])
        }

        return flatten(this,depth);
    }
}

const nestedArray = [1, [2, [3, [4, 5]]], 6];

console.log(nestedArray.myFlat()); // Default depth = 1 =&amp;gt; [1, 2, [3, [4, 5]], 6]
console.log(nestedArray.myFlat(2)); // Depth = 2 =&amp;gt; [1, 2, 3, [4, 5], 6]
console.log(nestedArray.myFlat(3)); // Depth = 3 =&amp;gt; [1, 2, 3, 4, 5, 6]
console.log(nestedArray.myFlat(0)); // Depth = 0 =&amp;gt; [1, [2, [3, [4, 5]]], 6]

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

&lt;/div&gt;



&lt;p&gt;Just a basic flattening function. Will it replace your backend job? No. But will it make you feel cool? Absolutely.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;map()&lt;/code&gt; Because Sometimes Arrays Need a Makeover
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if(!Array.prototype.myMap){
    Array.prototype.myMap = function(fn, thisArg){
       if(this == null)  { return new Error("Not an array")};

       if(typeof(fn) != 'function') {return new Error("Not an array")}

       let result = [];

       for(let i=0; i&amp;lt; this.length; i++){
           if(i in this){
               result.push(fn.call(thisArg,this[i], i, this));
           }
       }
       return result;
    }
}

a = [1,2,3,4,5].myMap(function (mem) {
    return mem*mem;
})

console.log(a); // [ 1, 4, 9, 16, 25 ]

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

&lt;/div&gt;



&lt;p&gt;Simple and clean. No magical spells involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;filter()&lt;/code&gt; Only the Worthy Shall Pass
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if(!Array.prototype.myFilter){
    Array.prototype.myFilter = function(fn, thisArg){
       if(this == null)  { return new Error("Not an array")};

       if(typeof(fn) != 'function') {return new Error("Not an array")}

       let result = [];

       for(let i=0; i&amp;lt; this.length; i++){
           if(i in this &amp;amp;&amp;amp; fn.call(thisArg,this[i], i, this)){
               result.push(this[i]);
           }
       }
       return result;
    }
}

b = [1,2,3,4,5].myFilter(function (mem) {
    return mem &amp;gt; 1;
})

console.log(b) //[ 2, 3, 4, 5 ]

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

&lt;/div&gt;



&lt;p&gt;Give it a test run with your own weird conditions. Even filter out people who don't like coffee; your call.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;reduce()&lt;/code&gt; The Boss Level
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if(!Array.prototype.myReduce){
    Array.prototype.myReduce = function(callback,initialValue){
        if(this == null) {return new Error("Not an Array");}

        if(typeof(callback) != 'function') {
            return new Error("Not a function");
        }

        let arr = this;
        let len = arr.length;

        let hasInitial = arguments.length &amp;gt; 1;
        let accumulator = hasInitial ? initialValue : arr[0];
        let startIndex = hasInitial ? 0 : 1;

        if (len === 0 &amp;amp;&amp;amp; !hasInitial) {
          throw new Error("Reduce of empty array with no initial value");
        }


        for (let i = startIndex; i &amp;lt; len; i++) {
          if (i in arr) {
            accumulator = callback(accumulator, arr[i], i, arr);
          }
        }

        return accumulator;
    }
}

let a = [1, 2, 3].myReduce((acc, val) =&amp;gt; acc + val, 0);
console.log(a);

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

&lt;/div&gt;



&lt;p&gt;Reduce is like that friend who seems intimidating at first, but once you get to know them, they carry the whole squad.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Do This?
&lt;/h3&gt;

&lt;p&gt;You may never need to use these in a production app. But knowing how they work helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Understand JavaScript fundamentals better.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Appreciate the built-in methods more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Have something cool to say in interviews or while pretending to debug in coffee shops.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So go ahead, mess around with these, add your own flavour, and maybe even extend a couple more methods just for fun.&lt;/p&gt;

&lt;p&gt;Thats all, folks! I hope you found this helpful. If you enjoyed this, check out more articles on my Blog, &lt;a href="https://blog.nandan.dev/" rel="noopener noreferrer"&gt;https://blog.nandan.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to comment, email me at &lt;a href="http://mailto:connect@nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;connect@nandan.dev&lt;/strong&gt;&lt;/a&gt;, or connect with me on Twitter, Instagram, or GitHub. Dont forget to subscribe to my newsletter for regular updates on JavaScript topics!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/_sirius93_" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://www.instagram.com/nandandotdev" rel="noopener noreferrer"&gt;&lt;strong&gt;Instagram&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/sirius93" rel="noopener noreferrer"&gt;&lt;strong&gt;Github&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>polyfills</category>
      <category>arraymethods</category>
      <category>javascript</category>
      <category>arrays</category>
    </item>
    <item>
      <title>Polyfills for Call, Apply, and Bind — Let’s Build Them From Scratch</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Mon, 28 Apr 2025 16:51:28 +0000</pubDate>
      <link>https://dev.to/sirius93/polyfills-for-call-apply-and-bind-lets-build-them-from-scratch-15g7</link>
      <guid>https://dev.to/sirius93/polyfills-for-call-apply-and-bind-lets-build-them-from-scratch-15g7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fisivduca05yae78s5phy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fisivduca05yae78s5phy.png" alt="Plyfill call,Apply,Bind Cover" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, so if you have ever deep-dived into JavaScript interviews or just casually scrolled through dev Twitter (yes, thats a thing), you would have stumbled upon someone throwing around the words &lt;strong&gt;polyfill&lt;/strong&gt; like they were born building web browsers.&lt;/p&gt;

&lt;p&gt;And if youre like me, you probably nodded along, opened a tab to Google it later, and procrastinated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9ctqdqoegm6l9nqr9z0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9ctqdqoegm6l9nqr9z0.gif" alt="Procastination Gif" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But no worries. Youre here now, and we are going to figure this out &lt;em&gt;together.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;First things first What the heck is a polyfill?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;polyfill&lt;/strong&gt; is just a piece of code (usually JavaScript) that &lt;strong&gt;adds functionality to older browsers&lt;/strong&gt; that do not natively support a certain feature.&lt;/p&gt;

&lt;p&gt;Think of it like you bringing a power bank to an event where there are no charging points. Your phone is old? No problem. You got the backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Now Why Call, Apply, and Bind?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Because theyre like the Avengers of function manipulation in JavaScript.&lt;/p&gt;

&lt;p&gt;And also because they confuse almost everyone at first.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;call&lt;/code&gt; lets you call a function with a given this value and arguments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;apply&lt;/code&gt; is like &lt;code&gt;call&lt;/code&gt;, but arguments are passed as an array.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;bind&lt;/code&gt; doesnt immediately invoke the function. It &lt;em&gt;returns a new function&lt;/em&gt; with a given this.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/sirius93/yet-another-blog-on-call-apply-bind-336d"&gt;Here is a detailed write-up on call, apply &amp;amp; bind&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Simple enough, right? Now lets make our versions&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Polyfill for call&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Heres how we can build a &lt;strong&gt;polyfill for call&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Function.prototype.myCall = function(context,...args){ 
    context = context || globalThis;
    let Symbolfn = Symbol("fn"); // Unique key
    context[Symbolfn] = this; // Assign 'this' function to the context
    let result = context[Symbolfn](...args); // Call the function
    delete context[Symbolfn];  // Clean up
    return result;
}

// Usage
function sayHello(greeting) {
  console.log(`${greeting}, ${this.name}`);
}

const user = { name: "Nandan" };
sayHello.myCall(user, "Hello");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool, right? Not rocket science.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Polyfill for apply&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now, lets tweak it a bit for &lt;strong&gt;apply&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Function.prototype.myApply = function(context,args){
    context = context || globalThis;
    let Symbolfn = Symbol("fn");
    context[Symbolfn] = this;
    let result = args ? context[Symbolfn](...args) : context[Symbolfn]();
    delete context[Symbolfn];
    return result;
}

// Usage
sayHello.myApply(user, ["Hi"]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Polyfill for bind&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now comes the big boss &lt;strong&gt;bind&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Function.prototype.myBind = function(context,...initialArgs){
    let fn = this;
    return function(...functArgs){
        return fn.apply(context, [...initialArgs,...functArgs])
    }
}

// Usage
const boundSayHello = sayHello.myBind(user, "Hey");
boundSayHello(); // Outputs: Hey, Nandan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;bind doesnt call the function right away.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It returns a &lt;em&gt;new function&lt;/em&gt; that you can call later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We merge the arguments provided at bind-time and call-time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Thats it You just built call, apply, and bind polyfills!&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Honestly, when I first read about polyfills, I thought it was some deep internal browser magic. Turns out, its just about creatively manipulating JavaScripts behavior.&lt;/p&gt;

&lt;p&gt;If you take one thing from this post, let it be this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The cowards never started and the weak died along the way. That leaves us, ladies and gentlemen. Us.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Yeah, thats a quote from &lt;em&gt;Shoe Dog&lt;/em&gt;. Still fits perfectly here, doesnt it?)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kitaaben.com/p/just-do-it-the-real-story-behind-nike-s-rise" rel="noopener noreferrer"&gt;Heres one if you want to read a review of the book Shoe Dog, written by me.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thats all, folks! I hope you found this short note on Polyfills of Call, Apply &amp;amp; Bind helpful. If you enjoyed this, check out more articles on my Blog, &lt;a href="https://blog.nandan.dev/" rel="noopener noreferrer"&gt;https://blog.nandan.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to comment, email me at &lt;a href="http://mailto:connect@nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;connect@nandan.dev&lt;/strong&gt;&lt;/a&gt;, or connect with me on Twitter, Instagram, or GitHub. Dont forget to subscribe to my newsletter for regular updates on JavaScript topics!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/_sirius93_" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://www.instagram.com/nandandotdev" rel="noopener noreferrer"&gt;&lt;strong&gt;Instagram&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/sirius93" rel="noopener noreferrer"&gt;&lt;strong&gt;Github&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>polyfills</category>
      <category>callapplyandbindmeth</category>
      <category>interviewquestions</category>
    </item>
    <item>
      <title>I Vibe Coded an AI Book Recommendation App and was DDOS-ed for it.</title>
      <dc:creator>Nandan Kumar</dc:creator>
      <pubDate>Tue, 15 Apr 2025 10:37:13 +0000</pubDate>
      <link>https://dev.to/sirius93/i-built-an-ai-book-recommendation-app-and-was-ddos-ed-for-it-p8</link>
      <guid>https://dev.to/sirius93/i-built-an-ai-book-recommendation-app-and-was-ddos-ed-for-it-p8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiecltirwrolay72b2vk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiecltirwrolay72b2vk5.png" alt="built an AI Book Recommendation App and was DDOS-ed" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last week, Google introduced its AI copilot Firebase Studio, and I decided to give it a try. It got me thinking about what I want to do next.&lt;/p&gt;

&lt;p&gt;If you may be following me on socials, I just started a newsletter for book reviews &amp;amp; suggestions. Check &lt;a href="https://kitaaben.com" rel="noopener noreferrer"&gt;https://kitaaben.com&lt;/a&gt;. That got me thinking: What if I built a small book recommendation application that can help users get book recommendations based on their interests?&lt;/p&gt;

&lt;p&gt;I started building it on Firebase studio. Started with a simple prompt to build an AI-based book recommendation application that shows a good variety of genres by default and an interest field for additional context. The Number of books you want to be recommended is either 1/3/6 or 12.&lt;/p&gt;

&lt;p&gt;The initial version came with these fields. But after some iterations, I decided to add the email field and email integration to help users get the recommendation directly to their email.&lt;/p&gt;

&lt;p&gt;The tech stack looks something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Nextjs + Reactjs ( For the application)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gemini API (For AI)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Firebase (For Hosting)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resend (For Email - Free Tier)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final version looked like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bookwise.kitaaben.com/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl488giekwyxolt91pyfg.png" alt="Bookwise AI Landing Page" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After some testing and a lot of dopamine, I went ahead and made it public. Mapped it to a subdomain and made it public. Here is the URL if you want to give it a try: &lt;a href="https://bookwise.kitaaben.com/" rel="noopener noreferrer"&gt;https://bookwise.kitaaben.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I shared it in my friends WhatsApp group, Instagram, Twitter( now X), Linkedin almost everywhere I could.&lt;/p&gt;

&lt;p&gt;But seems like someone didnt like the spamming and decided to do something about it. By launching a DDOS attack on my website.Targeting my email functionality, trying to exhaust my email limit and increasing my cloud hosting + AI usage cost.&lt;/p&gt;

&lt;p&gt;See the sudden spike in the requests?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbdwnpki575objsxzxjhc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbdwnpki575objsxzxjhc.png" alt="Bookwise AI backend dashboard" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Panic set in; I didnt know what to do. Should I bring down the site and end this or something else? I decided not to give up and just think and act quickly&lt;/p&gt;

&lt;p&gt;I did and quickly signed up for Cloudflare, added the Cloudflare protection layer to my app and observed the behaviour for some time. By this time, my email limit of 200 emails by resend had already been exhausted, so I waited for the next day and added a caching layer to store the emails and rate limit the emails to 3 emails per hour.&lt;/p&gt;

&lt;p&gt;Things seem to be stable for now. The traffic has dropped as well. However, I would not mind some humans trying the app, benefiting from it and providing some real feedback.&lt;/p&gt;

&lt;p&gt;If you are a human reading this, give the app a try. Tell me what you like/dislike. I am always just an email away at &lt;a href="//mailto:connect@nandan.dev"&gt;connect@nandan.dev&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Thats all, folks! I hope you found this short note on Call, Apply &amp;amp; Bind helpful. If you enjoyed this, check out more articles on my website, &lt;a href="https://nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;https://nandan.dev/&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to comment, email me at &lt;a href="http://mailto:connect@nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;connect@nandan.dev&lt;/strong&gt;&lt;/a&gt;, or connect with me on Twitter, Instagram, or GitHub. Dont forget to subscribe to my newsletter for regular updates on JavaScript topics!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/_sirius93_" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://www.instagram.com/nandandotdev" rel="noopener noreferrer"&gt;&lt;strong&gt;Instagram&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://github.com/sirius93" rel="noopener noreferrer"&gt;&lt;strong&gt;Github&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://nandan.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
      <category>ddos</category>
      <category>ddosprotection</category>
    </item>
  </channel>
</rss>
