Error handling: unknown in catch, typed boundaries, and validation at the edges
Learn how TypeScript sharpens JavaScript error and input handling by forcing clearer assumptions at the edges.
by the end of this lesson you can
- →Starts from a boundary-safe input type
- →Narrows before property access
- →Keeps runtime validation visible rather than implied by types alone
Overview
JavaScript and Node developers already know that external data and thrown values can be messy. TypeScript helps most when it prevents that mess from leaking inward unchecked. Unknown, boundary typing, and runtime validation are the tools that stop the compiler from becoming a false comfort blanket.
In JavaScript/Node, you often
catch errors loosely and trust request bodies, environment variables, or JSON payloads after only light inspection.
In TypeScript, the common pattern is
to keep trust boundaries explicit, narrow unknown values before use, and validate external inputs before the rest of the code treats them as typed.
why this difference matters
This lesson should push back against the common beginner move of replacing uncertainty with any. The whole point is to make uncertainty visible until the code has earned stronger assumptions.
JavaScript/Node
try {
await saveUser(payload);
} catch (error) {
console.error(error.message);
}TypeScript
try {
await saveUser(payload);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
}
}Deeper comparison
JavaScript/Node version
function readName(body) {
return body.name.trim();
}TypeScript version
function readName(body: unknown): string {
if (
typeof body !== "object" ||
body === null ||
!("name" in body) ||
typeof body.name !== "string"
) {
throw new Error("invalid name");
}
return body.name.trim();
}Reflect
Why is unknown usually a healthier starting point than any when data or errors cross a trust boundary?
what a strong answer notices
A strong answer mentions that unknown preserves uncertainty honestly, forces proof before use, and keeps the boundary explicit instead of letting unchecked assumptions spread.
Rewrite
Rewrite this JavaScript request-body helper into TypeScript without hiding uncertainty behind any.
Rewrite this JavaScript/Node
function normalizeUser(body) {
return { name: body.name.trim(), role: body.role };
}what good looks like
- Starts from a boundary-safe input type
- Narrows before property access
- Keeps runtime validation visible rather than implied by types alone
Practice
Design a TypeScript Express or Fastify handler boundary that validates incoming JSON, narrows it to a trusted shape, and then passes that trusted value into the domain layer.
success criteria
- Separates edge validation from internal typed logic
- Uses unknown or equivalent boundary discipline intentionally
- Makes clear where runtime checks still matter in a TypeScript codebase
Common mistakes
- Replacing unknown with any the moment the compiler asks a useful question.
- Typing raw request bodies as trusted domain objects before validation happens.
- Catching broadly and assuming every thrown value is an Error instance with a message.
takeaways
- ●This lesson should push back against the common beginner move of replacing uncertainty with any. The whole point is to make uncertainty visible until the code has earned stronger assumptions.
- ●A strong answer mentions that unknown preserves uncertainty honestly, forces proof before use, and keeps the boundary explicit instead of letting unchecked assumptions spread.
- ●Separates edge validation from internal typed logic