Test Unitarios con Deno

¿Qué es BDD?

BDD (Behavior-Driven Development) es una metodología de desarrollo que coloca el comportamiento del software en el centro del proceso. A diferencia del testing unitario tradicional que se enfoca en “¿qué hace esta función?”, BDD se pregunta “¿cuál es el comportamiento que espera el usuario o el negocio?”.

En BDD, los tests se escriben usando una estructura clara y legible:

  • Given (Dado): El contexto o estado inicial.
  • When (Cuando): La acción que realizamos.
  • Then (Entonces): El resultado esperado.

Esta estructura hace que los tests sean casi documentación viva del sistema. No solo validan que el código funciona, sino que también comunican por qué y cómo debe comportarse.

Fortalezas de BDD

  1. Claridad: Los tests se leen como especificaciones. Cualquiera (técnico o no) puede entender qué se está probando.
  2. Documentación actualizada: El comportamiento del código siempre está documentado en los tests; no se queda obsoleto como un README abandonado.
  3. Colaboración efectiva: Diseñadores, QA y desarrolladores pueden discutir el comportamiento esperado usando el mismo lenguaje.
  4. Menos bugs por ambigüedad: Al escribir primero el comportamiento esperado, se reducen las interpretaciones erróneas.
  5. Refactoring seguro: Con tests BDD claros, puedes modificar la implementación sin miedo a romper el comportamiento.

Por qué BDD en Deno

Deno nació con premisas fuertes en seguridad, modernidad y simplicidad. No sorprende que su sistema de testing incorpore características que se alinean perfectamente con BDD:

  • Sistema de testing built-in sin dependencias externas (no necesitas instalar nada adicional).
  • Sintaxis moderna con describe() e it() para expresar comportamiento.
  • Seguridad de permisos por defecto (los tests se ejecutan en un sandbox; solo permites lo que necesitas).
  • TypeScript out-of-the-box sin configuración.

Esto convierte a Deno en una plataforma ideal para practicar BDD sin fricción.

Iniciemos un proyecto de prueba

Crearemos esta estructura de directorios para ir creando los archivos necesarios para una prueba simple.

Estructura de directorios
calc-project/
├── src/
│ └── Calc.ts
├── test/
│ └── Calc.test.ts
└── deno.json

El contenido del archivo deno.json es el siguiente. Recuerda que en este archivo es donde se declaran las dependencias y las tareas ejecutables.

1
2
3
4
5
6
7
{
"imports": {
"@std/assert": "jsr:@std/assert@^1.0.15",
"@std/expect": "jsr:@std/expect@^1.0.17",
"@std/testing": "jsr:@std/testing@^1.0.16"
}
}

Ahora revisemos el contenido de los archivos del proyecto. El primero es una clase con 3 operaciones matemáticas simples: suma, resta y división.

Calc.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* Simple calculator utilities used in examples and tests.
*
* Esta clase es intencionalmente pequeña y tiene métodos básicos
* para demostrar pruebas unitarias en estilo BDD con Deno.
*/
export class Calc {
/**
* Suma dos números.
* @param a - primer sumando
* @param b - segundo sumando
* @returns la suma de `a` y `b`
*/
public sum(a: number, b: number): number {
return a + b;
}

/**
* Resta dos números (a - b).
* @param a - minuendo
* @param b - sustraendo
* @returns la resta de `a` menos `b`
*/
public subtract(a: number, b: number): number {
return a - b;
}

/**
* Divide dos números (a / b).
*
* Lanza un error cuando `b` es 0 o no es un número válido (NaN),
* para evitar divisiones inválidas.
*
* @param a - dividendo
* @param b - divisor (no debe ser 0 ni NaN)
* @returns el resultado de `a / b`
* @throws {Error} si `b` es 0 o NaN
*/
public div(a: number, b: number): number {
if (b === 0 || Number.isNaN(b)) {
throw new Error("No se puede dividir por 0");
}
return a / b;
}
}

