Vamos a crear una API de tareas con JavaScript y los módulos nativos de Node.js. Podrás listar tareas y crear una nueva enviando JSON. El objetivo es entender el contrato HTTP y la validación antes de añadir un framework, usuarios o una base de datos.
Última revisión: 2 de octubre de 2026. Probado con: Node.js 24.15.0, la versión disponible en este entorno. La rama 24 figura como LTS en el calendario oficial. No se necesitan paquetes npm. Las tareas viven en memoria y desaparecen al reiniciar; el servidor escucha únicamente en localhost.
Define el contrato
La API tendrá dos operaciones: GET /tasks responde con un arreglo y POST /tasks acepta un objeto con title. Un título debe contener entre 1 y 120 caracteres después de recortar espacios. Crear devuelve 201, y los errores devuelven un objeto con error.

Crea una carpeta vacía y guarda lo siguiente como server.mjs. La extensión permite usar imports sin añadir type: module a un package.json.
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';
const tasks = new Map();
const send = (res, status, data) => {
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(data));
};
async function readJson(req) {
const chunks = [];
let size = 0;
for await (const chunk of req) {
size += chunk.length;
if (size > 8192) {
throw Object.assign(new Error('Cuerpo demasiado grande'), { status: 413 });
}
chunks.push(chunk);
}
try {
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
} catch {
throw Object.assign(new Error('JSON inválido'), { status: 400 });
}
}
const server = createServer(async (req, res) => {
try {
const path = new URL(req.url, 'http://127.0.0.1').pathname;
if (path !== '/tasks') return send(res, 404, { error: 'Ruta inexistente' });
if (req.method === 'GET') return send(res, 200, [...tasks.values()]);
if (req.method !== 'POST') {
res.setHeader('Allow', 'GET, POST');
return send(res, 405, { error: 'Método no permitido' });
}
const mime = req.headers['content-type']?.split(';')[0].trim().toLowerCase();
if (mime !== 'application/json') {
return send(res, 415, { error: 'Usa application/json' });
}
const body = await readJson(req);
if (!body || typeof body !== 'object' || Array.isArray(body) ||
typeof body.title !== 'string') {
return send(res, 400, { error: 'title debe ser texto' });
}
const title = body.title.trim();
if (title.length < 1 || title.length > 120) {
return send(res, 400, { error: 'title admite de 1 a 120 caracteres' });
}
const task = { id: randomUUID(), title, done: false };
tasks.set(task.id, task);
send(res, 201, task);
} catch (error) {
if (!res.destroyed && !res.headersSent) {
send(res, error.status ?? 500, { error: error.status ? error.message : 'Error interno' });
}
}
});
server.requestTimeout = 10000;
server.listen(3080, '127.0.0.1', () => console.log('API: http://127.0.0.1:3080/tasks'));send centraliza las respuestas para que los éxitos y errores tengan el mismo formato. readJson cuenta bytes antes de interpretar la entrada: un cuerpo grande no debe acumularse sin límite. No basta con confiar en Content-Length, porque una petición puede llegar por fragmentos.
La lectura por iterador entrega buffers. Por eso se unen con Buffer.concat antes de convertir a UTF-8: un carácter multibyte puede quedar dividido entre fragmentos. El límite es 8 KiB, una decisión suficiente para este objeto pequeño.
El código valida el tipo de title antes de llamar a trim. randomUUID genera un identificador y el Map guarda la tarea por ese identificador. No copiamos arbitrariamente todas las propiedades del cuerpo: done siempre empieza en false.
Arranca y prueba
Ejecuta node server.mjs. En otra terminal prueba estos comandos de Bash; en Windows utiliza curl.exe y guarda el JSON en un archivo si tu shell modifica las comillas.
curl -i http://127.0.0.1:3080/tasks
curl -i -X POST http://127.0.0.1:3080/tasks \
-H 'Content-Type: application/json' \
--data '{"title":"Revisar el servidor"}'
curl -i http://127.0.0.1:3080/tasksLa primera consulta debe devolver []. El POST devuelve 201 con id, title y done. La última consulta muestra esa tarea. Para Windows, crea task.json con el mismo objeto y envía curl.exe -i -H "Content-Type: application/json" --data-binary "@task.json" http://127.0.0.1:3080/tasks.
Ahora envía {"title":" "} y confirma un 400; envía JSON truncado y confirma otro 400. Una ruta diferente produce 404, un PUT en /tasks produce 405, y un cuerpo sin el tipo JSON produce 415. Con cuerpos que exceden el límite, Node puede cerrar la conexión durante el aborto de la lectura; el consumidor también debe manejar errores de transporte.
Si aparece EADDRINUSE, hay otro proceso en el puerto 3080: cambia el puerto o detén tu instancia anterior. Si la lista se vacía después de reiniciar, es el comportamiento del Map, no un fallo de escritura.
Antes de exponer esta API a internet, añade persistencia y autorización por usuario, limita la frecuencia de peticiones y sitúala detrás de HTTPS. Una identidad aleatoria distingue tareas, pero no determina quién tiene permiso para verlas.
Fuentes y documentación
Proyecto: Node.js. Desarrollador: colaboradores de Node.js, bajo OpenJS Foundation. Descarga: Node.js oficial. Licencia: MIT y avisos de dependencias. El código y el diagrama del ejemplo son originales de The Silencers; no se redistribuye el runtime.






Comments 0
Join the conversation with your account.
Sign inLoading comments…