Copiar, renombrar y borrar archivos y carpetas con Node.js

Todo lo que harías con el explorador de archivos o con cp, mv y rm en la terminal se puede hacer desde JavaScript con node:fs/promises. Sin dependencias.

Las operaciones básicas, de una vez

import { mkdir, writeFile, copyFile, cp, rename, rm, readdir, stat } from "node:fs/promises";
import path from "node:path";

// Crear carpetas anidadas y un par de archivos
await mkdir("datos/2026/octubre", { recursive: true });
await writeFile("datos/informe.txt", "Ventas de octubre\n");
await writeFile("datos/2026/octubre/ventas.csv", "producto,unidades\nteclado,12\n");

// Copiar un archivo, copiar una carpeta entera, renombrar y borrar
await copyFile("datos/informe.txt", "datos/informe-copia.txt");
await cp("datos/2026", "respaldo/2026", { recursive: true });
await rename("datos/informe-copia.txt", "datos/informe-viejo.txt");
await rm("datos/informe-viejo.txt");

const info = await stat("datos/informe.txt");
console.log("informe.txt:", info.size, "bytes; ¿es un archivo?", info.isFile());

// Lo que ha quedado, ordenado
for (const raiz of ["datos", "respaldo"]) {
  const rutas = await readdir(raiz, { recursive: true });
  for (const r of rutas.sort()) console.log(path.join(raiz, r));
}
salidainforme.txt: 18 bytes; ¿es un archivo? true
datos/2026
datos/2026/octubre
datos/2026/octubre/ventas.csv
datos/informe.txt
respaldo/2026
respaldo/2026/octubre
respaldo/2026/octubre/ventas.csv
ParaFunciónEquivale a
Crear carpetas, también anidadasmkdir(ruta, { recursive: true })mkdir -p
Copiar un archivocopyFile(origen, destino)cp
Copiar una carpeta enteracp(origen, destino, { recursive: true })cp -r
Renombrar o moverrename(viejo, nuevo)mv
Borrar un archivorm(ruta)rm
Borrar una carpeta con todorm(ruta, { recursive: true, force: true })rm -rf
Tamaño, fechas, tipostat(ruta)ls -l

Con recursive: true, mkdir crea las carpetas intermedias que falten y no se queja si ya existen. rename también sirve para mover un archivo de carpeta, siempre que sea dentro del mismo disco.

Listar una carpeta

import { readdir } from "node:fs/promises";
import path from "node:path";

const entradas = await readdir("datos", { recursive: true, withFileTypes: true });
for (const e of entradas.filter((e) => e.isFile())) {
  console.log(path.join(e.parentPath, e.name));
}

const csv = (await readdir("datos", { recursive: true })).filter((r) => path.extname(r) === ".csv");
console.log(csv);
salidadatos/informe.txt
datos/2026/octubre/ventas.csv
[ '2026/octubre/ventas.csv' ]

Con withFileTypes: true, cada entrada trae métodos como isFile() e isDirectory(), y parentPath, la carpeta donde está. Sin esa opción, readdir devuelve solo los nombres, relativos a la carpeta que listas. El orden en que llegan no está garantizado: si importa, ordénalos.

Rutas: relativas, absolutas y la carpeta del propio archivo

import path from "node:path";
import { fileURLToPath } from "node:url";

console.log(path.join("datos", "2026", "..", "informe.txt"));
console.log(path.resolve("datos/informe.txt"));
console.log(path.basename("/w/datos/informe.txt"), path.extname("ventas.tar.gz"), path.dirname("/w/datos/informe.txt"));
console.log(path.parse("/w/datos/informe.txt"));

console.log("cwd:", process.cwd());
console.log("este archivo:", import.meta.filename);
console.log("su carpeta:", import.meta.dirname);
console.log(path.join(import.meta.dirname, "plantillas", "correo.html"));
salidadatos/informe.txt
/w/datos/informe.txt
informe.txt .gz /w/datos
{
  root: '/',
  dir: '/w/datos',
  base: 'informe.txt',
  ext: '.txt',
  name: 'informe'
}
cwd: /w
este archivo: /w/rutas.mjs
su carpeta: /w
/w/plantillas/correo.html

