Cookies Psst! Do you accept cookies?

We use cookies to enhance and personalise your experience.
Please accept our cookies. Checkout our Cookie Policy for more information.

Practical Code Architecture 🛠️ for Beginners (in JavaScript)

There are many ways to accomplish the same thing in programming. Code architecture is basically the study of "which way is better".

Note: In this article, "better" means code that's generally considered easier to read, scale, and maintain.

What's in this article?

This article will briefly introduce three major code architecture subjects, and explain how I've found them helpful in my 10+ years of experience as a full-stack JavaScript developer. This is intended to be a simple and practical introduction, rather than comprehensive and technical.

Subjects:

  • Paradigms
  • Principles
  • Patterns

Paradigms 🏛️

Paradigms are generally the broadest and (in my opinion) most important subject. They're sort of like "styles" to write code in. In my experience, the ones most relevant to web development are declarative/imperative code, Reactive Programming, and Functional Programming.

Declarative/Imperative Code

Declarative code is code that combines more work into fewer statements/expressions, while imperative is the opposite.

More Imperative

let x = 0;

...

if(something){
  x = 5;
}

...

return x;

More Declarative

const x = something ? 5 : 0;

...

return x;

In general, more declarative code is considered better. In the above examples, imagine if each ... were 100 lines of code. It would be much harder to figure out what value is returned in the imperative example.

Reactive Programming

Reactive Programming is about writing code to automatically trigger operations in response to data changing.

Imagine you're making the shopping cart page of shopping app. The cart has an underlying source of data we'll call items. Imagine you have a requirement to navigate back to the homepage if there are no items left in the cart.

One Approach

let items = [];

function onRemoveButtonClicked(item) {
  items = ...;
  if(items.length == 0){
    navigateHome();
  }
}

function onClearButtonClicked() {
  items = ...;
  if(items.length == 0){
    navigateHome();
  }
}

But what should really trigger the navigation? Clicking "Remove Item"? Clicking "Clear Items"? The true answer is "changing the data in items".

More Reactive

let items = [];

function setItems(newItems){
  items = newItems;
  if(items.length == 0){
    navigateHome();
  }
}

function onRemoveButtonClicked(item) {
  setItems(...);
}

function onClearButtonClicked() {
  setItems(...);
}

A more reactive approach could be to use setItems in place of items = .... That way, "changing the data in items" will trigger the navigation whenever appropriate.

Fun Fact: This paradigm is where React.js gets its name!

Functional Programming

Functional Programming is basically writing declarative code, but also writing "pure functions".

A function is "pure" if it...

  1. Gets everything it needs from arguments only
  2. Does not affect the outside world in any way

Consider this example...

let z = 0;
console.log(sum(2, 2));
console.log(z);

It's very obvious what should happen. You should see 4, then 0 in the console. If sum is a pure function, then that result is guaranteed. But, imagine if sum was defined like this...

function sum(x, y){
  z = x + y;
  return x + y;
}

This example is "impure", because z = x + y affects the outside world. This makes the results less predictable and harder to keep track of, which tends to cause bugs.

Note: A thing that affects the outside world is called a "side effect".

Principles 📜

Principles are less broad than Paradigms. They're sort of like rules to follow. The most popular Principles are the so-called SOLID principles.

S - Single Responsibility Principle
O - Open-closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle

Although these principles were intended to apply specifically to object-oriented programming, they can easily be applied to other types of "modules". Below are two that I've found particularly helpful...

Note: "Module" is a vague term in this field of study meaning a piece of code like a function, UI component, API route, file, etc. In JavaScript, "module" can also specifically mean a file that imports/exports things.

Single Responsibility Principle

The Single Responsibility Principle says that a module should have only one responsibility.

❌ Single Responsibility

const [area, volume] = getAreaAndVolume(rectangle);

✔️ Single Responsibility

const area = getArea(rectangle);
const volume = getVolume(rectangle);

In the above example, getAreaAndVolume clearly has more than one responsibility.

Dependency Inversion Principle

The Dependency Inversion Principle says that a module should only depend on generic "interfaces" provided by other modules, not specific details.

Note: "Interface" is another vague term which roughly means "the way to use" a module. In other programming languages, "interface" can also have a similar, but more specific meaning.

For example, imagine a UI component that needs to save some data. The UI component code shouldn't depend on the specific details of the data-saving code.

❌ Dependency Inversion

import { saveDataInLocalStorage } from "../data-store.js";

...

saveDataInLocalStorage(data);

✔️ Dependency Inversion

import { saveData } from "../data-store.js";

...

saveData(data);

In the above example, saveDataInLocalStorage is more specific than necessary. All we care about in the UI code is that we need to save data. We don't care about the details.

Patterns 📦

Patterns are even less broad than Principles. They're sort of like recipes you can use in your code.

Unidirectional Data Flow

One pattern called Unidirectional Data Flow can be implemented by rendering a UI simply as a reflection of the current state/data of the app.

❌ Unidirectional Data Flow

let items = [];

function onAddButtonClicked(item) {
  items = ...;
  document.querySelector("#item-list").innerHTML += `
    <div>${item.name}</div>
  `;
}

function onClearButtonClicked() {
  items = ...;
  document.querySelector("#item-list").innerHTML = ``;
}

✔️ Unidirectional Data Flow

let items = [];

function onAddButtonClicked(item) {
  items = ...;
  renderItems();
}

function onClearButtonClicked() {
  items = ...;
  renderItems();
}

In the above example, imagine the function renderItems just renders the current state of items at any time. Instead of updating individual parts of the UI depending on the situation, we can just call renderItems whenever items changes.

Conclusion

There's MUCH more to learn about code architecture out there. These are just a few concepts that I eventually learned throughout the years (sometimes the hard way). Hopefully this helps demystify the subject for others as well.

💬 Feedback/criticism are welcome below 👇

Last Stories

What's your thoughts?

Please Register or Login to your account to be able to submit your comment.