Type-Safe LLMs: Enforcing Strict Schemas in TypeScript & Express
An LLM is a probabilistic text generator that will, eventually, hand you a malformed object, an invented field, or prose where you asked for JSON. Your backend is a deterministic system that must never crash on it. The entire discipline of production AI engineering lives in that gap: treat every model response as untrusted input and validate it at the boundary, exactly as you would a request from the open internet.
Treat the LLM as an untrusted input boundary
You already validate user input before it touches your database. A model response deserves the same suspicion — more, because it is plausible enough to slip past a careless code path and corrupt state three layers deep. The rule is simple: no LLM output enters your application logic until it has passed a schema.
Structured output, then validate anyway
Ask the model for structured output and give it the schema — but never assume it complied. Parse every response through a runtime validator (zod is the standard) that doubles as your TypeScript type. If it fails, you retry with the validation error fed back in, or you fail the request loudly. What you never do is pass an unvalidated blob downstream.
const Verdict = z.object({
decision: z.enum(["guilty", "not_guilty"]),
confidence: z.number().min(0).max(1),
citations: z.array(z.string()).min(1),
});
const parsed = Verdict.safeParse(raw);
if (!parsed.success) return retryWithError(parsed.error);
return parsed.data; // now fully typed + safe to persistPush validation into Express middleware
Do not scatter parsing through your controllers. A small middleware that runs the schema, attaches the typed result to the request, and short circuits on failure keeps every AI-backed route uniform: the controller only ever sees data that already matched its contract. The same layer is where input validation and sanitization belong, so a request is clean before it reaches business logic in either direction.
Rate-limiting and defensive middleware
AI endpoints are expensive and abusable, which makes rate-limiting a correctness concern, not just a cost one. A per-identity limiter in front of the model — plus payload-size caps and strict request validation — is what kept the Hospital-API stable under load and cut a meaningful slice of server response time by rejecting bad traffic before it ever reached the work.
Fail loud, never silent
The worst outcome is not an error — it is a malformed response that looks fine and quietly writes garbage to your database. Schema validation turns that silent corruption into a visible, catchable failure. A crash you can see is a bug you can fix; a hallucination you persisted is an incident you discover weeks later.
The model is allowed to be wrong. Your backend is not allowed to believe it without proof. The schema is the proof.
This validation discipline underpins the Police RAG Agent and Hospital-API — typed outputs, hardened middleware, and zero tolerance for unvalidated model data.