Node
Tutorial Node: Módulo http y https
Node.js: Aprende a crear un servidor básico usando http.createServer(). Comprende solicitudes y respuestas HTTP de forma sencilla y eficiente.
Aprende Node GRATIS y certifícateCreando un servidor básico con http.createServer()
El módulo http
de Node.js permite la creación de servidores web y el manejo de solicitudes HTTP de forma sencilla. Para iniciar un servidor básico, utilizamos el método **http.createServer()**
, que devuelve una instancia de servidor.
Primero, importamos el módulo http
:
import { createServer } from "http";
A continuación, creamos el servidor:
const servidor = createServer((req, res) => {
// Manejar la solicitud y enviar una respuesta
});
La función de callback recibe dos objetos importantes: req
(la solicitud entrante) y res
(la respuesta que enviaremos al cliente). Estos objetos nos permiten acceder a información de la solicitud y configurar la respuesta respectivamente.
Dentro del callback, podemos establecer el código de estado HTTP y los encabezados de la respuesta:
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
Aquí, hemos establecido un código de estado 200, indicando éxito, y especificado el tipo de contenido como texto plano en UTF-8. Luego, enviamos la respuesta al cliente:
res.end('¡Hola, mundo!');
El método res.end()
finaliza la respuesta y envía los datos al cliente. Si necesitamos enviar datos en múltiples partes, podríamos usar res.write()
antes de res.end()
.
Finalmente, hacemos que el servidor escuche en un puerto específico:
const puerto = 3000;
servidor.listen(puerto, () => {
console.log(`Servidor ejecutándose en el puerto ${puerto}`);
});
El método servidor.listen()
inicia el servidor y lo configura para que escuche en el puerto indicado. El callback opcional se ejecuta cuando el servidor empieza a escuchar.
El código completo quedaría así:
import { createServer } from "http";
const servidor = createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("¡Hola, mundo!");
});
const puerto = 3000;
servidor.listen(puerto, () => {
console.log(`Servidor ejecutándose en el puerto ${puerto}`);
});
Para ejecutar el servidor, guardamos este código en un archivo, por ejemplo servidor.mjs
, y lo ejecutamos con Node.js:
A partir de esta lección, utilizaremos la extensión
.mjs
para nuestros archivos de JavaScript. Esto se debe a que.mjs
indica que estamos usando módulos ES6, lo cual nos permite aprovechar las características modernas de JavaScript, comoimport
yexport
.
node servidor.mjs
Ahora, si visitamos http://localhost:3000
en un navegador, veremos el mensaje "¡Hola, mundo!".
Este sencillo servidor es el fundamento para aplicaciones más complejas. Al comprender cómo funciona http.createServer()
, podemos expandir nuestra aplicación para manejar rutas, métodos HTTP y servir contenido dinámico. Además, podemos aprovechar características como la asincronía y los streams para crear aplicaciones eficientes y escalables.
Entendiendo req
(request) y res
(response)
Al crear un servidor HTTP en Node.js, los objetos req
y res
son esenciales para manejar las solicitudes y respuestas entre el cliente y el servidor. El objeto req
representa la solicitud entrante, mientras que res
representa la respuesta que el servidor enviará al cliente.
El objeto req
contiene información vital sobre la solicitud del cliente:
req.url
: la ruta solicitada, incluyendo la ruta de acceso y la cadena de consulta.req.method
: el método HTTP utilizado, comoGET
,POST
,PUT
,DELETE
, etc.req.headers
: un objeto que contiene los encabezados de la solicitud, proporcionando detalles como el agente de usuario, contenido aceptado y más.
Por ejemplo, para registrar la ruta y el método de cada solicitud:
console.log(`Solicitud recibida: Método=${req.method}, Ruta=${req.url}`);
El objeto res
permite configurar y enviar la respuesta al cliente. Algunas propiedades y métodos clave de res
son:
res.statusCode
: establece el código de estado HTTP de la respuesta (por defecto es 200).res.setHeader(nombre, valor)
: añade o modifica un encabezado en la respuesta.res.write(datos)
: escribe datos en el cuerpo de la respuesta sin finalizarla.res.end([datos])
: finaliza la respuesta, enviando opcionalmente datos adicionales.
Por ejemplo, para responder con un mensaje de texto plano:
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Respuesta exitosa');
Si se necesita enviar datos en múltiples partes, se puede utilizar res.write()
varias veces antes de llamar a res.end()
:
res.write('Parte 1 de la respuesta\n');
res.write('Parte 2 de la respuesta\n');
res.end('Fin de la respuesta');
En cuanto al manejo del cuerpo de la solicitud, en métodos como POST
o PUT
, los datos se reciben como un flujo (stream). Se pueden escuchar los eventos ‘data’
y ‘end’
para recopilar los datos:
let cuerpo = '';
req.on('data', (chunk) => {
cuerpo += chunk;
});
req.on('end', () => {
// Procesar el cuerpo recibido
console.log('Datos recibidos:', cuerpo);
});
Es importante manejar adecuadamente el flujo de datos para evitar problemas con solicitudes grandes o lentas. Además, se debe considerar el tipo de contenido para procesar correctamente los datos recibidos, verificando el encabezado req.headers['content-type']
.
Para acceder a parámetros en la URL o en la cadena de consulta, se puede utilizar el módulo nativo url
:
import { parse } from "url";
const urlParseada = url.parse(req.url, true);
const ruta = urlParseada.pathname;
const parametros = urlParseada.query;
Esto permite extraer la ruta y los parámetros de forma sencilla, facilitando el manejo de diferentes rutas y acciones basadas en los parámetros.
Al trabajar con encabezados, tanto en la solicitud como en la respuesta, es crucial respetar el protocolo HTTP. Por ejemplo, para configurar el encabezado Access-Control-Allow-Origin
y permitir CORS:
res.setHeader('Access-Control-Allow-Origin', '*');
El manejo correcto de los objetos req
y res
es esencial para la creación de servidores HTTP robustos. Permite controlar cada aspecto de la comunicación, desde la interpretación de las solicitudes hasta la configuración detallada de las respuestas, asegurando que el servidor responda adecuadamente.
Rutas simples y manejo de métodos HTTP (GET, POST, PUT, DELETE)
En Node.js, podemos crear rutas simples y manejar diferentes métodos HTTP utilizando el módulo http
. Al interpretar la propiedad req.url
y req.method
, dirigimos las solicitudes a distintos manejadores según la ruta y el método especificados por el cliente.
Para comenzar, ampliamos el servidor básico previamente creado:
import { createServer } from 'http';
const servidor = createServer((req, res) => {
// Lógica para manejar rutas y métodos
});
const puerto = 3000;
servidor.listen(puerto, () => {
console.log(`Servidor ejecutándose en el puerto ${puerto}`);
});
Analizamos la URL solicitada y el método HTTP utilizando la clase URL
nativa de Node.js:
const servidor = createServer((req, res) => {
const miURL = new URL(req.url, `http://${req.headers.host}`);
const ruta = miURL.pathname;
const metodo = req.method;
// Lógica para manejar rutas y métodos
});
Creamos una lógica de enrutamiento basada en la ruta y el método. Por ejemplo, manejamos las siguientes rutas:
- GET /: muestra un mensaje de bienvenida.
- GET /usuarios: devuelve una lista de usuarios.
- POST /usuarios: crea un nuevo usuario.
- PUT /usuarios: actualiza un usuario existente.
- DELETE /usuarios: elimina un usuario.
Implementamos esta lógica en el servidor:
const servidor = createServer(async (req, res) => {
const miURL = new URL(req.url, `http://${req.headers.host}`);
const ruta = miURL.pathname;
const metodo = req.method;
if (ruta === '/' && metodo === 'GET') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Bienvenido a nuestro servidor Node.js');
} else if (ruta === '/usuarios' && metodo === 'GET') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
const usuarios = [{ id: 1, nombre: 'Juan' }, { id: 2, nombre: 'María' }];
res.end(JSON.stringify(usuarios));
} else if (ruta === '/usuarios' && metodo === 'POST') {
const datos = await obtenerCuerpo(req);
const nuevoUsuario = JSON.parse(datos);
res.statusCode = 201;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ mensaje: 'Usuario creado', usuario: nuevoUsuario }));
} else if (ruta === '/usuarios' && metodo === 'PUT') {
const datos = await obtenerCuerpo(req);
const usuarioActualizado = JSON.parse(datos);
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ mensaje: 'Usuario actualizado', usuario: usuarioActualizado }));
} else if (ruta === '/usuarios' && metodo === 'DELETE') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ mensaje: 'Usuario eliminado' }));
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Ruta no encontrada');
}
});
Definimos la función obtenerCuerpo
para recopilar el cuerpo de la solicitud de manera asíncrona:
function obtenerCuerpo(req) {
return new Promise((resolver, rechazar) => {
let datos = '';
req.on('data', (chunk) => {
datos += chunk;
});
req.on('end', () => {
resolver(datos);
});
req.on('error', (err) => {
rechazar(err);
});
});
}
Al utilizar async/await, manejamos las operaciones asincrónicas de forma más limpia y clara. La función obtenerCuerpo
nos permite leer el cuerpo de las solicitudes POST y PUT, que generalmente contienen datos en formato JSON.
Para manejar rutas más complejas, como recursos con identificadores, podemos utilizar expresiones regulares o analizar partes de la ruta. Por ejemplo, para manejar /usuarios/123
:
const idUsuario = ruta.match(/^\/usuarios\/(\d+)$/);
if (idUsuario && metodo === 'GET') {
const id = idUsuario[1];
// Lógica para obtener el usuario con el id especificado
}
La expresión regular /^\/usuarios\/(\d+)$/
captura números en la ruta, permitiéndonos extraer el id del usuario. De esta manera nos ayuda a manejar rutas dinámicas y recursos específicos.
Finalmente, al desarrollar servidores HTTP en Node.js sin frameworks, adquirir una comprensión sólida del manejo de rutas y métodos nos permite crear APIs eficientes y adaptables. Aunque existen herramientas y frameworks que simplifican este proceso, entender los fundamentos nos proporciona mayor control y flexibilidad en nuestras aplicaciones.
Servir contenido estático/manual
Para mejorar la funcionalidad de nuestro servidor Node.js, es útil poder servir contenido estático como archivos HTML, CSS, JavaScript, imágenes y otros recursos. Esto permite que nuestros usuarios accedan a páginas web completas y recursos asociados desde nuestro servidor.
Para lograr esto manualmente, utilizaremos los módulos http
y fs
de Node.js. El módulo fs
(sistema de archivos) nos permite leer archivos del disco y enviarlos como respuestas a las solicitudes de los clientes.
Primero, importamos los módulos necesarios:
import { readFile } from "fs";
import { createServer } from "http";
import { join, extname, normalize } from "path";
import { fileURLToPath } from "url";
Definimos un directorio base desde el cual serviremos nuestros archivos estáticos. Por ejemplo:
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const directorioPublico = join(__dirname, "public");
Este directorio public
contendrá todos los archivos que queremos servir.
A continuación, creamos el servidor HTTP:
const servidor = createServer((req, res) => {
const rutaSolicitada = req.url;
let rutaArchivo = join(directorioPublico, rutaSolicitada);
// Manejo de rutas que terminan en '/'
if (rutaSolicitada.endsWith("/")) {
rutaArchivo = join(rutaArchivo, "index.html");
}
// Determinar la extensión del archivo
const extension = extname(rutaArchivo).toLowerCase();
// Mapear extensiones a tipos de contenido
const tiposMime = {
".html": "text/html; charset=utf-8",
".css": "text/css; charset=utf-8",
".js": "application/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".png": "image/png",
".jpg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/x-icon",
};
const tipoContenido = tiposMime[extension] || "application/octet-stream";
// Leer y servir el archivo
readFile(rutaArchivo, (error, contenido) => {
if (error) {
if (error.code === "ENOENT") {
// Archivo no encontrado
res.statusCode = 404;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("404 - Archivo no encontrado");
} else {
// Otro error del servidor
res.statusCode = 500;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("500 - Error interno del servidor");
}
} else {
// Éxito: Servir el archivo
res.statusCode = 200;
res.setHeader("Content-Type", tipoContenido);
res.end(contenido);
}
});
});
Con este código, el servidor intenta encontrar y leer el archivo solicitado en el directorio public
, y lo envía al cliente con el tipo MIME apropiado.
Es importante manejar diferentes extensiones y asignar el tipo de contenido correcto en el encabezado Content-Type
. Esto asegura que el navegador interprete correctamente los archivos recibidos.
Para mejorar la seguridad, evitamos permitir que los usuarios accedan a archivos fuera del directorio público. Esto se logra al usar path.join
y controlar adecuadamente las rutas.
Por ejemplo, para evitar ataques como Path Traversal, podemos normalizar la ruta y verificar que esté dentro del directorio público:
let rutaSegura = normalize(rutaArchivo);
if (!rutaSegura.startsWith(directorioPublico)) {
res.statusCode = 403;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("403 - Acceso prohibido");
return;
}
Además, podemos implementar una página de error personalizada cuando un archivo no se encuentra:
if (error.code === "ENOENT") {
readFile(join(directorioPublico, "404.html"), (error404, contenido404) => {
res.statusCode = 404;
res.setHeader("Content-Type", "text/html; charset=utf-8");
if (error404) {
res.end("404 - Archivo no encontrado");
} else {
res.end(contenido404);
}
});
}
De esta manera, servimos un archivo 404.html
personalizado ubicado en el directorio público.
Para iniciar el servidor, especificamos el puerto y comenzamos a escuchar:
const puerto = 3000;
servidor.listen(puerto, () => {
console.log(`Servidor ejecutándose en el puerto ${puerto}`);
});
Ahora, al colocar archivos en el directorio public
, el servidor los servirá según las solicitudes de los clientes.
Por ejemplo:
http://localhost:3000/index.html
servirá el archivopublic/index.html
http://localhost:3000/css/estilos.css
servirápublic/css/estilos.css
.- Si se accede a
http://localhost:3000/
, y la ruta termina en/
, servirápublic/index.html
por defecto.
Para manejar rutas más complejas y mejorar la eficiencia, podemos utilizar el módulo fs.createReadStream
para transmitir archivos grandes sin cargarlos completamente en memoria:
const streamLectura = createReadStream(rutaArchivo);
streamLectura.on('open', () => {
res.statusCode = 200;
res.setHeader('Content-Type', tipoContenido);
streamLectura.pipe(res);
});
streamLectura.on('error', (error) => {
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('500 - Error interno del servidor');
});
De esta forma, servimos archivos grandes de manera más eficiente, utilizando los streams de Node.js.
Es fundamental manejar los posibles errores y eventos en los streams para garantizar la estabilidad del servidor.
También podemos optimizar la configuración de tipos MIME utilizando un paquete externo como mime-types
, pero dado que estamos trabajando sin frameworks ni paquetes adicionales, mantenemos el mapa de tipos MIME manualmente.
Para servir contenido estático de manera manual en Node.js, comprender el manejo de rutas, sistema de archivos y encabezados HTTP es importante. Esto nos permite construir servidores flexibles y adaptados a nuestras necesidades sin depender de frameworks externos.
Todas las lecciones de Node
Accede a todas las lecciones de Node y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Node.js
Introducción Y Entorno
Fundamentos Del Entorno Node.js
Introducción Y Entorno
Módulo Http Y Https
Http Y Api Rest
Http Params, Headers Y Body
Http Y Api Rest
Validación De Datos
Http Y Api Rest
Conexión A Bases De Datos Sin Orm
Persistencia
Creación De Consultas Básicas (Crud) Sin Orm
Persistencia
Módulo Fs
Sistema De Archivos
Introducción A La Seguridad
Seguridad
Sesiones Y Cookies
Seguridad
Roles Y Permisos
Seguridad
Testing En Node.js
Testing
Estructura De Carpetas
Arquitectura
Configuración Y Variables De Entorno
Arquitectura
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender el uso del módulo
http
en Node.js. - Crear un servidor básico con
http.createServer()
. - Manejar solicitudes y respuestas HTTP.
- Interpretar y gestionar los objetos
req
yres
. - Configurar código de estado y encabezados HTTP.
- Enviar respuestas al cliente correctamente.
- Hacer que el servidor escuche en un puerto específico.