Node.js
Paths, URLs y filesystem
Manejo seguro de rutas, URLs y archivos en Node.js con path, URL y fs, incluyendo diferencias entre plataformas, permisos y operaciones asíncronas.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Node.js
Manejo seguro de rutas, URLs y archivos en Node.js con path, URL y fs, incluyendo diferencias entre plataformas, permisos y operaciones asíncronas.
import path from "node:path";
path.join("data", "users.json");
path.resolve("data", "users.json");join concatena y normaliza segmentos.resolve procesa desde la derecha hasta formar un path absoluto, usando cwd si hace falta.No uses concatenación manual con /; Windows y POSIX tienen separadores y raíces diferentes.
Un path relativo depende de una base. Declara cuál:
const configPath = path.resolve(process.cwd(), "config.json");Para un recurso junto al módulo:
const configUrl = new URL("./config.json", import.meta.url);Muchas APIs de fs aceptan file URLs directamente.
import { fileURLToPath, pathToFileURL } from "node:url";
const pathName = fileURLToPath(import.meta.url);
const fileUrl = pathToFileURL(pathName);No elimines manualmente el prefijo file://; codificación, hosts y plataformas hacen esa conversión más compleja.
import { readFile } from "node:fs/promises";
const text = await readFile(new URL("./data.txt", import.meta.url), "utf8");Node ofrece APIs síncronas, callbacks y promises.
No existe una prohibición absoluta; importa dónde y cuánto bloquea.
import { writeFile, rename } from "node:fs/promises";
const target = new URL("./settings.json", import.meta.url);
const temp = new URL("./settings.json.tmp", import.meta.url);
await writeFile(temp, JSON.stringify(settings), { mode: 0o600 });
await rename(temp, target);Escribir a un temporal y renombrar puede reducir archivos parcialmente escritos en el mismo filesystem. No convierte cualquier operación en transacción universal.
import { open } from "node:fs/promises";
const file = await open("report.txt", "r");
try {
const stats = await file.stat();
console.log(stats.size);
} finally {
await file.close();
}Un descriptor es un recurso limitado. Ciérralo incluso cuando falla el procesamiento.
No compruebes “existe” y luego abras suponiendo que nada cambió.
check
↓ otra operación modifica el path
useIntenta la operación final y maneja su error. Las comprobaciones previas solo sirven para experiencia de usuario, no como garantía de seguridad.
const base = path.resolve("uploads");
const candidate = path.resolve(base, userInput);
if (!candidate.startsWith(`${base}${path.sep}`)) {
throw new Error("Invalid path");
}Este control es una base, no una defensa completa frente a symlinks y cambios concurrentes. Usa nombres generados, directorios aislados y permisos mínimos.
Los watchers dependen del sistema operativo. Eventos pueden agruparse, duplicarse o perder detalles; no diseñes sincronización crítica suponiendo un evento perfecto por cambio.
readFile carga el contenido completo. Para gigabytes, utiliza streams y procesa por partes.
readFile → memoria proporcional al archivo
stream → buffers acotados + procesamiento incrementalexists antes de abrir.fs puede fallar por razones normales: permisos, carreras, espacio o desaparición.¿Por qué access() seguido de open() no garantiza que abrirás el mismo recurso comprobado?
Porque otra operación puede cambiar, reemplazar o eliminar el path entre ambas llamadas. Esa ventana es una condición TOCTOU.
Buffers, bytes y codificación explica qué datos circulan realmente por archivos y red.