Home/Courses/JavaScript/Maintainable & Scalable Architecture

    ๐Ÿ’ก Running Code Locally

    While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:Download Node.js to run JavaScript on your computer, use your browser's Developer Console (Press F12) to test code snippets, or create a .html file with <script> tags and open it in your browser.

    Writing Maintainable & Scalable Code Architecture

    Learn how to structure JavaScript applications that are easy to change, extend, and maintain โ€” no matter how big the project grows.

    What Is Architecture Really?

    Most developers think "architecture" means fancy diagrams and complicated patterns. Not true.

    • Maintainable architecture = code that is easy to change
    • Scalable architecture = code that doesn't fall apart when features grow

    The job of architecture is simple: Keep the codebase understandable, predictable, and safe to modify โ€” no matter how big the project gets.

    โญ The 4 Pillars of Maintainable & Scalable Code

    1. Separation of Concerns (SoC)

    The most important rule in architecture. Every file/module should do one thing well โ€” not twenty responsibilities.

    โŒ Bad example (beginners do this):

    Bad: Everything in One File

    This approach mixes all responsibilities in a single file

    Try it Yourself ยป
    JavaScript
    // login.js โ€” DOES EVERYTHING
    fetch('/login')
      .then(...)
    localStorage.setItem(...)
    updateUI(...)
    trackAnalytics(...)

    โœ” Good separation:

    Good: Separated Concerns

    Each responsibility lives in its own place

    Try it Yourself ยป
    JavaScript
    // Clean folder structure
    // api/
    //   authApi.js
    // services/
    //   authService.js
    // ui/
    //   loginForm.js
    // utils/
    //   validators.js
    
    // Each responsibility lives in its own place
    console.log("Separation of Concerns = Clean Code");

    2. Modularity

    A modular system is like LEGO: pieces are small, pieces connect cleanly, pieces can be replaced without breaking everything.

    Modularity Example

    Small, reusable functions used everywhere

    Try it Yourself ยป
    JavaScript
    // utils/calcTotal.js
    function calcTotal(items) {
      return items.reduce((sum, i) => sum + i.price, 0);
    }
    
    // Used everywhere without duplication
    const cart = [
      { name: "Shirt", price: 25 },
      { name: "Pants", price: 50 }
    ];
    
    console.log("Total:", calcTotal(cart));

    3. Clear Boundaries Between Layers

    A scalable app separates logic into layers:

    • UI Layer โ€” components, views, DOM
    • Service Layer โ€” business logic
    • API/Infrastructure Layer โ€” fetch, localStorage, databases

    Layer Boundaries

    Separate API, Service, and UI layers

    Try it Yourself ยป
    JavaScript
    // api/userApi.js
    async function fetchUser(id) {
      return { id, name: "John" };
    }
    
    // services/userService.js
    async function getUser(id) {
      const user = await fetchUser(id);
      return { ...user, displayName: user.name.toUpperCase() };
    }
    
    // Usage
    getUser(1).then(user => console.log(user));

    4. Consistency (Naming, Structuring, Coding Style)

    A clean codebase is predictable. Always use the same naming conventions and put files in the same structure.

    Consistent Naming

    Use the same naming conventions everywhere

    Try it Yourself ยป
    JavaScript
    // โŒ Bad (random styles)
    // user-service.js
    // UserApi.js
    // helper.js
    // utils2.js
    
    // โœ” Good (consistent)
    // services/
    //   userService.js
    // api/
    //   userApi.js
    // utils/
    //   formatUser.js
    
    console.log("Consistency = Predictability");

    โญ The 3-Layer Architecture

    This is the most effective structure for large JavaScript apps:

    3-Layer Architecture

    The most effective structure for large apps

    Try it Yourself ยป
    JavaScript
    // 3-Layer Architecture
    // presentation/   (UI)
    // application/    (services, business rules)
    // infrastructure/ (APIs, storage, databases)
    
    console.log("3 Layers:");
    console.log("1. Presentation (UI)");
    console.log("2. Application (Business Logic)");
    console.log("3. Infrastructure (APIs, Storage)");

    Presentation Layer (UI Layer)

    Handles ONLY: rendering, user input, event handling

    Presentation Layer

    Handles only rendering, user input, event handling

    Try it Yourself ยป
    JavaScript
    // presentation/LoginForm.jsx
    // import { loginUser } from "../application/authService.js";
    
    async function handleSubmit(email, password) {
      const user = await loginUser(email, password);
      console.log("Logged in:", user);
    }
    
    // Mock loginUser for demo
    async function loginUser(email, password) {
      return { email, name: "Demo User" };
    }
    
    // The UI has NO idea how login works internally
    // It just calls a function
    handleSubmit("test@example.com", "password123");

    Application Layer (Business Logic)

    Contains: rules, validation, orchestration, workflows, state transformations

    Application Layer

    Contains business rules, validation, and workflows

    Try it Yourself ยป
    JavaScript
    // application/authService.js
    async function loginRequest(credentials) {
      // Mock API call
      return { 
        user: { email: credentials.email, active: true }, 
        token: "abc123" 
      };
    }
    
    async function loginUser(email, password) {
      const res = await loginRequest({ email, password });
    
      // business rules here
      if (!res.user.active) {
        throw new Error("Account not active");
      }
    
      console.log("Token saved:", res.token);
      return res.user;
    }
    
    loginUser("test@example.com", "pass").then(consol
    ...

    Infrastructure Layer (APIs, Databases, Storage)

    Handles ONLY: HTTP requests, storage, browser APIs, third-party integrations

    Infrastructure Layer

    Handles HTTP requests, storage, and third-party APIs

    Try it Yourself ยป
    JavaScript
    // infrastructure/authApi.js
    async function loginRequest(credentials) {
      // In real app: fetch("/api/login", { method: "POST", ... })
      console.log("Making API request with:", credentials);
      
      // Mock response
      return { 
        success: true, 
        user: { id: 1, email: credentials.email } 
      };
    }
    
    // Key: If your backend changes โ€” only this file updates
    loginRequest({ email: "test@test.com", password: "123" })
      .then(res => console.log("Response:", res));

    โญ Real-World Folder Structure (Proven to Scale)

    Here is a structure used by top engineering teams:

    Scalable Folder Structure

    Structure used by top engineering teams

    Try it Yourself ยป
    JavaScript
    // Real-world folder structure
    const structure = {
      "src/": {
        "presentation/": ["components/", "pages/", "hooks/"],
        "application/": {
          "auth/": ["authService.js", "authValidators.js"],
          "users/": ["userService.js", "userRules.js"],
          "orders/": ["orderService.js"]
        },
        "infrastructure/": {
          "api/": ["userApi.js", "authApi.js", "orderApi.js"],
          "storage/": ["sessionStorage.js", "localStorageAdapter.js"]
        },
        "domain/": ["models/", "types/"],
        "utils
    ...

    ๐Ÿ’ก This gives you: feature-driven structure, clear boundaries, logical grouping, easy scalability, and massive maintainability.

    โญ API Adapters โ€” The Secret to Scalability

    Never call fetch() or Axios inside UI or services. Instead, build API adapters.

    API Adapters Pattern

    Build reusable HTTP client adapters

    Try it Yourself ยป
    JavaScript
    // infrastructure/httpClient.js
    async function httpGet(url) {
      console.log("GET:", url);
      // In real app: const res = await fetch(url);
      return { data: "Mock data from " + url };
    }
    
    async function httpPost(url, body) {
      console.log("POST:", url, body);
      return { success: true, data: body };
    }
    
    // Now your API files become clean:
    // infrastructure/userApi.js
    function getUser(id) {
      return httpGet(`/api/users/${id}`);
    }
    
    getUser(123).then(console.log);

    โญ Dependency Direction Matters

    The flow of dependencies should always go DOWNWARD:

    Dependency Direction

    Dependencies should flow downward, never upward

    Try it Yourself ยป
    JavaScript
    // Dependency flow: UI โ†’ Services โ†’ API โ†’ External systems
    console.log("Dependency Direction:");
    console.log("UI โ†’ Services โ†’ API โ†’ External");
    console.log("");
    console.log("The lower layer must NEVER import the upper layer.");
    console.log("");
    console.log("โŒ Bad: authService imports loginForm");
    console.log("โŒ Bad: loginForm imports authService (circular!)");
    console.log("");
    console.log("โœ” Good: UI imports services");
    console.log("โœ” Good: Services import APIs");
    console.log("โœ” Good: APIs impor
    ...

    โญ Apply the "Pure Function Core" Rule

    For maximum maintainability: business logic should be pure functions, UI/infra should be thin wrappers.

    Pure Function Core

    Business logic as pure functions

    Try it Yourself ยป
    JavaScript
    // โŒ Bad architecture (mixed concerns):
    let cart = [];
    function badAddItemToCart(item) {
      cart.push(item);
      // updateUI(cart);
      // logAnalytics(item);
      console.log("Bad: Mixed logic, UI, analytics");
    }
    
    // โœ” Good architecture (pure functions):
    function addItemToCart(cart, item) {
      return [...cart, item]; // Pure function!
    }
    
    // Usage in presentation layer:
    let myCart = [];
    myCart = addItemToCart(myCart, { name: "Shirt", price: 25 });
    myCart = addItemToCart(myCart, { name: "Pants", price: 50
    ...

    โญ Real Refactoring Example (JUNIOR โ†’ SENIOR)

    Original messy function:

    Bad: Messy Function

    Everything mixed in one function

    Try it Yourself ยป
    JavaScript
    // โŒ Messy function (everything in one place)
    async function checkout(cart) {
      // const res = await fetch("/api/checkout", { ... });
      // const data = await res.json();
      const data = { ok: true, items: cart }; // Mock
      
      if (!data.ok) {
        alert("ERROR");
      } else {
        localStorage.setItem("lastOrder", JSON.stringify(data));
        // window.location.href = "/thank-you";
        console.log("Redirecting to thank-you page");
      }
    }
    
    // This has:
    // โŒ API logic
    // โŒ business logic  
    // โŒ storage log
    ...

    Step 1 โ€” Extract Infrastructure Layer:

    Step 1: Extract API Layer

    Infrastructure layer handles HTTP requests

    Try it Yourself ยป
    JavaScript
    // infrastructure/orderApi.js
    async function placeOrder(cart) {
      console.log("API: Placing order with", cart);
      // const res = await fetch("/api/checkout", { ... });
      // if (!res.ok) throw new Error("Checkout failed");
      // return res.json();
      return { success: true, items: cart, orderId: 123 };
    }
    
    // Test it
    placeOrder([{ name: "Shirt", price: 25 }]).then(console.log);

    Step 2 โ€” Extract Business Logic Layer:

    Step 2: Extract Business Logic

    Application layer contains business rules

    Try it Yourself ยป
    JavaScript
    // Mock API
    async function placeOrder(cart) {
      return { success: true, items: cart, orderId: 123 };
    }
    
    // application/orderService.js
    async function checkoutOrder(cart) {
      const order = await placeOrder(cart);
    
      // business rule: order must contain items
      if (order.items.length === 0) {
        throw new Error("Invalid order");
      }
    
      return order;
    }
    
    // Test it
    checkoutOrder([{ name: "Shirt", price: 25 }]).then(console.log);

    Step 3 โ€” Extract UI Layer:

    Step 3: Extract UI Layer

    Presentation layer handles user interactions

    Try it Yourself ยป
    JavaScript
    // Mock service
    async function checkoutOrder(cart) {
      return { success: true, items: cart, orderId: 123 };
    }
    
    // presentation/checkoutUI.js
    async function handleCheckout(cart) {
      try {
        const order = await checkoutOrder(cart);
        localStorage.setItem("lastOrder", JSON.stringify(order));
        console.log("Order saved! Redirecting...");
        // window.location.href = "/thank-you";
      } catch (err) {
        alert(err.message);
      }
    }
    
    // Final result:
    // โœ” clean
    // โœ” testable
    // โœ” scalable
    
    handleCh
    ...

    โญ Guard Your Module Boundaries

    Use barrel exports (index.js) to control what other modules can access:

    Module Boundaries

    Use barrel exports to control public API

    Try it Yourself ยป
    JavaScript
    // auth/index.js (barrel export)
    // Only export what other modules should use
    // export { loginUser, logoutUser } from "./authService.js";
    // 
    // Internal functions stay private:
    // - validateCredentials
    // - hashPassword
    // - refreshToken
    
    console.log("Barrel exports = controlled boundaries");
    console.log("");
    console.log("Public API:");
    console.log("- loginUser()");
    console.log("- logoutUser()");
    console.log("");
    console.log("Internal (private):");
    console.log("- validateCredentials()");
    conso
    ...

    โญ Avoid These Common Mistakes

    Common Mistakes to Avoid

    Learn what NOT to do in architecture

    Try it Yourself ยป
    JavaScript
    // Common Architecture Mistakes:
    
    console.log("โŒ MISTAKES TO AVOID:");
    console.log("");
    console.log("1. Putting everything in one file");
    console.log("2. Circular dependencies between modules");
    console.log("3. UI components calling fetch() directly");
    console.log("4. Business logic in API layer");
    console.log("5. No consistent naming conventions");
    console.log("6. Giant util files with unrelated functions");
    console.log("7. Importing from deep paths instead of barrels");
    console.log("");
    consol
    ...

    ๐Ÿš€ Try It Yourself

    Practice refactoring this messy function into clean layers:

    Architecture Challenge

    Refactor this messy function into clean layers

    Try it Yourself ยป
    JavaScript
    // CHALLENGE: Refactor this into 3 layers
    
    async function createUser(name, email) {
      // Validation
      if (!name || !email) {
        alert("Name and email required");
        return;
      }
      
      // API call
      const response = { id: 1, name, email }; // Mock
      
      // Store result
      localStorage.setItem("newUser", JSON.stringify(response));
      
      // Update UI
      console.log("User created:", response);
      
      return response;
    }
    
    // Your task:
    // 1. Create validateUser() in application layer
    // 2. Create saveUser()
    ...

    ๐Ÿ“Œ Key Takeaways

    • Separation of Concerns โ€” each file does one thing
    • Modularity โ€” small, reusable pieces
    • 3-Layer Architecture โ€” Presentation, Application, Infrastructure
    • Dependencies flow downward โ€” never import UI into services
    • Pure functions โ€” business logic should be testable
    • Consistency โ€” same naming and structure everywhere
    • Barrel exports โ€” control your module boundaries

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous