Skip to main content

Facade Pattern

Provide a single simplified interface to a complex subsystem.


The Problem​

You have a complex user registration flow: validate input, hash password, save to database, send welcome email. The naive approach scatters all these calls at every registration site.

// Scattered across controllers, routes, tests...
const validated = validateUser(data);
const hashed = await hashPassword(validated.password);
const user = await saveToDb({ ...validated, password: hashed });
await sendWelcomeEmail(user.email);

Every place that registers a user repeats the same sequence. Change the order or add a step? Hunt down every call site.


The Solution​

A function that orchestrates the subsystems. That's it.

const validateUser = (data: UserInput) => { /* ... */ };
const hashPassword = (password: string) => { /* ... */ };
const saveToDb = (user: User) => { /* ... */ };
const sendWelcomeEmail = (email: string) => { /* ... */ };

// Facade: one function, one place
async function registerUser(data: UserInput): Promise<User> {
const validated = validateUser(data);
const hashed = await hashPassword(validated.password);
const user = await saveToDb({ ...validated, password: hashed });
await sendWelcomeEmail(user.email);
return user;
}

// Client only sees registerUser
await registerUser({ name: "Alice", email: "alice@example.com", password: "..." });

No class needed. A function that calls functions is already a facade.

Absorbed by the Language

This solution doesn't use Pithos. That's the point.

In functional TypeScript, every function that simplifies a complex operation is a facade. Eidos exports a @deprecated createFacade() function that exists only to guide you here.


Live Demo​

Type a user ID and click Fetch. Toggle between "Without Facade" (6 steps executing visually one by one) and "With Facade" (one fetchUser(id) call). Same output, radically different experience.

Code
/**
* API Request Facade.
*
* 6 subsystem steps vs 1 facade function β€” same result, different experience.
* fetchUser() is the facade: one call orchestrates everything.
*/

import type { User } from "./types";

const USERS: Record<number, User> = {
1: { id: 1, name: "Alice Martin", email: "alice@example.com", role: "admin", lastLogin: "2026-03-25T14:30:00Z" },
2: { id: 2, name: "Bob Chen", email: "bob@example.com", role: "editor", lastLogin: "2026-03-24T09:15:00Z" },
3: { id: 3, name: "Clara Dupont", email: "clara@example.com", role: "viewer", lastLogin: "2026-03-20T18:45:00Z" },
42: { id: 42, name: "Douglas Adams", email: "douglas@example.com", role: "admin", lastLogin: "1979-10-12T00:00:00Z" },
};

const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));

export async function validateInput(userId: number): Promise<string> {
await delay(300);
if (!Number.isInteger(userId) || userId < 1) throw new Error(`Invalid user ID: ${userId}`);
return `ID ${userId} is valid`;
}

export async function buildAuthHeaders(): Promise<string> {
await delay(400);
return `Authorization: Bearer ${btoa(`demo-token-${Date.now()}`).slice(0, 20)}…`;
}

export async function serializeRequest(userId: number): Promise<string> {
await delay(200);
return `POST /api/users β†’ ${JSON.stringify({ id: userId, fields: ["name", "email", "role", "lastLogin"] }).slice(0, 40)}…`;
}

export async function executeFetch(userId: number): Promise<User> {
await delay(500);
const user = USERS[userId];
if (!user) throw new Error(`User ${userId} not found`);
return user;
}

export async function parseResponse(user: User): Promise<string> {
await delay(250);
if (!user.name || !user.email) throw new Error("Malformed response");
return `Parsed: ${user.name} <${user.email}>`;
}

export async function formatResult(user: User): Promise<User> {
await delay(150);
return { ...user, lastLogin: new Date(user.lastLogin).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" }) };
}

/** The Facade β€” one call, same result */
export async function fetchUser(userId: number): Promise<User> {
await validateInput(userId);
await buildAuthHeaders();
await serializeRequest(userId);
const raw = await executeFetch(userId);
await parseResponse(raw);
return formatResult(raw);
}
Result

API​

  • facade @deprecated β€” just write a function

Related