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
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.
// 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
// 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:
// 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
// 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;
const el = document.createElement("button");
el.textContent = "Click Me";
document.body.appendChild(el);
ReactDom facade gives us a declarative API
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.
// 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.
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:
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