A continuación, veremos cómo se implementan los tests unitarios sobre la clase Calc usando el estilo BDD que nos ofrece Deno y las dependencias de @std/testing y @std/expect.

Test unitarios sobre la clase Calc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { expect } from "@std/expect/expect";
import { describe, it } from "@std/testing/bdd";
import { Calc } from "../src/Calc.ts";

describe("Test unitarios sobre la clase Calc", () => {
describe("sum - casos adicionales (BDD)", () => {
it("suma números positivos", () => {
const calc = new Calc();
expect(calc.sum(2, 3)).toEqual(5);
expect(calc.sum(10, 20)).toEqual(30);
});
});

describe("subtract - casos adicionales (BDD)", () => {
it("resta números positivos", () => {
const calc = new Calc();
expect(calc.subtract(5, 3)).toEqual(2);
expect(calc.subtract(20, 10)).toEqual(10);
});
});

describe("div - validaciones", () => {
it("debería fallar dividiendo por 0", () => {
const calc = new Calc();
// Capturar el error de ejecución
expect(() => calc.div(1, 0)).toThrow("No se puede dividir por 0");
});

it("debería dividir correctamente 8/2=4", () => {
const calc = new Calc();
const result = calc.div(8, 2);
expect(result).toEqual(4);
});
});
});

Para ejecutar todos los tests, usamos el comando deno test. Este buscará y correrá todos los archivos de test en el proyecto, generando una salida similar a la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
running 4 tests from ./test/Calc.test.ts
Test unitarios sobre la clase Calc ...
sum - casos adicionales (BDD) ...
suma números positivos ... ok (1ms)
sum - casos adicionales (BDD) ... ok (1ms)
subtract - casos adicionales (BDD) ...
resta números positivos ... ok (0ms)
subtract - casos adicionales (BDD) ... ok (0ms)
div - validaciones ...
debería fallar dividiendo por 0 ... ok (1ms)
debería dividir correctamente 8/2=4 ... ok (0ms)
div - validaciones ... ok (1ms)
Test unitarios sobre la clase Calc ... ok (2ms)

ok | 4 passed | 0 failed (10ms)

En este bloque de código, estamos definiendo la estructura de nuestros tests utilizando el estilo BDD que nos proporciona Deno:

  1. describe("Test unitarios sobre la clase Calc", ...): Este es el bloque principal que agrupa todos los tests relacionados con nuestra clase Calc. Funciona como un contenedor o una suite de pruebas.

  2. describe("sum - casos adicionales (BDD)", ...): Dentro de la suite principal, creamos un sub-grupo específico para las pruebas del método sum. Esto ayuda a organizar y contextualizar los tests.

  3. it("suma números positivos", ...): Aquí definimos un caso de prueba concreto. La descripción "suma números positivos" deja claro cuál es el comportamiento que estamos validando.

    • Dentro del it, creamos una instancia de Calc.
    • Usamos expect(calc.sum(2, 3)).toEqual(5); para afirmar que el resultado de llamar a sum(2, 3) es igual a 5. La función expect viene de la librería @std/expect y nos ofrece una forma legible y expresiva de hacer aserciones.
  4. describe("subtract - casos adicionales (BDD)", ...): De manera similar, creamos otro sub-grupo para el método subtract, manteniendo nuestros tests bien organizados.

  5. describe("div - validaciones", ...): Finalmente, un grupo para el método div. Aquí se incluyen dos casos de prueba interesantes:

    • it("debería fallar dividiendo por 0", ...): Este test verifica que el código maneja los errores correctamente. Usamos expect(() => calc.div(1, 0)).toThrow(...) para asegurar que, al intentar dividir por cero, se lance un error con el mensaje esperado. Nota que la llamada a la función que debe fallar se envuelve en una función flecha () => ....
    • it("debería dividir correctamente 8/2=4", ...): Un test para el “camino feliz” o el caso de uso normal, asegurando que la división funciona como se espera.

Esta estructura jerárquica con describe e it no solo ejecuta el código, sino que también lo documenta de una manera que es fácil de leer y entender para cualquier persona en el equipo.

