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.
Learn how to structure JavaScript applications that are easy to change, extend, and maintain โ no matter how big the project grows.
Most developers think "architecture" means fancy diagrams and complicated patterns. Not true.
The job of architecture is simple: Keep the codebase understandable, predictable, and safe to modify โ no matter how big the project gets.
The most important rule in architecture. Every file/module should do one thing well โ not twenty responsibilities.
โ Bad example (beginners do this):
This approach mixes all responsibilities in a single file
// login.js โ DOES EVERYTHING
fetch('/login')
.then(...)
localStorage.setItem(...)
updateUI(...)
trackAnalytics(...)โ Good separation:
Each responsibility lives in its own place
// 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");A modular system is like LEGO: pieces are small, pieces connect cleanly, pieces can be replaced without breaking everything.
Small, reusable functions used everywhere
// 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));A scalable app separates logic into layers:
Separate API, Service, and UI layers
// 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));A clean codebase is predictable. Always use the same naming conventions and put files in the same structure.
Use the same naming conventions everywhere
// โ 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");This is the most effective structure for large JavaScript apps:
The most effective structure for large apps
// 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)");Handles ONLY: rendering, user input, event handling
Handles only rendering, user input, event handling
// 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");Contains: rules, validation, orchestration, workflows, state transformations
Contains business rules, validation, and workflows
// 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
...Handles ONLY: HTTP requests, storage, browser APIs, third-party integrations
Handles HTTP requests, storage, and third-party APIs
// 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));Here is a structure used by top engineering teams:
Structure used by top engineering teams
// 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.
Never call fetch() or Axios inside UI or services. Instead, build API adapters.
Build reusable HTTP client adapters
// 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);The flow of dependencies should always go DOWNWARD:
Dependencies should flow downward, never upward
// 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
...For maximum maintainability: business logic should be pure functions, UI/infra should be thin wrappers.
Business logic as pure functions
// โ 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
...Everything mixed in one function
// โ 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
...Infrastructure layer handles HTTP requests
// 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);Application layer contains business rules
// 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);Presentation layer handles user interactions
// 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
...Use barrel exports (index.js) to control what other modules can access:
Use barrel exports to control public API
// 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
...Learn what NOT to do in architecture
// 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
...Practice refactoring this messy function into clean layers:
Refactor this messy function into clean layers
// 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()
...Sign up for free to track which lessons you've completed and get learning reminders.