Muhammad SharjeelSenior Software Engineer

Islamabad · --:--:--


Taming CORS in a Microservices Monorepo: From Async Validation to a Unified Config

CORS errors can be frustrating, especially in a microservices monorepo setup. This blog dives into what CORS really is, why it matters, and how I solved the CORS challenge using a shared JSON config file — establishing a single source of truth for all services.

Cross-Origin Resource Sharing (CORS) is a security feature implemented by web browsers. It restricts web applications running on one origin (domain) from interacting with resources from another unless explicitly allowed.

Let’s say your frontend is hosted at https://app.example.com and your API is at https://api.example.com. By default, browsers will block requests from the frontend to the backend unless the backend server allows it via CORS headers.

Why CORS is Necessary

CORS exists to protect users from malicious websites that attempt to read sensitive data from another origin (like your banking session). Without CORS, any site could fetch data from any API the user is authenticated with — a nightmare for security.

In development, however, it often feels like a headache: “Why is my API call failing even though the backend is up?” More often than not, it’s a CORS misconfiguration.

How CORS Works (Including Preflight)

There are two types of CORS requests:

  • Simple Requests: Like a GET with no custom headers.
  • Preflighted Requests: Any request that modifies data (e.g., POST, PUT) or includes custom headers triggers a preflight check — an initial OPTIONS request sent by the browser to verify if the real request is safe to send.

If your server fails to respond to this OPTIONS request correctly, the real request is never made.

My CORS Problem: Microservices in a Monorepo

I’m working on a platform called PolyX — built using a microservices architecture, all managed in a Yarn monorepo. Each service runs independently but shares the same workspace — and that’s where the challenge came in.

I initially allowed each service to define its own list of allowed CORS origins. But in a monorepo setup, this quickly became unmanageable. A new frontend domain? I had to update every service manually. Worse: discrepancies started appearing between environments.

Early Attempt: Asynchronous Origin Validation

My first idea was to validate origins asynchronously — fetching allowed origins from a central store (like Redis or MongoDB) during every request. It was technically flexible, but practically inefficient.

Why? Because:

  • Every request triggered a DB read
  • This added latency to every API call
  • Even OPTIONS (preflight) requests had to go through this check

It was elegant in theory, but a performance and stability bottleneck in practice.

The Real Solution: A Shared JSON File

So, I went back to basics. I created a single JSON file inside the monorepo:

/poly-auth/poly-common/config/allowed-origins.json

Every microservice now loads its allowed origins from this file at startup. And this is where the magic of a monorepo shines — since everything lives in one place, all services can import and reference the same file easily.

Sample JSON File

{
    "allowedOrigins": [
      "https://admin.example.com",
      "https://dashboard.example.com",
      "https://app.example.com"
    ]
  }

Why a Single Source of Truth Matters

Beyond CORS, this experience taught me the value of single source of truth (SSOT) in monorepo architecture:

  • ✅ Reduces config drift across services
  • ✅ Makes onboarding easier
  • ✅ Enables automation and CI checks on shared settings
  • ✅ Avoids bugs caused by inconsistent behavior

This pattern now powers not just CORS — but also shared error codes, status enums, role definitions, and even environment flags.

Implementing It in Node.js

Here’s a basic snippet of how a service loads and uses the JSON config:

import fs from 'fs';
  import path from 'path';
  
  const ORIGIN_FILE_PATH = path.join(__dirname, '../config/allowed-origins.json');
  const rawOrigins = fs.readFileSync(ORIGIN_FILE_PATH, 'utf-8');
  const { allowedOrigins } = JSON.parse(rawOrigins);
  
  app.use(cors({
    origin: (origin, callback) => {
      if (!origin || allowedOrigins.includes(origin)) {
        return callback(null, true);
      }
      return callback(new Error("Not allowed by CORS"));
    },
    credentials: true
  }));

This way, no matter which service handles a request, it follows the same rules — with zero runtime calls to databases or external services.

Lessons Learned

Here’s what I learned through this journey:

  • Don’t over-engineer early. CORS doesn’t need to be dynamic for most setups.
  • Monorepos simplify shared config. Lean into that instead of duplicating.
  • Performance matters in security middleware. Especially for preflight requests.

