Skip to content

Facade pattern explained

The Facade Pattern is a structural design pattern that provides a simplified, unified interface to a larger body of complex code, libraries, or subsystems. Facades;

  • simplify usage where they hide internal details from clients.
  • unifies APIs by turning many moving parts into one coherent entry point.
  • decouples clients: clients don’t depend on internal subsystems directly.
  • are still extensible: advanced users can bypass the facade and talk to subsystems directly

In this article we are going to look at various areas where facade pattern is used in Javascript in the real world.

1. Facades - same API different environments

In a simple example console.log() facade works in two very different environments where under the hood, the implementation depends on the runtime either Node Streams (process.stdout) or Chrome DevTools protocol. when we write

js
console.log("User created:", { id: 42 });
  • In Node js the above console.log is just a thin wrapper around process.stdout.write().It formats the string, appends a newline, and writes it to the standard output stream as follows.
js
// Node.js internals simplified
console.log = function(...args) {
  const message = args.join(" ") + "\n";
  process.stdout.write(message);
};
  • In Chrome console.log talks to the DevTools protocol where the browser pipes messages into the DevTools console via a WebSocket-like channel.

2. Axios. A facade over fetch and XMLHttpRequest

Prior to fetch the only way to do HTTP on the browser was

js
// Raw XHR — verbose, callback-heavy
const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/users");
xhr.setRequestHeader("Content-Type", "application/json");

xhr.onload = function () {
  if (xhr.status >= 200 && xhr.status < 300) {
    const data = JSON.parse(xhr.responseText);
    console.log("User created:", data);
  } else {
    console.error("Request failed:", xhr.statusText);
  }
};

xhr.onerror = function () {
  console.error("Network error");
};

xhr.send(JSON.stringify({ name: "Alice" }));

When fetch arrived, things improved but were still verbose:

js
// Raw fetch — cleaner, but still noisy
const res = await fetch("/api/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Alice" }),
});

if (!res.ok) throw new Error(res.statusText);
const data = await res.json();

Now with the axios facade

js
// Facade: Axios
const { data } = await axios.post("/api/users", { name: "Alice" });

Under the hood Under the Hood axios does not just shorten syntax:

  • In browsers: Axios uses an XHR adapter (wrapping XMLHttpRequest) to maximize compatibility, handle progress events, and support older browsers.
  • In modern environments: Axios can use fetch (community adapters exist).
  • In Node.js: Axios uses the native http and https modules.

So Axios acts as a cross-environment facade, normalizing:

  • Request/response formatting
  • Error handling
  • Interceptors (middleware for logging, retries, auth)
  • Promises (unifying callback-based XHR into a Promise API)

3. ReactDOM: A Facade Over the DOM

If we were to manipulate the DOM directly we would;

js
const el = document.createElement("button");
el.textContent = "Click Me";
document.body.appendChild(el);

ReactDom facade gives us a declarative API

js
import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root"))
  .render(<button>Click Me</button>);

In the background

  • ReactDOM translates JSX into React.createElement calls.
  • The reconciler diffs the virtual tree against the real DOM.
  • Efficient updates are batched and applied only where neede

4. AWS SDK: A Facade Over Signed HTTP Requests

If we are to uploading a file to S3 manually it means we have to:

  • Craft an HTTP PUT request.
  • Generate an HMAC signature with your credentials.
  • Handle retries, redirects, and regional endpoints.
js
// Barebones S3 upload using fetch (painful)
const crypto = require("crypto");
const key = "my-secret-key"; // loaded securely

const signature = crypto
  .createHmac("sha256", key)
  .update("string-to-sign")
  .digest("base64");

await fetch("https://s3.amazonaws.com/my-bucket/photo.png", {
  method: "PUT",
  headers: { Authorization: `AWS ${signature}` },
  body: fileBuffer,
});

with AWS SDK facade auth, retries, endpoint resolution are hidden, indeed everything that distracts from your real goal to put this file in the cloud.

js
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client();
await s3.send(new PutObjectCommand({
  Bucket: "my-bucket",
  Key: "photo.png",
  Body: fileBuffer,
}));

5. AuthFacade: A Map-Based Facade for OAuth Providers

In web applications, supporting multiple login providers is common, without a facade, clients must know Google’s SDK, GitHub’s API, Twitter’s quirks. We can have a map-based facade that avoids constructor bloat and is extensible:

js
class AuthFacade {
  constructor(providers = {}) {
    this.providers = providers;
  }

  async login(provider, token) {
    const service = this.providers[provider];
    if (!service) throw new Error(`Unknown provider: ${provider}`);
    return service.verify(token);
  }
}

// Usage
const auth = new AuthFacade({
  google: googleAuth,
  github: githubAuth,
  twitter: twitterAuth,
});

// Client stays clean
const user = await auth.login("google", token);

// Adding LinkedIn later is zero-touch
auth.providers.linkedin = linkedinAuth;

javascript book

If this interested you, check out my Javascript Book

Enjoy every byte.