¡Ven a la JSConf España 2026! Comprar entradas

Diferencias entre usar Express y no usarlo

En esta clase comparamos directamente un servidor creado con Node.js nativo frente a el mismo servidor usando Express, para entender de forma clara qué cambia y por qué Express facilita tanto el trabajo del desarrollador.

La idea no es demonizar el servidor nativo, sino entender qué problemas de escalabilidad y mantenimiento aparecen cuando el código empieza a crecer.

Servidor sin Express (Node.js nativo)

Cuando usamos el módulo node:http, tenemos que encargarnos manualmente de casi todo el ciclo de vida de la petición:

  • Comprobar el método HTTP (GET, POST, etc.).
  • Parsear la URL para obtener el pathname.
  • Gestionar cabeceras (Content-Type, etc.).
  • Enviar la respuesta y cerrarla manualmente con res.end().
  • Repetir este patrón para cada ruta nueva.

Ejemplo de servidor nativo:

import http from 'node:http'

const server = http.createServer((req, res) => {
  const { url, method } = req

  if (method === 'GET' && url === '/') {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
    res.end('<h1>¡Hola Mundo desde lo nativo!</h1>')
    return
  }

  res.writeHead(404)
  res.end('No encontrado')
})

server.listen(1234)

Este código funciona, pero tiene varios inconvenientes:

  • Se vuelve repetitivo: Cada ruta requiere validaciones similares.
  • Es más difícil de leer: La lógica de negocio se mezcla con la lógica de red.
  • Escala mal: A medida que añades 10, 20 o 50 rutas, los condicionales se vuelven inmanejables.

Servidor usando Express

Con Express, el mismo comportamiento se escribe de forma mucho más declarativa y limpia:

import express from 'express'

const app = express()
const PORT = process.env.PORT ?? 1234

app.get('/', (req, res) => {
  res.send('<h1>¡Hola Mundo con Express!</h1>')
})

app.use((req, res) => {
  res.status(404).send('No encontrado')
})

app.listen(PORT, () => {
  console.log(`Servidor levantado en http://localhost:${PORT}`)
})

¿Qué ha cambiado aquí?

  1. Enrutamiento semántico: Usamos app.get(), app.post(), etc., lo que hace que el código sea autodocumentado.
  2. Abstracción de cabeceras: res.send() detecta automáticamente el contenido y añade los headers necesarios por ti.
  3. Sin parsing manual: No hemos tenido que importar url ni parsear nada manualmente.
  4. Ciclo de respuesta automático: No hace falta llamar a res.end().

Las 3 diferencias clave

1. Legibilidad y Mantenibilidad

Con Express, al leer el código es evidente qué rutas existen y qué hace cada una. En el servidor nativo, esa información suele estar dispersa dentro de un gran bloque de condicionales.

2. Eliminación de código repetitivo (Boilerplate)

Express elimina la necesidad de escribir una y otra vez el código para manejar streams, parsear el cuerpo de la petición o configurar códigos de estado.

3. Facilidad para escalar

Añadir una nueva funcionalidad o un nuevo endpoint en Express es tan sencillo como añadir una línea más. En un servidor nativo, esto suele implicar refactorizar el flujo de control de la aplicación.

¿Node.js nativo o Express?

Node.js nativo es excelente para entender los fundamentos de cómo funciona la web por debajo. Sin embargo, Express existe para ahorrar tiempo y reducir la complejidad accidental. Express no hace “magia”, simplemente usa http por debajo de una forma optimizada para nosotros.

Para terminar…

  • ✅ Comparamos el enfoque imperativo (nativo) vs el declarativo (Express).
  • ✅ Entendimos por qué res.send() es más potente que res.end().
  • ✅ Vimos cómo Express simplifica la estructura de nuestro proyecto.
  • ✅ Aprendimos que Express nos permite centrarnos en la lógica de nuestra aplicación en lugar de en los detalles del protocolo HTTP.

En las próximas clases seguiremos profundizando en cómo Express maneja datos más complejos y cómo estructurar nuestra API profesionalmente.