Crear un servidor de datos de experimentos con Express
Hasta ahora, hemos aprendido a solicitar datos a un servidor con fetch. Ahora, vamos a crear el lado opuesto: un servidor que envíe datos.
Al igual que en un laboratorio se utiliza un LIMS (Sistema de Gestión de Información de Laboratorio), en la web necesitamos un servidor que almacene y proporcione datos. Express es la herramienta para crear ese servidor. Es un framework web que se ejecuta sobre Node.js, que hemos aprendido antes: en otras palabras, es un kit de herramientas para el desarrollo de servidores.
Aunque se puede crear un servidor solo con Node.js (¿recuerdas http.createServer?), tendríamos que implementar todo manualmente. Con Express, el framework se encarga de tareas repetitivas, como enviar diferentes datos según la URL, proporcionar archivos o gestionar errores. Es como utilizar un kit validado en lugar de comprar y combinar los reactivos uno por uno.
Hola Mundo: Empezar un servidor en 3 líneas
Esta es la forma más básica de un servidor Express:
const express = require("express");
const app = express();
app.get("/", function(req, res) {
res.send("LIMS Server Running");
});
app.listen(3000, function() {
console.log("Servidor iniciado: http://localhost:3000");
});Guarde este código como server.js y ejecute node server.js en la terminal, y podrá acceder a él en el navegador a través de http://localhost:3000.
Desglosemos los elementos clave:
require("express")— Importa la biblioteca Express.app.get("/", ...)— Registra una función que se ejecutará cuando se acceda a la ruta "/".req— Solicitud (request). Información enviada por el cliente.res— Respuesta (response). Información que el servidor enviará.res.send()— Envía datos al cliente.app.listen(3000)— Escucha las solicitudes en el puerto 3000.
Enrutamiento: enviar diferentes datos según la URL
El enrutamiento (routing) consiste en determinar "qué datos se enviarán cuando se realice una solicitud a una determinada dirección". En LIMS, es como ingresar un número de muestra para obtener la información correspondiente, o ingresar el nombre de un protocolo para obtener el protocolo correspondiente.
const express = require("express");
const app = express();
app.get("/", function(req, res) {
res.send("Bienvenido al servidor de datos experimentales");
});
app.get("/genes", function(req, res) {
const genes = [
{ name: "TP53", chromosome: "17p13.1", type: "tumor suppressor" },
{ name: "BRCA1", chromosome: "17q21.31", type: "DNA repair" },
{ name: "EGFR", chromosome: "7p11.2", type: "receptor tyrosine kinase" }
];
res.json(genes);
});
app.get("/protocols", function(req, res) {
const protocols = ["DNA Extraction", "PCR", "Western Blot", "ELISA"];
res.json(protocols);
});
app.listen(3000, function() {
console.log("Servidor iniciado: http://localhost:3000");
});Al acceder a http://localhost:3000/genes, se muestra una lista de genes en formato JSON, y al acceder a /protocols, se muestra una lista de protocolos en formato JSON. res.json() convierte un objeto JavaScript en formato JSON y lo envía.
Enrutamiento dinámico: recibir parámetros desde la URL
Si desea consultar solo la información de un gen específico, incluya un parámetro en la URL:
const geneDatabase = {
TP53: { name: "TP53", chromosome: "17p13.1", function: "tumor suppressor", length_bp: 19149 },
BRCA1: { name: "BRCA1", chromosome: "17q21.31", function: "DNA repair", length_bp: 81189 },
EGFR: { name: "EGFR", chromosome: "7p11.2", function: "receptor tyrosine kinase", length_bp: 188307 }
};
app.get("/gene/:id", function(req, res) {
const geneId = req.params.id;
const gene = geneDatabase[geneId];
if (gene) {
res.json(gene);
} else {
res.status(404).json({ error: "No se encontró el gen solicitado", query: geneId });
}
});Al acceder a /gene/TP53 se obtiene la información de TP53, y al acceder a /gene/XYZ123 se devuelve un error 404. La parte :id es un parámetro de la URL; este cambia cada vez que se realiza una solicitud y se extrae con req.params.id.
Middleware: etapa de preprocesamiento experimental
El middleware es un concepto clave de Express. Es la etapa de procesamiento que se realiza entre el momento en que una solicitud llega al servidor y el momento en que se envía una respuesta.
Piense en el proceso de preprocesamiento que se realiza en un experimento antes de que una muestra entre en el equipo de análisis:
[Recepción de la muestra] → [Verificación de la etiqueta] → [Control de calidad] → [Preprocesamiento] → [Análisis] → [Informe de resultados]En Express, las solicitudes también siguen el mismo flujo de trabajo:
[Llega la solicitud] → [Middleware 1] → [Middleware 2] → [Controlador de ruta] → [Envío de la respuesta]El middleware se registra como app.use():
const express = require("express");
const app = express();
app.use(function(req, res, next) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
app.get("/genes", function(req, res) {
res.json([{ name: "TP53" }, { name: "BRCA1" }]);
});
app.listen(3000);La función registrada con app.use() se ejecuta para cada solicitud. Aquí, cada vez que llega una solicitud, se registra la hora, el método (GET/POST) y la dirección en la consola. Es como escribir en un cuaderno de laboratorio: "A qué hora, qué muestra y qué análisis se realizó".
Lo importante es next(). Esta función debe llamarse para pasar a la siguiente etapa (el siguiente middleware o controlador de ruta). Si no se llama a next(), la solicitud se detiene aquí y no se envía ninguna respuesta, al igual que una muestra que no pasa el control de calidad no pasa a la siguiente etapa.
Middleware práctico: análisis de datos POST
Si el cliente envía datos mediante el método POST (por ejemplo, para registrar una nueva muestra), es necesario analizarlos para que se puedan leer. Express proporciona un middleware integrado para ello:
app.use(express.json());
app.post("/samples", function(req, res) {
const newSample = req.body;
console.log("Nueva muestra registrada:", newSample);
res.json({ message: "Muestra registrada correctamente", sample: newSample });
});Si no hay express.json(), entonces req.body es undefined. Este middleware analiza automáticamente los datos que llegan en formato JSON y los introduce en req.body.
Ejemplo completo: API de gestión de muestras
Vamos a crear un servidor de gestión de muestras sencillo combinando lo que hemos aprendido hasta ahora:
const express = require("express");
const app = express();
app.use(express.json());
app.use(function(req, res, next) {
console.log(`[LOG] ${req.method} ${req.url}`);
next();
});
const samples = [
{ id: "S001", name: "Blood Sample A", od: 1.85, status: "pass" },
{ id: "S002", name: "Tissue Sample B", od: 0.42, status: "fail" },
{ id: "S003", name: "Serum Sample C", od: 2.10, status: "pass" }
];
app.get("/samples", function(req, res) {
res.json(samples);
});
app.get("/sample/:id", function(req, res) {
const found = samples.find(function(s) {
return s.id === req.params.id;
});
if (found) {
res.json(found);
} else {
res.status(404).json({ error: "No se encontró la muestra" });
}
});
app.get("/samples/passed", function(req, res) {
const passed = samples.filter(function(s) {
return s.status === "pass";
});
res.json({ count: passed.length, samples: passed });
});
app.listen(3000, function() {
console.log("Servidor de gestión de muestras iniciado: http://localhost:3000");
});Este servidor ofrece tres puntos finales de API:
GET /samples— Lista completa de muestrasGET /sample/S001— Consulta de una muestra específicaGET /samples/passed— Filtrado para mostrar solo las muestras que pasaron el control de calidad
Si realiza una solicitud a este servidor utilizando fetch, que aprendimos anteriormente:
fetch("http://localhost:3000/samples/passed")
.then(function(response) { return response.json(); })
.then(function(data) { console.log(data); });
// { count: 2, samples: [{ id: "S001", ... }, { id: "S003", ... }] }El siguiente es el momento en que el frontend (fetch) y el backend (Express) se conectan. Es una versión simplificada de lo que sucede cuando se busca un gen en NCBI.
¡Pruébalo tú mismo! (Ejemplo atenuado)
Completa los espacios en blanco a continuación para completar la ruta de Express que devuelve la información del gen.
const express = require("express");const app = express();app.get("/gene/", function(req, res) {const geneId = req..id;res.json({ gene: geneId, found: true });});app.listen();
Errores comunes y soluciones
P: Obtengo un error Cannot GET /ruta
La ruta para ese endpoint no ha sido registrada. Verifique si existe el código app.get("/ruta", ...) y que no haya errores tipográficos. Si no hay una ruta registrada, Express envía automáticamente el mensaje Cannot GET.
P: req.body es undefined
Asegúrese de que app.use(express.json()) esté declarado antes de la ruta. El middleware se ejecuta en el orden del código. Si el middleware de parsing de JSON se encuentra después de la ruta, la solicitud llegará a la ruta sin haber sido procesada aún.
P: Modifiqué el servidor pero los cambios no se reflejan
Node.js no aplica los cambios automáticamente al modificar el código. Detenga el servidor en la terminal con Ctrl+C y ejecútelo de nuevo con node server.js. Si necesita reinicio automático, instale nodemon y ejecútelo con nodemon server.js.
P: Error: listen EADDRINUSE: address already in use :::3000
Ya hay un programa utilizando el puerto 3000. Es posible que un servidor ejecutado anteriormente aún esté activo. Verifique con lsof -i :3000 y finalícelo, o cambie el número de puerto a 3001, por ejemplo.
P: Dicen que Express es un "framework", ¿en qué se diferencia de otros paquetes que se instalan con npm (como mysql2, etc.)?
La diferencia clave es quién llama a quién. mysql2 es una librería: usted llama a connection.query() cuando lo necesita. Express es un framework: si registra código como app.get("/ruta", callback), cuando llegue una solicitud, Express llamará a su callback.
Este concepto se denomina "Inversión de Control (Inversion of Control)". Para una analogía y explicación más detalladas, consulte el tema Framework y Librería.