StructuralTypeScriptverifiedVerified
Adapter Pattern in TypeScript
Converts the interface of a class into another interface that clients expect, allowing incompatible interfaces to work together.
How to Implement the Adapter Pattern in TypeScript
1Step 1: Define the target interface and the incompatible adaptee
interface Target {
request(): string;
}
class Adaptee {
specificRequest(): string {
return "Adaptee: specific behaviour";
}
}2Step 2: Implement the adapter that bridges both interfaces
class Adapter implements Target {
private adaptee: Adaptee;
constructor(adaptee: Adaptee) {
this.adaptee = adaptee;
}
request(): string {
const result = this.adaptee.specificRequest().split("").reverse().join("");
return `Adapter: (translated) ${result}`;
}
}3Step 3: Use the adapter transparently through the target interface
function clientCode(target: Target): void {
console.log(target.request());
}
const adaptee = new Adaptee();
console.log("Adaptee: " + adaptee.specificRequest());
const adapter = new Adapter(adaptee);
clientCode(adapter);
// Output:
// Adaptee: Adaptee: specific behaviour
// Adapter: (translated) ruoivaheb cificeps :eetpadA// Adapter Pattern – Production
// Normalises divergent API response shapes from v1 and v2 endpoints
// into a single canonical User record consumed by the application.
export interface User {
id: string;
fullName: string;
email: string;
avatarUrl: string | null;
}
// ── Legacy v1 shape ──────────────────────────────────────────────────────────
interface ApiV1User {
userId: number;
first_name: string;
last_name: string;
email_address: string;
profile_pic?: string;
}
// ── Modern v2 shape ──────────────────────────────────────────────────────────
interface ApiV2User {
uuid: string;
displayName: string;
contactEmail: string;
avatar: { url: string } | null;
}
// ── Adapter interface ────────────────────────────────────────────────────────
export interface UserAdapter<TRaw> {
adapt(raw: TRaw): User;
}
// ── V1 Adapter ───────────────────────────────────────────────────────────────
export class V1UserAdapter implements UserAdapter<ApiV1User> {
adapt(raw: ApiV1User): User {
return {
id: String(raw.userId),
fullName: `${raw.first_name} ${raw.last_name}`.trim(),
email: raw.email_address,
avatarUrl: raw.profile_pic ?? null,
};
}
}
// ── V2 Adapter ───────────────────────────────────────────────────────────────
export class V2UserAdapter implements UserAdapter<ApiV2User> {
adapt(raw: ApiV2User): User {
return {
id: raw.uuid,
fullName: raw.displayName,
email: raw.contactEmail,
avatarUrl: raw.avatar?.url ?? null,
};
}
}
// ── Factory ───────────────────────────────────────────────────────────────────
export function adaptUser(raw: ApiV1User | ApiV2User, version: "v1" | "v2"): User {
if (version === "v1") return new V1UserAdapter().adapt(raw as ApiV1User);
return new V2UserAdapter().adapt(raw as ApiV2User);
}
// ── Usage ─────────────────────────────────────────────────────────────────────
const legacyPayload: ApiV1User = {
userId: 42,
first_name: "Jane",
last_name: "Doe",
email_address: "[email protected]",
profile_pic: "https://cdn.example.com/jane.png",
};
const modernPayload: ApiV2User = {
uuid: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
displayName: "John Smith",
contactEmail: "[email protected]",
avatar: { url: "https://cdn.example.com/john.png" },
};
console.log(adaptUser(legacyPayload, "v1"));
console.log(adaptUser(modernPayload, "v2"));Adapter Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Adapter Pattern in the Real World
“A travel power adapter lets your American laptop plug (client) work in a European wall socket (adaptee) without modifying either. The adapter speaks both “languages”, translating the two-pin plug to the two-round-pin socket, making them interoperable without any changes on either side.”