Two sources describe how enabling TypeScript strict mode (including strict null checks) reduces, but does not eliminate, null/undefined-related production errors. They note that strict: true activates static checks such as strictNullChecks, noImplicitAny, and strictFunctionTypes, but the TypeScript compiler cannot verify the shape of data that enters from outside the program (for example, API responses, JSON.parse results, database outputs, or HTTP headers). The articles identify four recurring patterns where code can compile cleanly yet still fail at runtime: (1) assertion functions declared as type guards but implemented without throwing when values are null/undefined, causing the type to “lie”; (2) external libraries with imprecise typings (including any or outdated @types) that do not match runtime behavior; (3) Prisma ORM optional relations where a relation field may be absent from the returned object if a query does not include/select it as expected, even though the schema suggests it may be null; and (4) JSON.parse used without runtime validation, since JSON.parse returns any. The suggested mitigation is to validate at system boundaries using runtime validators such as Zod, align Prisma include/select with downstream usage, and ensure assertion functions throw appropriately rather than only logging.
Strict TypeScript null checks still miss runtime failures in Next.js and Prisma
Two sources describe how enabling TypeScript strict mode (including strict null checks) reduces, but does not eliminate, null/undefined-related production errors. They note that strict: true activates...
- TypeScript strict: true enables static checks (including strictNullChecks) but does not validate runtime data shapes from external sources.
- Assertion functions used as type guards can produce runtime errors if they do not throw when encountering null/undefined.
- External libraries with imprecise or outdated typings can allow incorrect assumptions that fail at runtime.
- In Prisma 5, optional relations may not be present in query results unless include/select matches what the code later accesses.
- JSON.parse returns any in TypeScript, so runtime validation (e.g., with Zod) is needed to enforce expected structure.
Strict Null Checks in TypeScript: What the Compiler Won't Tell You and Where It Actually Hurts in Production I was reviewing a Server Action in Next.js — something that compiled without a single error, clean types, green lint — when a Cannot read properties of undefined (reading 'id') hit in runtime. Three minutes of retrospective later I understood the problem: the compiler had given me the green light and I believed it. That was a mistake. My thesis, straight up: strict null checks is necessary but not sufficient. The TypeScript compiler is the first filter in the system, not the last. Real null safety comes from runtime validation at the edges of the system — and there are four concrete patterns where the compiler says OK and production says otherwise. This isn't a "turn on strict: true and you're done" post. It's a map of where the compiler fails silently, using the Next.js 16 + Prisma ORM 5 + strict TypeScript stack as a concrete reference. Strict Null Checks in TypeScript Production: What the Flag Actually Activates When you enable strict: true in tsconfig.json, TypeScript turns on a more restrictive set of checks. According to the official docs, strict is a shorthand that includes, among others: strictNullChecks — null and undefined are not assignable to other types without an explicit guard. noImplicitAny — no variable can be left without an inferred type. strictFunctionTypes — function types are checked contravariantly. // tsconfig.json — recommended base configuration { "compilerOptions": { "strict": true, "target": "ES2022", "lib": ["ES2022"], "moduleResolution": "bundler" } } What strict does not do is verify that data arriving from the outside — an API, a JSON.parse, a database response, an HTTP header — actually has the shape the type declares. The compiler works with static types; runtime works with real data. Two different worlds, and the gap between them is exactly where bugs live. The 4 Patterns Where the Compiler Says OK and Runtime Blows Up Anyway Pattern 1 — Badly Typed Assertion Functions Assertion functions are functions the compiler treats as type guards. If you declare them wrong, TypeScript trusts them blindly. // ⚠️ Assertion function that doesn't do what it promises function assertDefined<T>(val: T | null | undefined): asserts val is T { // You forgot the throw — TypeScript won't catch this // The compiler still marks val as T after this call if (val === null || val === undefined) { console.warn("null value detected"); // log without throw } } const userId: string | null = getUserId(); assertDefined(userId); // After this, TypeScript believes userId is string // But if it was null, the console.warn didn't stop the flow console.log(userId.toUpperCase()); // TypeError in runtime The compiler accepts the asserts val is T contract without checking the function body. If the assertion doesn't throw, the type is lying. The fix is simple but not obvious: // ✅ Correct assertion function — the throw is mandatory function assertDefined<T>(val: T | null | undefined): asserts val is T { if (val === null || val === undefined) { throw new Error(`Required value was null or undefined`); } } Pattern 2 — Libraries with Imprecise Types or Implicit any Plenty of ecosystem libraries publish types in @types/ that don't always reflect actual return values. The most common case: a function typed as string | undefined that returns null in certain codepaths, or the other way around. // Example with a hypothetical cookie parsing library import { parseCookie } from "some-cookie-lib"; const sessionId: string = parseCookie(req.headers.cookie, "session"); // The lib is typed as string — but it can return null at runtime // TypeScript doesn't complain because it trusts the declared type The warning sign is when you see as string scattered around, or when a library returns a broad type like any or Record<string, unknown>. At that point, the compiler delegates responsibility to whatever type you declare — and if that type is optimistic, you've already lost. Checklist for external libraries: Signal in the types Risk What to do any return High Validate with Zod at point of use Outdated @types/ types Medium Check the lib's CHANGELOG `string undefined when it could be null` Medium Auto-generated types (OpenAPI, etc.) Variable Validate at the entry boundary Pattern 3 — Optional Prisma ORM 5 Relations This one has surprised me the most working with Prisma. When you have an optional relation in the schema — user User? — Prisma types it as User | null. So far so good. The problem shows up when you do an include and then try to access the relation without having selected that field. // schema.prisma // model Post { // id Int @id // author User? @relation(fields: [authorId], references: [id]) // authorId Int? // } // ❌ The compiler accepts this — runtime can blow up const post = await prisma.post.findUnique({ where: { id: 1 }, // No include of author }); // TypeScript infers post.author as User | null | undefined // based on the generated type — but if you didn't include it, // author simply doesn't exist on the returned object if (post?.author?.name) { console.log(post.author.name); // undefined at runtime, not null } Prisma 5 generates types that reflect the schema, but not the exact shape of each query. If you don't include the relation in include, the field doesn't come back in the object — and the generated type doesn't express that with enough granularity. The fix: // ✅ Explicit result typing with the include const post = await prisma.post.findUnique({ where: { id: 1 }, include: { author: true }, // now the type correctly includes author }); // TypeScript now knows post.author can be User | null (optional relation) // and forces you to guard it before using it if (post && post.author) { console.log(post.author.name); } The practical rule: in Prisma, the generated type reflects the schema, not the query. Always make your include/select match what the downstream code expects to consume. Pattern 4 — JSON.parse Without Runtime Validation This is the most classic one and the most underestimated. JSON.parse returns any in TypeScript — the compiler has no idea what shape that JSON has until runtime. // ❌ The compiler accepts this completely async function getConfiguration(): Promise<{ timeout: number; endpoint: string }> { const raw = await fs.readFile("config.json", "utf-8"); return JSON.parse(raw); // returns any — TypeScript trusts the declared return type } const config = await getConfiguration(); // config.timeout could be undefined, string, null — the compiler doesn't know const ms = config.timeout * 1000; // NaN or TypeError at runtime The solution is to validate at the boundary. Zod is the tool that fits best in this stack: // ✅ Validation with Zod at the external data entry point import { z } from "zod"; const ConfigSchema = z.object({ timeout: z.number().positive(), endpoint: z.string().url(), }); async function getConfiguration() { const raw = await fs.readFile("config.json", "utf-8"); const parsed = JSON.parse(raw); return ConfigSchema.parse(parsed); // throws ZodError if shape doesn't match } // Now the inferred type is exactly { timeout: number; endpoint: string } // and runtime guarantees the shape before the data reaches the rest of the code const config = await getConfiguration(); const ms = config.timeout * 1000; // safe The same pattern applies to Server Actions in Next.js that receive form data, to external API responses, and to any data that crosses the system boundary. Common Mistakes When Configuring Strict Null Checks Three mistakes show up constantly when teams enable strict on an existing codebase: 1. Turning off individual checks to make it compile // ❌ This defeats the entire purpose of strict { "compilerOptions": { "strict": true, "strictNullChecks": false } } If a check breaks too much existing code, the right path is to migrate progressively with annotated and dated // @ts-expect-error comments — not to disable the flag globally. 2. Using the non-null assertion operator (!) without a real guard // ❌ The ! operator tells the compiler "trust me" // but does zero verification at runtime const name = user!.name; // TypeError if user is null Every ! in the codebase is potential technical debt. If you see more than five ! in a single file, that's a signal that the types aren't accurately modeling the domain's reality. 3. Confusing strict in Next.js config with strict in tsconfig next.config.js has a typescript.ignoreBuildErrors option that, when set to true, completely bypasses the compiler during the build. The strict in tsconfig.json means nothing if the build never fails on type errors. Decision Checklist: Where to Validate and Where to Trust the Compiler Before deciding whether to add runtime validation or trust the static type, run through this checklist: Question Yes No Does the data come from outside the process? (API, file, DB, form) Validate with Zod Compiler is enough Does the library have any types or outdated @types/? Add explicit guard Compiler is enough Are you using custom assertion functions? Verify they throw — Is the Prisma relation in the include? Type is precise Add defensive guard Does the type use ! to suppress a null? Revisit the domain model — Rule of thumb: if the data crossed a system boundary (network, disk, form, environment variable), validate at runtime. If the data is internal to the process and the type was inferred by TypeScript, the compiler is enough. Limits of This Guide What you can't conclude from this post without more evidence: How many production bugs come from each pattern — that depends on the specific codebase, test coverage, and team maturity. Whether Zod is always the best option over alternatives like Valibot or ArkType — there are bundle size and ergonomics trade-offs that deserve their own analysis. Whether these patterns apply equally in a codebase using tRPC or GraphQL with codegen — those systems have their own validation layers that change the equation. What you can conclude: the four patterns are reproducible, have concrete solutions, and apply directly to the Next.js 16 + Prisma 5 + strict TypeScript stack. FAQ — Strict Null Checks TypeScript Production With strict: true enabled, can I trust there are no nulls at runtime? No. strict: true guarantees the compiler warns you when a type can be null or undefined — but it can't verify data coming in from outside the process. Data from APIs, forms, files, and databases needs additional runtime validation. Does Prisma ORM generate types that exactly reflect what each query returns? Partially. Prisma 5 infers the type from the schema and from the query's include/select. If you don't include a relation, the field won't be on the returned object — but the generated type may not express that with enough precision in all cases. The safe practice is to always make the include match what downstream code consumes. When does it make sense to use // @ts-expect-error instead of properly fixing the type? Only in two cases: when you're progressively migrating a legacy codebase to strict (annotated with a comment explaining why and an expected resolution date), or when you're deliberately testing an error. In stable production code, @ts-expect-error without justification is technical debt with an unknown expiry date. Does JSON.parse always return any? Yes, by design. TypeScript can't know the shape of the JSON until runtime. The only way to recover a concrete type is to validate the result with a library like Zod or write manual type guards. Manual guards don't scale well; Zod scales better. Are assertion functions a bad practice? Not necessarily. They're a legitimate tool in TypeScript's type system. The problem is using them without a real throw — in that case, the contract you declare isn't fulfilled at runtime and the compiler can't detect it. With a proper throw, they're a clean way to do imperative narrowing. Does it make sense to migrate to strict null checks in a large codebase that doesn't have it? Yes, but with a strategy. The practical approach is to enable strict: true and use annotated @ts-expect-error to silence existing errors, then resolve them module by module — prioritizing system boundaries first (APIs, parsers, DB adapters) — and never disable strictNullChecks individually just to make it compile faster. The Compiler Is the First Filter, Not the Last Working with strict TypeScript in Next.js 16 and Prisma 5 changed how I think about type safety. Not as a binary "it compiled = it's safe" but as a chain: the compiler filters static errors, runtime validation filters errors at the boundaries, and integration tests cover the rest. The four patterns in this post — assertion functions without throw, libraries with imprecise types, optional Prisma relations without include, and JSON.parse without validation — have one thing in common: they all pass the compiler and they can all fail at runtime. The difference between teams that catch these before production and those that don't is systematic: the first group puts validation at the boundary and doesn't assume the compiler solves what it can't see. My practical stance: every time data enters the system from outside, Zod or equivalent. Every assertion function with a real throw. Every Prisma include reflecting what the downstream code actually needs. And zero ! operators without a real guard behind them. If you're working with a TypeScript codebase that mixes strict and legacy patterns, the concrete next step is to find every JSON.parse without validation and start there — it's the most common boundary and the easiest one to fix first. If you want to go deeper on system boundaries with TypeScript, I have related posts that can add context: DeepSeek API in TypeScript, Node.js and the event loop as a stack component, and Docker healthchecks in production all touch on the difference between what the system promises and what it delivers. Original sources: TypeScript Handbook — Strict Mode: https://www.typescriptlang.org/tsconfig#strict Zod Documentation: https://zod.dev/ This article was originally published on juanchi.dev
1 hour agoStrict null checks en TypeScript: lo que el compilador no te dice y dónde sí duele en producción Estaba revisando un Server Action en Next.js — algo que compilaba sin un solo error, tipos limpios, lint verde — cuando llegó un Cannot read properties of undefined (reading 'id') en runtime. Tres minutos de retrospectiva después entendí el problema: el compilador me había dado luz verde y yo lo creí. Eso fue un error. Mi tesis, sin rodeos: strict null checks es necesario pero insuficiente. El compilador de TypeScript es el primer filtro del sistema, no el último. La verdadera seguridad contra nulls viene de validación en runtime en los bordes del sistema — y hay cuatro patrones concretos donde el compilador dice OK y producción dice otra cosa. No es un post de "activá strict: true y listo". Es un mapa de dónde el compilador falla en silencio, con el stack Next.js 16 + Prisma ORM 5 + TypeScript estricto como referencia concreta. Strict null checks en TypeScript producción: qué activa la flag y qué no Cuando habilitás strict: true en el tsconfig.json, TypeScript activa un conjunto de checks más restrictivos. Según la documentación oficial, strict es un shorthand que incluye, entre otros: strictNullChecks — null y undefined no son asignables a otros tipos sin una guarda explícita. noImplicitAny — ninguna variable puede quedarse sin tipo inferido. strictFunctionTypes — los tipos de función se verifican contravariante. // tsconfig.json — configuración base recomendada { "compilerOptions": { "strict": true, "target": "ES2022", "lib": ["ES2022"], "moduleResolution": "bundler" } } Lo que strict no hace es verificar que los datos que llegan desde el exterior —una API, un JSON.parse, una respuesta de base de datos, un header HTTP— tengan la forma que el tipo declara. El compilador trabaja con tipos estáticos; runtime trabaja con datos reales. Son dos mundos distintos y la brecha entre ellos es donde aparecen los bugs. Los 4 patrones donde el compilador dice OK y runtime te revienta igual Patrón 1 — Assertion functions mal tipadas Las assertion functions son funciones que el compilador trata como guardas de tipo. Si las declarás mal, TypeScript confía en ellas ciegamente. // ⚠️ Assertion function que no hace lo que promete function assertDefined<T>(val: T | null | undefined): asserts val is T { // Olvidaste el throw — TypeScript no lo detecta // El compilador igual marca val como T después de esta llamada if (val === null || val === undefined) { console.warn("valor null detectado"); // log sin throw } } const userId: string | null = obtenerUserId(); assertDefined(userId); // Después de acá, TypeScript cree que userId es string // Pero si era null, el console.warn no detuvo el flujo console.log(userId.toUpperCase()); // TypeError en runtime El compilador acepta el contrato de asserts val is T sin verificar el cuerpo de la función. Si la assertion no lanza un error, el tipo miente. La corrección es simple pero no obvia: // ✅ Assertion function correcta — el throw es obligatorio function assertDefined<T>(val: T | null | undefined): asserts val is T { if (val === null || val === undefined) { throw new Error(`Valor requerido era null o undefined`); } } Patrón 2 — Librerías sin tipos precisos o con any implícito Muchas librerías del ecosistema publican tipos en @types/ que no siempre reflejan los retornos reales. El caso más común: una función tipada como string | undefined que en ciertos codepaths devuelve null, o viceversa. // Ejemplo con una librería hipotética de parseo de cookies import { parseCookie } from "alguna-lib-de-cookies"; const sessionId: string = parseCookie(req.headers.cookie, "session"); // La lib está tipada como string — pero puede devolver null en runtime // TypeScript no protesta porque confía en el tipo declarado La señal de alerta es cuando ves as string o cuando una librería retorna un tipo amplio como any o Record<string, unknown>. En ese punto, el compilador delega la responsabilidad al tipo que vos declarás — y si ese tipo es optimista, perdiste. Checklist para librerías externas: Señal en los tipos Riesgo Qué hacer Retorno any Alto Validar con Zod en el punto de uso Tipos en @types/ desactualizados Medio Revisar el CHANGELOG de la lib `string undefined cuando podría ser null` Medio Tipos generados automáticamente (OpenAPI, etc.) Variable Validar en el borde de entrada Patrón 3 — Relaciones opcionales de Prisma ORM 5 Este es el que más me ha sorprendido trabajando con Prisma. Cuando tenés una relación opcional en el schema — user User? — Prisma la tipea como User | null. Hasta acá bien. El problema aparece cuando hacés un include y después intentás acceder a la relación sin haber guardado ese campo en el select. // schema.prisma // model Post { // id Int @id // author User? @relation(fields: [authorId], references: [id]) // authorId Int? // } // ❌ El compilador acepta esto — runtime puede explotar const post = await prisma.post.findUnique({ where: { id: 1 }, // Sin include de author }); // TypeScript infiere post.author como User | null | undefined // según el tipo generado — pero si no hiciste el include, // author directamente no existe en el objeto retornado if (post?.author?.name) { console.log(post.author.name); // undefined en runtime, no null } Prisma 5 genera tipos que reflejan el schema, pero no el shape exacto de cada query. Si no incluís la relación en el include, el campo no viene en el objeto — y el tipo generado no lo expresa con suficiente granularidad. La corrección: // ✅ Tipado explícito del resultado con el include const post = await prisma.post.findUnique({ where: { id: 1 }, include: { author: true }, // ahora el tipo incluye author correctamente }); // TypeScript ahora sabe que post.author puede ser User | null (relación opcional) // y lo fuerza a que lo guardes antes de usarlo if (post && post.author) { console.log(post.author.name); } La regla práctica: en Prisma, el tipo generado refleja el schema, no la query. Siempre hacé coincidir el include/select con lo que el código downstream espera consumir. Patrón 4 — JSON.parse sin validación de runtime Este es el más clásico y el que más se subestima. JSON.parse retorna any en TypeScript — el compilador no puede saber qué forma tiene ese JSON hasta que llegue en runtime. // ❌ El compilador acepta esto completamente async function obtenerConfiguracion(): Promise<{ timeout: number; endpoint: string }> { const raw = await fs.readFile("config.json", "utf-8"); return JSON.parse(raw); // retorna any — TypeScript confía en el tipo de retorno declarado } const config = await obtenerConfiguracion(); // config.timeout podría ser undefined, string, null — el compilador no sabe const ms = config.timeout * 1000; // NaN o TypeError en runtime La solución está en validar en el borde. Zod es la herramienta que mejor encaja en este stack: // ✅ Validación con Zod en el punto de entrada del dato externo import { z } from "zod"; const ConfigSchema = z.object({ timeout: z.number().positive(), endpoint: z.string().url(), }); async function obtenerConfiguracion() { const raw = await fs.readFile("config.json", "utf-8"); const parsed = JSON.parse(raw); return ConfigSchema.parse(parsed); // lanza ZodError si el shape no coincide } // Ahora el tipo inferido es exactamente { timeout: number; endpoint: string } // y el runtime garantiza la forma antes de que el dato llegue al resto del código const config = await obtenerConfiguracion(); const ms = config.timeout * 1000; // seguro El mismo patrón aplica a Server Actions en Next.js que reciben datos de formularios, a responses de APIs externas y a cualquier dato que cruce el borde del sistema. Errores comunes al configurar strict null checks Hay tres errores que aparecen seguido cuando equipos habilitan strict en un codebase existente: 1. Apagar checks individuales para que compile // ❌ Esto anula el propósito de strict { "compilerOptions": { "strict": true, "strictNullChecks": false } } Si un check rompe demasiado código existente, el camino correcto es migrar progresivamente con // @ts-expect-error anotado y fechado — no desactivar la flag globalmente. 2. Usar non-null assertion operator (!) sin guarda real // ❌ El operador ! le dice al compilador "confiá en mí" // pero no hace ninguna verificación en runtime const nombre = usuario!.nombre; // TypeError si usuario es null Cada ! en el codebase es una deuda técnica potencial. Si ves más de cinco ! en un archivo, es una señal de que los tipos no están modelando bien la realidad del dominio. 3. Confundir que strict en Next.js config y en tsconfig son cosas distintas next.config.js tiene una opción typescript.ignoreBuildErrors que, si está en true, bypassea completamente el compilador en el build. El strict del tsconfig.json no sirve de nada si el build nunca falla por errores de tipos. Checklist de decisión: dónde validar y dónde confiar en el compilador Antes de decidir si agregar validación de runtime o confiar en el tipo estático, pasá por esta checklist: Pregunta Sí No ¿El dato viene de fuera del proceso? (API, archivo, DB, formulario) Validar con Zod El compilador alcanza ¿La librería tiene tipos any o tipos de @types/ desactualizados? Agregar guarda explícita El compilador alcanza ¿Usás assertion functions propias? Verificar que lancen throw — ¿La relación de Prisma está en el include? El tipo es preciso Agregar guarda defensiva ¿El tipo usa ! para suprimir un null? Revisitar el modelo de dominio — Regla de dedo: si el dato cruzó un borde del sistema (red, disco, formulario, variable de entorno), validá en runtime. Si el dato es interno al proceso y el tipo fue inferido por TypeScript, el compilador alcanza. Límites de esta guía Lo que no podés concluir de este post sin más evidencia: Cuántos bugs en producción vienen de cada patrón — eso depende del codebase específico, la cobertura de tests y la madurez del equipo. Si Zod es siempre la mejor opción frente a alternativas como Valibot o ArkType — hay trade-offs de bundle size y ergonomía que merecen análisis propio. Si estos patrones aplican igual en un codebase que usa tRPC o GraphQL con codegen — esos sistemas tienen sus propias capas de validación que cambian la ecuación. Lo que sí podés concluir: los cuatro patrones son reproducibles, tienen solución concreta y aplican directamente al stack Next.js 16 + Prisma 5 + TypeScript estricto. FAQ — strict null checks TypeScript producción ¿Con strict: true activado puedo confiar en que no hay nulls en runtime? No. strict: true garantiza que el compilador te avisa cuando un tipo puede ser null o undefined — pero no puede verificar los datos que entran desde afuera del proceso. Los datos de APIs, formularios, archivos y bases de datos necesitan validación en runtime adicional. ¿Prisma ORM genera tipos que reflejan exactamente lo que retorna cada query? Parcialmente. Prisma 5 infiere el tipo a partir del schema y del include/select de la query. Si no hacés include de una relación, el campo no va a estar en el objeto retornado — pero el tipo generado puede no expresar eso con suficiente precisión en todos los casos. La práctica segura es hacer coincidir siempre el include con lo que el código downstream consume. ¿Cuándo tiene sentido usar // @ts-expect-error en lugar de resolver el tipo correctamente? Solo en dos casos: cuando estás migrando un codebase legacy a strict de forma progresiva (anotado con un comentario que explique el motivo y una fecha de resolución esperada), o cuando estás testeando un error deliberado. En código de producción estable, @ts-expect-error sin justificación es una deuda técnica con fecha de vencimiento desconocida. ¿JSON.parse siempre retorna any? Sí, por diseño. TypeScript no puede saber la forma del JSON hasta runtime. La única forma de recuperar un tipo concreto es validar el resultado con una librería como Zod o escribir guardas de tipo manuales. Las guardas manuales escalan mal; Zod escala mejor. ¿Las assertion functions son una mala práctica? No necesariamente. Son una herramienta legítima del sistema de tipos de TypeScript. El problema es usarlas sin un throw real — en ese caso, el contrato que declarás no se cumple en runtime y el compilador no puede detectarlo. Con un throw correcto, son una forma limpia de narrowing imperativo. ¿Tiene sentido migrar a strict null checks en un codebase grande que no lo tiene? Sí, pero con estrategia. La forma práctica es habilitar strict: true y usar @ts-expect-error anotado para silenciar los errores existentes, resolverlos de a módulos priorizando los bordes del sistema primero (APIs, parsers, adapters de DB), y nunca desactivar strictNullChecks individualmente para que compile más rápido. El compilador es el primer filtro, no el último Trabajar con TypeScript estricto en Next.js 16 y Prisma 5 cambió cómo pienso la seguridad de tipos. No como un binario "compiló = seguro" sino como una cadena: el compilador filtra los errores estáticos, la validación de runtime filtra los errores en los bordes, y los tests de integración cubren el resto. Los cuatro patrones de este post — assertion functions sin throw, librerías con tipos imprecisos, relaciones opcionales de Prisma sin include, y JSON.parse sin validación — tienen algo en común: todos pasan el compilador y todos pueden fallar en runtime. La diferencia entre los equipos que los atrapa antes de producción y los que no es sistemática: los primeros ponen validación en el borde y no asumen que el compilador resuelve lo que no puede ver. Mi postura práctica: cada vez que un dato entra al sistema desde afuera, Zod o equivalente. Cada assertion function con un throw real. Cada include de Prisma reflejando lo que el código downstream necesita. Y cero operadores ! sin guarda real detrás. Si trabajás con una codebase TypeScript que mezcla strict y patrones legacy, el siguiente paso concreto es buscar todos los JSON.parse sin validación y empezar ahí — es el borde más común y el más fácil de resolver primero. Si te interesa profundizar en los bordes del sistema con TypeScript, tengo posts relacionados que pueden sumar contexto: DeepSeek API en TypeScript, Node.js y el event loop como pieza del stack y Docker healthchecks en producción también tocan la diferencia entre lo que el sistema promete y lo que entrega. Fuentes originales: TypeScript Handbook — Strict Mode: https://www.typescriptlang.org/tsconfig#strict Zod Documentation: https://zod.dev/ Este artículo fue publicado originalmente en juanchi.dev
1 hour ago
Video shows England youth players brawling during camp while team-mates record
Multiple reports describe footage from an England youth team camp in which two youth players are seen fighting in a hote...
Tips for starting and staying safe when biking to work
Several outlets offer practical guidance for people who want to commute by bicycle. The advice focuses on getting starte...
‘The Odyssey’ Imax 70mm screenings sell out as theaters limit large-format showings
Christopher Nolan’s “The Odyssey” opens in theaters in his preferred large-format presentation, filmed entirely with IMA...