TypeScript
API with phantom
Learn safe API design with phantom types.
API design with phantoms
Phantom types prevent API functions from being called in the wrong order or with wrong arguments. For example, an HTTP request type can carry the authentication state as a phantom argument; thus send accepts only requests in the Authenticated state.
type Request<Auth> = { url: string };
type Anonymous = Request<"anonymous">;
type Authenticated = Request<"authenticated">;
function authenticate(r: Anonymous): Authenticated {
return r;
}
function send(r: Authenticated): string {
return r.url;
}
Compile-time safety
In a phantom-designed API, misuse becomes a compile error; for example, send cannot be called with an Anonymous request. This prevents runtime errors and makes the code safe.
const req: Anonymous = { url: "/api" };
const authed = authenticate(req);
send(authed); // valid
send(req); // error: Anonymous is not Authenticated
Exercise Authentication flow with phantoms
Define the Request<Auth> generic type (body url: string). Create Anonymous and Authenticated types with phantom arguments. Write authenticate(r: Anonymous): Authenticated and send(r: Authenticated): string functions. Start from an Anonymous value, authenticate it, get the URL via send, and print it via console.log.
Show example solution
type Request<Auth> = { url: string };
type Anonymous = Request<"anonymous">;
type Authenticated = Request<"authenticated">;
function authenticate(r: Anonymous): Authenticated {
return r;
}
function send(r: Authenticated): string {
return r.url;
}
const req: Anonymous = { url: "/api" };
const url = send(authenticate(req));
console.log(url);
Test yourself
-
What is the goal of safe API design with phantoms?
- Hiding API calls
- Ensuring API functions are called only in the correct order and state
- Writing shorter function signatures
- Parameter validation at runtime
Show answer
Ensuring API functions are called only in the correct order and state — With phantoms each API function accepts only values in a specific state; thus wrong order or missing steps become compile errors.
-
Which type does send accept
type Request<Auth> = { url: string };
type Anonymous = Request<"anonymous">;
type Authenticated = Request<"authenticated">;
function send(r: ____): string { return r.url; }
Show answer
type Request<Auth> = { url: string };
type Anonymous = Request<"anonymous">;
type Authenticated = Request<"authenticated">;
function send(r: Authenticated): string { return r.url; }
-
Phantom API parts
- Anonymous durumu
- not yet authenticated
- Authenticated durumu
- ready for send
- send(r: Authenticated)
- accepts only verified request
- Phantom Auth parametresi
- carries auth state
Next lesson: Summary - Phantom types