Ahora, profundicemos en cómo se estructuran y ejecutan los tests. La función describe es fundamental, ya que actúa como un agrupador que define un ámbito o scope para un conjunto de pruebas relacionadas. Dentro de este ámbito, podemos usar “hooks” (funciones especiales) para controlar el ciclo de vida de nuestros tests.

Estos son los hooks principales:

  • beforeAll: Se ejecuta una sola vez antes de que comiencen todos los tests dentro de su describe. Es ideal para preparar un estado inicial que no cambiará, como levantar un servidor o conectar a una base de datos de prueba.
  • beforeEach: Se ejecuta antes de cada test (it) dentro de su describe. Perfecto para resetear el estado entre pruebas, como limpiar una tabla de la base de datos o crear una nueva instancia de una clase.
  • afterEach: Se ejecuta después de cada test (it). Se usa para tareas de limpieza que deben ocurrir después de cada prueba, como borrar archivos temporales o cerrar una conexión.
  • afterAll: Se ejecuta una sola vez después de que todos los tests dentro de su describe hayan finalizado. Ideal para la limpieza final, como detener el servidor o cerrar la conexión a la base de datos.

La magia ocurre cuando anidamos bloques describe, ya que los hooks del ámbito exterior también se aplican a los ámbitos interiores.

Para ilustrar esto, veamos el siguiente código de ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { afterAll, afterEach, beforeAll, beforeEach, describe, it } from "@std/testing/bdd";

describe("Test - scope y hooks", () => {
beforeAll(() => {
console.log("Before All 1 (Scope Padre)");
});

afterAll(() => {
console.log("After All 1 (Scope Padre)");
});

beforeEach(() => {
console.log("Before Each 1 (Scope Padre)");
});

afterEach(() => {
console.log("After Each 1 (Scope Padre)");
});

it("should test 1", () => {
console.log("Test 1");
});

describe("Test - Scope Anidado", () => {
beforeAll(() => {
console.log("------> Before All 2 (Scope Anidado)");
});

afterAll(() => {
console.log("------> After All 2 (Scope Anidado)");
});

beforeEach(() => {
console.log("------> Before Each 2 (Scope Anidado)");
});

afterEach(() => {
console.log("------> After Each 2 (Scope Anidado)");
});

it("should test 2", () => {
console.log("------> Test 2");
});
});
});

La salida de esta ejecución revela el orden exacto en que Deno ejecuta cada bloque. Desglosemos lo que sucedió:

Salida: Orden de Ejecución
Test - scope y hooks ...
------- output -------
Before All 1 (Scope Padre)
----- output end -----
should test 1 ...
------- output -------
Before Each 1 (Scope Padre)
Test 1
After Each 1 (Scope Padre)
----- output end -----
should test 1 ... ok (0ms)
Test - Scope Anidado ...
------- output -------
------> Before All 2 (Scope Anidado)
----- output end -----
should test 2 ...
------- output -------
Before Each 1 (Scope Padre)
------> Before Each 2 (Scope Anidado)
------> Test 2
------> After Each 2 (Scope Anidado)
After Each 1 (Scope Padre)
----- output end -----
should test 2 ... ok (0ms)
------- output -------
------> After All 2 (Scope Anidado)
----- output end -----
Test - Scope Anidado ... ok (0ms)
------- output -------
After All 1 (Scope Padre)
----- output end -----
Test - scope y hooks ... ok (1ms)

ok | 1 passed (3 steps) | 0 failed (3ms)
  1. Inicio del Scope Padre: Se ejecuta beforeAll del scope padre (Before All 1).
  2. Ejecución del primer test:
    • Se ejecuta beforeEach del padre (Before Each 1).
    • Se ejecuta el cuerpo del Test 1.
    • Se ejecuta afterEach del padre (After Each 1).
  3. Inicio del Scope Anidado:
    • Se ejecuta el beforeAll del scope anidado (Before All 2).
  4. Ejecución del segundo test (anidado):
    • Se ejecuta beforeEach del padre (Before Each 1), porque el test anidado está dentro de su ámbito.
    • Se ejecuta beforeEach del scope anidado (Before Each 2).
    • Se ejecuta el cuerpo del Test 2.
    • Se ejecuta afterEach del scope anidado (After Each 2).
    • Se ejecuta afterEach del padre (After Each 1).
  5. Fin del Scope Anidado: Se ejecuta afterAll del scope anidado (After All 2).
  6. Fin del Scope Padre: Se ejecuta afterAll del scope padre (After All 1).