path.join une trozos con la barra correcta del sistema (en Windows, la invertida) y resuelve los ... path.resolve convierte una ruta relativa en absoluta. Pero la pregunta importante es: ¿relativa a qué?

Las rutas relativas parten de process.cwd(); import.meta.dirname es la carpeta del archivoDesde dónde se resuelve una ruta relativa/home/ana/tienda/datos/informe.txtsrc/app.mjsplantillas/correo.htmlprocess.cwd()la carpeta desde la queejecutas node, sea cual sea:cd tienda && node src/app.mjsimport.meta.dirnamela carpeta del propio archivo,se lance desde donde se lance:/home/ana/tienda/src«datos/informe.txt» se busca en process.cwd(); para lo que va con el código, usa import.meta.dirname.

Una ruta relativa depende de dónde lanzas node; import.meta.dirname no. Esquema: decodigo.com.

Las rutas relativas se resuelven desde process.cwd(), la carpeta donde estabas al escribir node. Si tu programa lee un archivo que va con el código, como una plantilla, y alguien lo lanza desde otra carpeta, no lo encontrará. Para eso está import.meta.dirname, la carpeta del propio archivo .mjs, disponible desde Node 20.11.

Los errores, por su código

import { rm, rename, mkdir, copyFile, constants } from "node:fs/promises";

for (const [nombre, accion] of [
  ["rm de una carpeta sin recursive", () => rm("datos")],
  ["rename de algo que no existe", () => rename("no-existe.txt", "otro.txt")],
  ["mkdir de una carpeta que ya existe", () => mkdir("datos")],
  ["copyFile sin sobrescribir", () => copyFile("datos/informe.txt", "datos/informe.txt", constants.COPYFILE_EXCL)],
  ["rm con force de algo que no existe", () => rm("no-existe", { force: true })],
]) {
  try {
    await accion();
    console.log(`${nombre}: bien`);
  } catch (err) {
    console.log(`${nombre}: ${err.code}`);
    console.log(`  ${err.message}`);
  }
}
await rm("respaldo", { recursive: true, force: true });
console.log("respaldo borrado entero");
salidarm de una carpeta sin recursive: ERR_FS_EISDIR
  Path is a directory: rm returned EISDIR (is a directory) datos
rename de algo que no existe: ENOENT
  ENOENT: no such file or directory, rename 'no-existe.txt' -> 'otro.txt'
mkdir de una carpeta que ya existe: EEXIST
  EEXIST: file already exists, mkdir 'datos'
copyFile sin sobrescribir: EEXIST
  EEXIST: file already exists, copyfile 'datos/informe.txt' -> 'datos/informe.txt'
rm con force de algo que no existe: bien
respaldo borrado entero
CódigoSignificaCuándo aparece
ENOENTNo existeLeer, copiar o renombrar algo que no está
EEXISTYa existemkdir sin recursive, o copiar con COPYFILE_EXCL
ERR_FS_EISDIREs una carpetarm de una carpeta sin recursive
EACCES / EPERMSin permisoCarpetas del sistema o de otro usuario
copyFile sobrescribe sin preguntar. Si el destino ya existe, lo reemplaza. Para evitarlo, pasa constants.COPYFILE_EXCL como tercer argumento, como en el ejemplo: entonces falla con EEXIST en vez de pisar el archivo. Y rm con recursive y force borra sin papelera: compruébalo dos veces antes de pasarle una ruta que venga de fuera.

Todo el código de esta página se ejecutó con Node.js 24.22 en un contenedor limpio antes de publicarla; las salidas, incluidos los errores, están copiadas de esa ejecución. Documentación oficial: API de fs/promises de Node.js.