Final Thoughts

CORS errors can feel like black magic at times — but understanding how they work and structuring your architecture with clarity makes all the difference. For me, it wasn’t about a fancy fix. It was about designing with simplicity, maintainability, and shared ownership in mind.

And sometimes, the best solution is the one that lives in a good old JSON file.

esc
HomeThe hero, the system map and selected workPageWorkAll 6 projects, each with a glimpse of its interfacePageAboutExperience, how I work and the stack I usePageWriting12 posts on engineering, AI and designPageGet in touchContact me by email or hire me on UpworkSection
Alara CodeTurns a client brief into a complete, human-approved software delivery pack.ProjectPolyXIdentity, tenancy and access platform shared by every product in the company.ProjectPolyX SDKTypeScript auth SDK on npm that puts the platform behind one typed client.ProjectAlara SpaceMulti-tenant team wiki that AI agents can read and write through MCP.ProjectAlara PlanProject tracker with AI meeting minutes, support intake and an MCP server.ProjectODINOmni-channel customer engagement platform on a microservices backend.Project
A portfolio that builds itself while you scrollThis site starts as a blank canvas and a swarm of cursors draws it in front of you. Here is why I wanted that, what it is made of, and everything that went wrong on the way.8 min readUnslopping AI output with nothing but a skillYou do not need a better model to get rid of the purple gradients. You need your taste written down as a skill the agent loads before it touches the screen.2 min readAI Assistant Coding vs Vibe CodingAs AI becomes ubiquitous in software development, a dangerous pattern is emerging: developers blindly accepting AI suggestions without understanding the code. This 'vibe coding' approach undermines the fundamentals of being a developer. Here's why understanding code matters more than shipping it, and how to use AI as a proper assistant.8 min readHosting Multiple Domains on One Server with Docker, NGINX & SSL (DevOps for Developers)As a full-stack developer, I recently had to deploy multiple backend services on one server, each with its own domain and SSL certificate. Here's how I combined Docker, NGINX, and Certbot to build a scalable, production-grade setup — without needing a dedicated DevOps engineer.3 min readTaming CORS in a Microservices Monorepo: From Async Validation to a Unified ConfigCORS errors can be frustrating, especially in a microservices monorepo setup. This blog dives into what CORS really is, why it matters, and how I solved the CORS challenge using a shared JSON config file — establishing a single source of truth for all services.4 min readDebugging Tips for Frontend Developers (React + Next.js)Effective debugging is one of the most valuable skills for frontend developers. Here's a practical guide to debugging with logs, using the debugger keyword, troubleshooting APIs, and improving performance — with a special focus on React and Next.js.3 min readIs the ‘AI Developer’ a Threat to Jobs – or a Marketing Stunt?AI dev tools are booming — but are they replacing developers or just reshaping workflows? Here's the balanced view.3 min readUsing Docker & NGINX to Build a Suite of MicroservicesExplore how to use Docker and NGINX to containerize and orchestrate a scalable microservices suite. Includes setup tips, architecture advice, and real-world tradeoffs.3 min readGetting Started with Next.js: Structure, Tips & DeploymentA developer-friendly introduction to Next.js — learn how to scaffold projects, understand folder structure, configure features, and deploy to Vercel seamlessly.3 min readGrid Systems: The Foundation of Great UI DesignWant cleaner, more balanced layouts? A grid system brings structure, rhythm, and clarity to your UI — and Tailwind makes it easier than ever to implement.2 min readUsing Generative AI to Speed Up Creative and Dev WorkflowsFrom ChatGPT to v0.dev — here's how generative AI helps streamline UI design, prototyping, coding, and problem-solving in modern workflows.3 min readWhat Is Accessible Design and Why It MattersAccessibility isn’t a nice-to-have. It’s a necessity. Here's why accessible design matters — and how to get it right in your UI and frontend code.3 min read
GitHubgithub.com/isharjeelddNew tabLinkedInlinkedin.com/in/muhammad-sharjeel-254a48108New tabUpworkHire me for freelance workNew tab