Generic Result and ApiResponse Types

A Generic ApiResponse

Most backends wrap every response in the same envelope. One generic type describes them all.

Example

type ApiResponse<T> =
  | { success: true; data: T }
  | { success: false; error: { code: string; message: string } };

type Product = { id: number; name: string; price: number };

function ok<T>(data: T): ApiResponse<T> {
  return { success: true, data };
}
function fail<T>(code: string, message: string): ApiResponse<T> {
  return { success: false, error: { code, message } };
}

function render(res: ApiResponse<Product[]>): string {
  if (res.success) return res.data.map((p) => `${p.name} Rs.${p.price}`).join(", ");
  return "Error " + res.error.code + ": " + res.error.message;
}

console.log(render(ok([{ id: 1, name: "Kettle", price: 1299 }])));
console.log(render(fail("SERVER", "Database unavailable")));

Output

Kettle Rs.1299
Error SERVER: Database unavailable

A Generic Result Type

Example

Result<T, E> is a popular pattern (from Rust) for functions that can fail without throwing. You will use it again in the Error Handling module.

type Result<T, E = string> = { ok: true; value: T } | { ok: false; error: E };

function parsePrice(input: string): Result<number> {
  const n = Number(input);
  if (Number.isNaN(n)) return { ok: false, error: `"${input}" is not a number` };
  if (n < 0) return { ok: false, error: "Price cannot be negative" };
  return { ok: true, value: n };
}

for (const input of ["499", "abc", "-5"]) {
  const r = parsePrice(input);
  console.log(input.padEnd(4), r.ok ? "value " + r.value : "error " + r.error);
}

Output

499  value 499
abc  error "abc" is not a number
-5   error Price cannot be negative