Separate user, moderator and admin code into multiple repos

Summary

The architectural decision of whether to split user, moderator, and admin functionality into separate repositories is a classic tension between security boundaries and operational complexity. While the intention is to implement the Principle of Least Privilege, over-engineering the repository structure early in a project often leads to distributed monolith hell, where deployment synchronization and shared logic management become significant bottlenecks.

Root Cause

The desire to separate codebases stems from several valid but often misapplied security concerns:

  • Access Control Misconception: The belief that repository-level permissions are the primary line of defense for application-level authorization.
  • Blast Radius Anxiety: The fear that a vulnerability in the user-facing code will automatically grant an attacker access to administrative tooling.
  • Onboarding Friction: The goal of minimizing the “cognitive load” for external contributors by limiting their view of the entire system.

Why This Happens in Real Systems

In scaling organizations, this tension is real. As teams grow, you cannot have 50 developers all committing to a single “monorepo” without significant tooling (like Bazel or Nx). However, in the early stages of a startup or a solo project, the overhead of managing multiple CI/CD pipelines, versioning shared libraries, and syncing breaking changes across three repos outweighs the security benefits.

The fundamental mistake is confusing Code Isolation with Runtime Isolation.

Real-World Impact

If you implement three separate repositories for a single application without a mature DevOps culture, you will face:

  • Dependency Hell: Updating a critical security patch in the “Common” library requires three separate pull requests, three builds, and three deployments.
  • Deployment Desynchronization: An admin feature might require a schema change that the user repo isn’t aware of yet, leading to runtime crashes.
  • Increased Surface Area: Instead of securing one production environment, you are now managing the secrets, build runners, and access tokens for three distinct environments.
  • Shadow Complexity: Developers spend more time managing package.json versions and git submodules than writing actual business logic.

Example or Code (if necessary and relevant)

Instead of splitting repositories, senior engineers use Logical Separation within a single codebase using Middleware and Role-Based Access Control (RBAC).

interface User {
  id: string;
  role: 'USER' | 'MODERATOR' | 'ADMIN';
}

const authorize = (requiredRole: string) => {
  return (req: Request, res: Response, next: NextFunction) => {
    if (req.user.role !== requiredRole) {
      return res.status(403).json({ error: "Access Denied" });
    }
    next();
  };
};

// Admin routes are logically isolated but live in the same repo
app.post('/api/admin/withdraw-funds', authorize('ADMIN'), async (req, res) => {
  // Critical logic here
});

// User routes are also here
app.get('/api/user/balance', authorize('USER'), async (req, res) => {
  // Standard logic here
});

How Senior Engineers Fix It

Senior engineers prioritize Runtime Isolation over Source Code Isolation. If you are building a crypto platform where security is paramount, the correct approach is:

  1. Single Repository (Monorepo): Keep the code in one place to ensure atomic commits and easy refactoring.
  2. Logical Separation: Use strict folder structures (e.g., /src/modules/admin, /src/modules/user) and internal linting rules to prevent accidental imports.
  3. Runtime Microservices (The Real Fix): If the security risk is truly high, deploy the Admin functionality as a completely separate service (different container, different VPC, different database user) that communicates via a private network.
  4. Infrastructure-Level Security: Use IAM roles and Network Security Groups to ensure the “User Service” physically cannot reach the “Admin Database” directly.

Why Juniors Miss It

Juniors often focus on “Security by Obscurity”—thinking that if a developer can’t see the code, they can’t exploit it. They miss the fact that:

  • Code is not the boundary; the API is. An attacker doesn’t need to see your admin source code to exploit an unprotected /admin/delete-user endpoint.
  • Complexity is the enemy of security. By creating three repos, you increase the chance of a configuration error in one of the CI/CD pipelines, which is often a much easier target than a hardened code repository.
  • They mistake “Development Ease” for “Architectural Purity.” They try to build a “perfect” distributed system before they have even validated their core business logic.

Leave a Comment