Ir al contenido
angel.library

Servidor HTTP nativo

El módulo http de Node es la base sobre la que está construido Express (y prácticamente cualquier framework backend de Node) — entenderlo ayuda a entender qué es exactamente lo que un framework agrega encima.

server.ts
import { createServer } from "node:http"
const server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" })
res.end("Hola mundo")
})
server.listen(3000, () => {
console.log("Escuchando en http://localhost:3000")
})

createServer recibe una función que Node llama por cada request entrante, con dos objetos: req (la request: método, URL, headers, body) y res (la respuesta: lo que se manda de vuelta).

const server = createServer((req, res) => {
console.log(req.method) // 'GET', 'POST', etc.
console.log(req.url) // '/usuarios?activo=true' (ruta + query string, sin parsear)
console.log(req.headers) // objeto con todos los headers
res.end("ok")
})

req.url llega como un string crudo: Node no separa automáticamente la ruta del query string.

import { createServer } from "node:http"
const server = createServer((req, res) => {
const url = new URL(req.url!, `http://${req.headers.host}`)
console.log(url.pathname) // '/usuarios'
console.log(url.searchParams.get("activo")) // 'true'
res.end("ok")
})

A diferencia de req, el body no llega ya armado — req es en realidad un stream, y hay que juntarlo a mano:

function leerBody(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let data = ""
req.on("data", (chunk) => (data += chunk))
req.on("end", () => resolve(data))
req.on("error", reject)
})
}
const server = createServer(async (req, res) => {
if (req.method === "POST") {
const body = await leerBody(req)
const json = JSON.parse(body)
res.end(JSON.stringify({ recibido: json }))
}
})

Parsear el body y exponer req.body es una de las tareas que Express resuelve mediante middleware, en lugar de repetir esta función en cada proyecto.

res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify({ ok: true }))
// Atajo equivalente para JSON:
res.setHeader("Content-Type", "application/json")
res.statusCode = 200
res.end(JSON.stringify({ ok: true }))

res.end() es obligatorio — sin llamarlo, la respuesta nunca se envía y la request queda colgada hasta el timeout.

Node no tiene concepto de “rutas” — cada request pasa por la misma función, y el ruteo es simplemente comparar req.method/url.pathname a mano:

const server = createServer((req, res) => {
const url = new URL(req.url!, `http://${req.headers.host}`)
if (req.method === "GET" && url.pathname === "/usuarios") {
res.end("lista de usuarios")
} else if (req.method === "POST" && url.pathname === "/usuarios") {
res.end("usuario creado")
} else {
res.writeHead(404)
res.end("No encontrado")
}
})

Esto deja de escalar rápidamente: treinta rutas formarían una cadena difícil de mantener. Un router de framework resuelve ese problema. Consulta API REST mínima con Node puro para una versión más organizada, todavía sin framework.

APIQué es
createServer((req, res) => {...})Crea el servidor, una función por cada request
req.method / req.urlMétodo y ruta+query cruda (sin parsear)
new URL(req.url, base)Parsea req.url en pathname + searchParams
req.on('data'/'end')El body llega como stream, hay que juntarlo a mano
res.writeHead(status, headers) / res.end(body)Mandar la respuesta
  • Esto no tiene next() — ese concepto (una cadena de funciones middleware que se pasan el control entre sí) no existe en el http nativo, lo agregan frameworks como Express. Ver Middlewares en Express.
  • Node nativo no parsea query strings, no parsea el body, no tiene router, no tiene manejo de errores centralizado — todo eso es exactamente el trabajo que hace un framework. Entender esto ayuda a entender qué gana realmente un proyecto al agregar Express (o cualquier otro) en vez de “es una capa random encima”.