Cómo tener tipos en JavaScript sin TypeScript usando Zod y JSDoc

#javascript#jsdoc#typescript

Los tipos en JavaScript pueden parecer un lujo innecesario, pero cuando trabajas en proyectos personales o prototipos rápidos, tener algo de verificación puede ahorrarte horas de depuración.

Durante años, TypeScript ha sido la solución por excelencia para proyectos más grandes. Pero si eres como yo y prefieres mantener las cosas simples en proyectos pequeños, la configuración adicional se siente excesiva. Además, el código termina siendo tan diferente de JavaScript “puro” que no puedes simplemente copiarlo y pegarlo en el navegador.

¿Qué tal si te dijera que puedes tener lo mejor de ambos mundos? Tipos en tu editor, verificación en tiempo de ejecución, y todo funcionando en JavaScript puro sin compilar ni transpilar nada.

En esta publicación, exploraremos cómo usar Zod con JSDoc para lograr exactamente eso. Ya seas un desarrollador que busca una solución más ligera o alguien que quiere verificar sus tipos sin complicarse con la configuración de TypeScript, encontrarás perspectivas prácticas aquí.

Cómo usar JSDoc para tipos en JavaScript

Cuando quiero type hints para proyectos pequeños y/o personales, uso JSDoc, que me permite declarar tipos desde comentarios de JavaScript, además se puede compartir entre archivos.

// file: file-1.js
/**
 * @typedef Candy
 * @property {string} name
 * @property {"chocolate"|"gummy"} type
 */

// file: file-2.js
/** @type { import('./file-1').Candy[] } */
const candy = [
  { name: "Kit Kat", type: "chocolate" },
  { name: "Peach Rings", type: "gummy" },
];

Lo mejor de todo esto es que no necesitas configurar absolutamente nada. Y además, las sugerencias de tipo en el editor de texto aparecen automáticamente. Escribes . después de un objeto y boom — el autocompletado te muestra todas las propiedades disponibles.

Pero hay un detalle importante: JSDoc solo te da sugerencias en el editor. No hay verificación real de tipos en tiempo de ejecución. Puedes escribir código que rompa y el editor no te advertirá.

Para obtener esa verificación, puedes agregar @ts-check al principio de tu archivo:

// @ts-check
/** @type { import('./file-1').Candy } */
const candy = { name: "Kit Kat", type: "menta" }; // Error: "menta" no es válido

Ahora el editor te mostrará errores de tipo directamente en el archivo con esas líneas rojas que tanto nos gustan.

Validación de datos en runtime con Zod

Aquí es donde las cosas se ponen interesantes. JSDoc es genial para el editor, pero solo funciona ahí. En el navegador, tu código se ejecuta sin ninguna verificación. Los errores de tipo los descubres cuando algo falla en producción.

Zod es una librería que te permite definir esquemas de validación y usarlos en tiempo de ejecución. Lo que lo hace especial es que puedes inferir el tipo de TypeScript directamente desde el esquema:

import z from "zod";

const CandySchema = z.object({
 name: z.string(),
 type: z.enum(["chocolate", "gummy"]),
});

// Extrae el tipo para TypeScript automáticamente
type Candy = z.infer<typeof CandySchema>;

Ahora tienes dos cosas valiosas:

  1. Un esquema que puedes usar para validar datos en runtime
  2. Un tipo de TypeScript que puedes usar en tu código

El problema es que esto solo funciona en archivos .ts (TypeScript). Si estás usando JavaScript puro, no puedes usar z.infer de la misma manera.

Cómo combinar Zod y JSDoc para tipado en JavaScript

Aquí está el truco que hace todo esto posible: puedes inferir tipos de Zod directamente en tus comentarios JSDoc.

Funciona así:

import z from "zod";

// Declara tu esquema con Zod
const CandySchema = z.object({
  name: z.string(),
  type: z.enum(["chocolate", "gummy"]),
});

// Extrae el tipo inference como un tipo JSDoc
/** @typedef { z.infer<typeof CandySchema> Candy } */

Espera, ¿qué? Sí, es posible. El truco está en usar z.infer<typeof ...> directamente en el @typedef de JSDoc.

Ahora tienes lo mejor de ambos mundos:

Aquí tienes un ejemplo completo:

import z from "zod";

// file: file-1.js
const CandySchema = z.object({
  name: z.string(),
  type: z.enum(["chocolate", "gummy"]),
});

/** @typedef { z.infer<typeof CandySchema> Candy } */

// file: file-2.js
/** @type { import('./file-1').Candy[] } */
const candy = [
  { name: "Kit Kat", type: "chocolate" },
  { name: "Peach Rings", type: "gummy" },
];

Agrega un @ts-check al principio y tendrás verificación de errores en tiempo de edición, sin necesidad de archivos .ts.

Ejemplo del Mundo Real

Ahora que entiendes la técnica, veamos cómo aplicarla en un caso real. Imagina que estás construyendo un generador de sitios estáticos (SSG) donde recibes datos de algún origen y necesitas asegurarte de que los datos tienen la forma correcta.

Con esta aproximación, puedes:

  1. Definir tus esquemas con Zod en archivos JavaScript
  2. Validar los datos en tiempo de build
  3. Obtener sugerencias de tipo en tu editor mientras programas
import z from "zod";

// schemas/post.js
const PostSchema = z.object({
  title: z.string().min(1),
  slug: z.string().regex(/^[a-z0-9-]+$/),
  content: z.string(),
  publishDate: z.string().datetime(),
  tags: z.array(z.string()),
});
/** @typedef { z.infer<typeof PostSchema> Post } */

// validation/validate-post.js
function validatePost(data) {
  const result = PostSchema.safeParse(data);
  if (!result.success) {
    console.error("Error de validación:", result.error);
    throw new Error("Datos del post inválidos");
  }
  return result.data;
}

Mientras programas tu generador, el editor te mostrará sugerencias para cada propiedad del post. Y cuando ejecutes el build, Zod validará que los datos realmente tienen la forma esperada.

Mejores Prácticas y Recomendaciones

Buenas prácticas

Buenas prácticas

Lecturas Adicionales

Conclusión

Esta aproximación no es para todos los proyectos. Para aplicaciones grandes y equipos distribuidos, TypeScript con su verificación estática sigue siendo la mejor opción. Pero para proyectos personales, prototipos rápidos, o cuando simplemente quieres algo más ligero, combinar Zod con JSDoc te da:

Y todo eso sin necesidad de compilar, transpilar, o configurar nada. Solo JavaScript puro que puedes copiar y pegar directamente en el navegador.

Lo que más me gusta de esta técnica es que es “suficiente para ayudarme a ser productivo sin requerir que configure y mantenga herramientas TypeScript con el tiempo.” Para proyectos donde quiero moverme rápido pero no quiero sacrificar completamente la seguridad de tipos, esto es lo ideal.