Como puedes ver, la función describe no solo agrupa tests, sino que crea una jerarquía. Los hooks del describe padre “envuelven” a los hooks y tests de los describe hijos, permitiendo crear configuraciones y limpiezas complejas y ordenadas. Esto es clave para escribir tests mantenibles y escalables.

Palabras al Cierre

Espero que esta guía te haya dado una buena base para empezar a escribir tests unitarios robustos y expresivos en Deno usando BDD. Esta metodología no solo mejora la calidad de tu código, sino que también ayuda a que otros desarrolladores entiendan rápidamente el objetivo de cada test y lo que se busca lograr.

Si hay algún otro tema sobre Deno o testing que te gustaría que explorara, ¡déjamelo saber en los comentarios!


Referencias

Aquí tienes algunos enlaces para profundizar en los conceptos que hemos cubierto:

Hono: Framework para aplicaciones web

Cuando me enfrento a un nuevo proyecto, ya sea una API para una aplicación móvil o un backend para un sitio web, siempre busco herramientas que me hagan la vida más simple y fácil. No quiero mantener configuraciones complejas ni enfocar esfuerzos a optimizar código con un framework pesado que consuma más recursos de los necesarios. Mis prioridades son simples: quiero que sea rápido, ligero y que se integre sin problemas con Deno, que es el entorno de ejecución de TypeScript que he adoptado para casi todo.

Y ahí es donde entra Hono.

Descubrí Hono buscando una alternativa moderna a Express.js (un framework que usé durante años en el mundo de Node.js). Aunque Express es casi de facto el framework web para node.js, sentía que se estaba quedando desactualizado no encajaba del todo con la simplicidad y seguridad que me ofrecía Deno. Necesitaba algo que se sintiera más nativo, más ágil y moderno.

Lo que me convenció de Hono fue precisamente eso. Es increíblemente liviano, casi no añade overhead a mi aplicación, y su rendimiento es espectacular. Pero la verdadera joya de la corona, para mí, es su versatilidad. Aunque mi ecosistema principal es Deno, Hono me da la tranquilidad de saber que mi código es portable. Si el día de mañana decido mover una API a Cloudflare Workers para que corra en el edge (más cerca de los usuarios), o si un cliente me pide desplegarla en un servidor tradicional con Node.js, puedo hacerlo con cambios mínimos. Esa libertad es algo que valoro muchísimo.

Como vengo del mundo de Express, empezar con Hono fue muy sencillo. La forma de definir rutas, gestionar peticiones (req) y respuestas (res) y usar middlewares es tan intuitiva y familiar que no sentí que hiciera un esfuerzo adicional, es decir, si vienes del mundo Express, la curva de aprendizaje es rápida.

En este post quiero contarte, desde mi experiencia, por qué Hono se ha convertido en mi framework de cabecera para casi cualquier proyecto web que involucre un backend.

Manos a la Obra: Tu Primer Proyecto con Hono y Deno

Ya te conté por qué me gusta Hono, ahora vamos a la práctica para que veas lo rápido que es empezar.

Primero, abre tu terminal, crea una carpeta para el proyecto y entra en ella:

1
mkdir mi-proyecto-hono && cd mi-proyecto-hono

Deno tiene un comando muy práctico para inicializar proyectos que nos ahorra bastante trabajo. Usaremos el siguiente:

1
deno init

Este comando te creará una estructura básica con un main.ts y un deno.json. Ahora, para instalar Hono, agregamos la dependencia en el archivo deno.json y luego simplemente lo importamos en nuestro main.ts. Deno lo descargará y cacheará la primera vez que ejecutes el código.

