TypeScript checks your code before it runs. Once it ships, the types are gone. The compiler cannot tell you what a third-party API actually returned at 3 AM on a Sunday. I've written full-stack TypeScript with Nuxt and Vue for about six years. The production bugs I remember most were rarely logic errors. Most came from one assumption: "the data has the shape I typed it as." The examples below are small and made up for this article. They are not taken from a specific project. The problem: a type assertion is a promise, not a check // server/api/user/[id].get.ts interface User { id: number email: string plan: 'free' | 'pro' } export default defineEventHandler(async (event) => { const id = getRouterParam(event, 'id') const user = await $fetch(`https://api.example.com/users/${id}`) as User return { label: user.email.toLowerCase(), plan: user.plan } }) Enter fullscreen mode Exit fullscreen mode This compiles. If the API one day returns email: null for accounts created through a different sign-up flow, user.email.toLowerCase() throws. The as User told the compiler to stop asking questions. It checked nothing. Fix: parse at the boundary with a schema A schema library such as Zod keeps the type and the runtime check in one place: import { z } from 'zod' const UserSchema = z.object({ id: z.number(), email: z.string().email().nullable(), plan: z.enum(['free', 'pro']), }) type User = z.infer export default defineEventHandler(async (event) => { const id = getRouterParam(event, 'id') const raw = await $fetch(`https://api.example.com/users/${id}`) const parsed = UserSchema.safeParse(raw) if (!parsed.success) { throw createError({ statusCode: 502, statusMessage: 'Upstream returned an unexpected shape', }) } const user: User = parsed.data return { label: user.email?.toLowerCase() ?? 'no email', plan: user.plan } }) Enter fullscreen mode Exit fullscreen mode Two things change. The null case is now a type error you have to handle, not a surprise in production. And a bad upstream response fails loudly with a clear status, instead of a vague TypeError deep in your code. Where to validate, and where not to bother You don't need a schema around every object. Validate where data crosses a trust boundary: Responses from APIs you don't control Request bodies and query params in your own server routes (readValidatedBody and getValidatedQuery in Nitro/H3 take a validator for this) localStorage, cookies and anything else the user can edit Output of any model or service whose format isn't guaranteed Inside your own code, after the boundary, trust the types. A tradeoff to be aware of Schemas cost a little bundle size and a little time per parse. On the server this rarely matters. In a hot client path, parse once when the data arrives, not on every render. Also, strict schemas can make you reject responses that were fine in practice. Decide per field: .passthrough() or .strip() for unknown keys, and be careful with strict enums on upstreams that add values without warning. Takeaway as SomeType is documentation. A schema is a check. Use assertions for things you actually know, and parse the rest at the edge.
Your TypeScript types disappear at runtime: validating data at the edges of a Nuxt app
Full Article
Original Source
Read the full article at Dev →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.