Puedes agregar Hono como dependencia usando JSR de la siguiente forma, pero debes editar el archivo deno.json para corregir el alias en el import.

1
deno add jsr:@hono/hono

El corazón de tu proyecto - deno.json

Antes de ver el código, echemos un vistazo al deno.json. Aquí es donde Deno gestiona las dependencias y tareas. Para usar Hono, podemos añadirlo a la sección imports o usar la línea de comandos de deno para agregar la dependencia:

1
2
3
4
5
6
7
8
{
"imports": {
"hono": "jsr:@hono/hono@^4.10.3" // <-- ojo con el alias
},
"tasks": {
"start": "deno run --allow-net main.ts"
}
}

Un detalle importante es que, como ves, Hono está disponible en JSR (el nuevo registro de paquetes de JavaScript), lo que hace que manejar las versiones sea muy limpio.

Primer controlador

Ahora sí, el código de main.ts. Es tan simple y elegante como esto:

1
2
3
4
5
6
7
8
9
import { Hono } from "hono";

const app = new Hono();

app.get("/", (c) => {
return c.text("Hello Hono!");
});

Deno.serve(app.fetch);

¿Qué hace este código?

  1. Importa Hono.
  2. Crea una nueva instancia de la aplicación.
  3. Define una ruta para el método GET en la URL raíz (/).
  4. Cuando alguien visita esa ruta, le devuelve el texto “Hello Hono!”.
  5. Finalmente, Deno.serve inicia el servidor.

Para ponerlo en marcha, usamos la tarea que definimos en deno.json:

1
deno task start

Como Deno es seguro por defecto, te pedirá permiso para acceder a la red. ¡Y listo! Ya tienes un servidor corriendo. Si usas una herramienta como httpie, verás esto:

1
2
3
4
5
6
$ http :8000/

HTTP/1.1 200 OK
...

Hello Hono!

¿Y si quiero devolver JSON?

Fácil. Hono tiene un método específico para eso. Simplemente cambia c.text() por c.json():

1
2
3
app.get("/", (c) => {
return c.json({ message: "Hello Hono!" });
});

El resultado ahora será una respuesta JSON perfecta:

1
2
3
4
5
6
7
8
9
$ http :8000/

HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
...

{
"message": "Hello Hono!"
}

En solo unos minutos, hemos montado un servidor web funcional. Esta simplicidad y rapidez es lo que me terminó de convencer de la combinación de Hono y Deno.

¿ Cómo cambiar el puerto del servidor ?

Esto me costó pillarlo en su momento, así que dejo aquí cómo se hace:

1
Deno.serve({ port: 8080 }, app.fetch);

Revisemos algunos middlewares útiles

logger

Un middleware bien útil es el de logger, permite registrar (o loggear) lo que entra y lo que sale a Hono (obviamente depende de la ubicación en el archivo principal de la aplicación.)

1
2
3
4
5
6
import { Hono } from "hono";
import { logger } from "hono/logger";

const app = new Hono();

app.use(logger());

Con esta configuración se puede obtener este tipo de logs.

1
2
3
Listening on http://0.0.0.0:8000/ (http://localhost:8000/)
<-- GET /
--> GET / 200 0ms

etag

La cabecera o header ETag (que viene de Entity Tag o “etiqueta de entidad”) es básicamente una huella digital o un identificador único que el servidor le asigna a una versión específica de un recurso (como una página, una imagen o un archivo JSON).

Imagina que tienes un archivo en tu servidor. Cada vez que modificas y guardas ese archivo, su ETag cambia. Es como el número de versión de ese recurso en un momento exacto.

¿Y para qué sirve?

Su propósito principal es hacer que el cacheo sea mucho más eficiente y ahorrar ancho de banda. El flujo es el siguiente:

  1. Primera Visita: Cuando tu navegador pide un recurso (ej: styles.css) por primera vez, el servidor se lo envía completo y, además, incluye el header ETag con su “huella digital”. Por ejemplo: ETag: “v1-a2b3c4d5”.
  2. El Navegador Guarda la Huella: Tu navegador guarda el archivo styles.css en su caché y también anota su ETag.
  3. Siguientes Visitas: La próxima vez que necesite ese mismo archivo, el navegador no lo pide a ciegas. En su lugar, le pregunta al servidor: “Oye, tengo una versión de styles.css con la huella v1-a2b3c4d5. ¿Sigue siendo la buena?”. Esto lo hace enviando un header llamado If-None-Match: “v1-a2b3c4d5”.
  4. El Servidor Decide:
    • Si la “huella digital” del archivo en el servidor coincide con la que envió el navegador, significa que el archivo no ha cambiado. El servidor responde con un 304 Not Modified. Este mensaje es muy liviano, no contiene el archivo, y básicamente le dice al navegador: “Todo sigue igual, usa la copia que ya tienes en tu caché”.
    • Si la “huella digital” no coincide, significa que el archivo fue actualizado. El servidor responde con un 200 OK, envía la nueva versión del archivo y, por supuesto, su nuevo ETag.

En resumen, el ETag permite al navegador evitar volver a descargar recursos que no han cambiado, haciendo que la navegación sea mucho más rápida y consuma menos datos. Además ayuda a las capas superiores de la aplicación a tomar mejores decisiones respecto del cache de la aplicación.

Ahora la configuración para Hono:

1
2
3
4
5
6
import { Hono } from "hono";
import { etag } from "hono/etag";

const app = new Hono();

app.use(etag());

Finalmente esta será la respuesta donde aparece una cabecera con el etag.

1
2
3
4
5
6
7
8
9
10
11
http :8000/
HTTP/1.1 200 OK
content-length: 25
content-type: application/json
date: Tue, 28 Oct 2025 03:25:31 GMT
etag: "6e488564c25da2b4d7326d5ebfa92e957af61dcb"
vary: Accept-Encoding

{
"message": "Hello Hono!"
}

cors

Probablemente, uno de los “problemas” más comunes al empezar a desarrollar una API es toparse con un error de CORS en el navegador.

Imagina que los sitios web son como reinos amurallados. Por seguridad, un reino (tudominio.com) no puede simplemente enviar un mensajero a pedirle recursos a otro reino (api.otrodominio.com). Esta regla se llama Política del Mismo Origen (Same-Origin Policy) y es fundamental para la seguridad en la web.

El middleware de CORS de Hono es el pasaporte diplomático que nos permite configurar qué “reinos” externos tienen permiso para comunicarse con nuestra API.

Por ejemplo, una configuración de CORS muy específica y segura podría ser así:

1
2
3
4
5
6
7
8
9
10
11
import { Hono } from 'hono';
import { cors } from 'hono/cors';

app.use('/api/*', cors({
origin: 'http://example.com',
allowMethods: ['POST', 'GET', 'OPTIONS'],
allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'],
exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],
maxAge: 600,
credentials: true,
}));

Analicemos las reglas que hemos definido en este bloque:

  • app.use('/api/*', ...): Primero, le indicamos a Hono que aplique este middleware únicamente a las rutas que comiencen con /api/. Esto es muy práctico para tener políticas de seguridad distintas para diferentes partes de tu aplicación.
  • origin: 'http://example.com': La regla más importante. Se especifica que solo las peticiones que vengan desde http://example.com están permitidas.
  • allowMethods: [...]: Define qué métodos HTTP están autorizados (POST, GET, OPTIONS). Un DELETE sería bloqueado.
  • allowHeaders: [...]: Una lista blanca de las cabeceras HTTP que el cliente tiene permitido enviar.
  • exposeHeaders: [...]: Permite que el código JavaScript del cliente pueda leer cabeceras de la respuesta que, por defecto, el navegador oculta por seguridad, como Content-Length.
  • maxAge: 600: Una optimización de rendimiento. Indica al navegador que puede guardar en caché el resultado de la petición de “verificación” (preflight) durante 600 segundos (10 minutos).
  • credentials: true: Esencial si tu frontend necesita enviar credenciales como cookies o cabeceras de autenticación (Authorization).

En resumen, este middleware te da un control muy granular para configurar una política de CORS segura, una práctica mucho más recomendable que simplemente abrir el acceso a todo el mundo con origin: '*'.

Palabras al Cierre

Como hemos visto en este recorrido, Hono se presenta como una alternativa muy atractiva en el ecosistema de JavaScript y TypeScript. Su filosofía minimalista, su rendimiento y, sobre todo, su capacidad para adaptarse a cualquier entorno de ejecución, lo convierten en una herramienta que vale la pena tener en nuestro arsenal.

Personalmente, la combinación de la simplicidad de Hono con la seguridad y el entorno moderno de Deno ha hecho que desarrollar APIs vuelva a ser una experiencia ágil y productiva.

Pero esto es solo el comienzo. Este artículo es el primero de una serie donde exploraremos a fondo el ecosistema de Deno y Hono. En las próximas entregas, veremos temas un poco más avanzados como:

  • Creación de nuestros propios middlewares.
  • Helpers de Hono
  • Rutas avanzadas y manejo de grupos.
  • Estrategias de testing para nuestras APIs.

Te invito a que experimentes con lo que hemos visto hoy y, por supuesto, a que te mantengas atento para los próximos artículos de la serie.


Referencias Útiles

Aquí te dejo una lista de enlaces que te serán de gran ayuda para profundizar en Hono y Deno:

Deno: ¿Una alternativa real a Node.js?

Si te mueves en el mundo de JavaScript, seguro que Node.js es tu pan de cada día. Pero, ¿has oído hablar de Deno? Y no, no es solo “Node” al revés (aunque el guiño es genial). Deno es un runtime para JavaScript y TypeScript creado por el mismísimo Ryan Dahl, el padre de Node.js.

Imagínate que creas algo increíble, pero con el tiempo te das cuenta de que podrías haberlo hecho mejor. Eso es Deno para Ryan Dahl. Es su oportunidad de redimirse y arreglar lo que él considera los “errores de diseño” de Node.js. El resultado es un entorno de ejecución más moderno, seguro y con una experiencia de desarrollo que te va a sorprender.

¿Qué hace a Deno tan especial?

¡A programar desde el minuto cero!

Una de las cosas que más me gustan de Deno es que viene con todo lo que necesitas para empezar a trabajar. ¿Cansado de configurar ESLint, Prettier, Jest y un montón de dependencias de desarrollo cada vez que empiezas un proyecto?

Deno incluye out of the box:

  • Un formateador de código (deno fmt): Para que tu código siempre luzca impecable y consistente.
  • Un linter (deno lint): Para detectar problemas y mantener la calidad del código.
  • Un runner de tests (deno test): Con soporte para tests unitarios y de integración, al estilo BDD (como Jest) o al estilo de Deno con Deno.test.
  • Soporte nativo para TypeScript: Sin necesidad de instalar tsc ni configurar tsconfig.json. Escribes en TypeScript y Deno se encarga del resto.
  • Un repositorio de librerías (JSR), Deno tiene su propio ecosistema de dependencias llamado JSR. Pero en las últimas versiones ahora soporta librerías del ecosistema de NPM.

Esto significa menos tiempo configurando y más tiempo programando. ¡Productividad al máximo desde el primer momento! Toda la configuración del entorno queda en el archivo deno.json y de esta forma es fácil de transportar, lo subes al repositorio y todo tu equipo lo usa (y lo va mejorando).

Seguridad: Tú tienes el control

Otro de los pilares de Deno es la seguridad. A diferencia de Node.js, donde un script puede acceder a tu sistema de archivos, a la red o a las variables de entorno sin que te des cuenta, Deno ejecuta el código en un sandbox.

Por defecto, un script de Deno no tiene permisos para hacer nada “peligroso”. Si necesita acceder a la red, al sistema de archivos o a cualquier otra cosa, tienes que dárselo explícitamente con flags al ejecutar el comando:

1
2
3
4
5
6
7
8
# Permite leer del sistema de archivos
deno run --allow-read mi_script.ts

# Permite conexiones de red
deno run --allow-net mi_script.ts

# ¡Permite todo! (Úsalo con cuidado)
deno run -A mi_script.ts

Este modelo de permisos explícitos te da un control total sobre lo que tus dependencias pueden y no pueden hacer, evitando sorpresas desagradables.

Gestión de dependencias

En Deno, no existe la carpeta node_modules ni el archivo package.json. Las dependencias se importan directamente desde una URL, como en el navegador:

1
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

La forma de hacer los imports con las urls en los archivos TS, a mi modo de ver, es poco mantenible. Se hicieron varios esfuerzos para que sea una vía simple de mantener, pero al parecer llegaron a algo como un administrador de dependencias centralizado en el archivo deno.json. En las últimas versiones, además, agregaron soporte para incluir dependencias del ecosistema de NPM, de esta forma, se amplió la cantidad de librerías que puedes usar.

Si vas a usar dependencias de NPM, a veces se genera un archivo package.json muy básico con el propósito de agregar dependencias solamente.

Deno descarga y cachea las dependencias la primera vez que se ejecuta el script. Si quieres asegurarte de que las dependencias no cambien, puedes usar un archivo deno.json (o deno.jsonc) para gestionar las versiones, de forma similar a como lo harías con un package.json, pero mucho más simple.

Un ejemplo rápido

La mejor forma de entenderlo es probándolo. ¡Vamos a crear un pequeño proyecto!

1. Instalación

Deno está disponible para la mayoría de los sistemas operativos. Pásate por la página de instalación, busca tu SO y sigue los pasos.

2. Inicializando el proyecto

Una vez instalado, crear un proyecto es tan fácil como esto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ mkdir mi-proyecto-deno && cd mi-proyecto-deno
$ deno init

✅ Project initialized

Run these commands to get started

# Run the program
deno run main.ts

# Run the program and watch for file changes
deno task dev

# Run the tests
deno test

¡Y ya está! Deno te crea tres archivos:

  • deno.json: El corazón de tu proyecto, similar al package.json.
  • main.ts: Tu punto de entrada.
  • main_test.ts: Un archivo de ejemplo para tus tests.

3. El código

El archivo main.ts que se genera es súper simple:

1
2
3
4
5
6
7
8
export function add(a: number, b: number): number {
return a + b;
}

// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
if (import.meta.main) {
console.log("2 + 3 =", add(2, 3));
}

Puedes ejecutarlo con deno run main.ts y verás en la consola:

1
2 + 3 = 5

4. Los tests

Y el archivo de test, main_test.ts, no se queda atrás en simplicidad:

1
2
3
4
5
6
import { assertEquals } from "@std/assert";
import { add } from "./main.ts";

Deno.test(function addTest() {
assertEquals(add(2, 3), 5);
});

Ejecútalo con deno test y verás que todo funciona como la seda:

1
2
3
4
5
Check file:///Users/pcollaog/mi-proyecto-deno/main_test.ts
running 1 test from ./main_test.ts
addTest ... ok (0ms)

ok | 1 passed | 0 failed (3ms)

Conclusión

Como ves, Deno no es solo un capricho. Es una propuesta seria que replantea el desarrollo del lado del servidor con JavaScript/TypeScript, poniendo el foco en la seguridad, la simplicidad y una experiencia de desarrollo moderna y sin fricciones.

Si vienes de Node.js, la curva de aprendizaje es mínima y las ventajas son enormes. Menos configuración, más seguridad y un conjunto de herramientas unificado que te hará la vida más fácil.

¿Mi recomendación? Dale una oportunidad. Puede que te sorprendas y encuentres a tu nuevo mejor amigo para tus proyectos de backend.

¿Y tú, ya has probado Deno? ¡Cuéntame tu experiencia en los comentarios!

En un próximo artículo, profundizaré en mi experiencia con los frameworks web más populares para Deno.


Referencias