Skip to content

fs, path y os ​

Tres módulos que usarás constantemente: fs para leer y escribir archivos, path para construir rutas que funcionen en cualquier sistema operativo y os para obtener información de la máquina. Con ellos podrás crear scripts de automatización, guardar logs, generar reportes y más.

Concepto ​

  • fs (file system): es tu "explorador de archivos" programable.
  • path: arma rutas correctamente. Windows usa \ y Linux/macOS usan /; path se encarga de eso.
  • os: información del sistema: CPU, memoria, usuario, carpeta temporal.

fs tiene tres estilos de API:

EstiloImportaciónCuándo usarlo
Promesasnode:fs/promises✅ Por defecto, con async/await
Callbacksnode:fsCódigo antiguo
Síncrononode:fs (readFileSync)Scripts sencillos o al arrancar la app

path ​

js
// rutas.js (ESM)
import path from 'node:path'

const archivo = '/usuarios/ana/proyectos/reporte.final.pdf'

console.log(path.basename(archivo))          // reporte.final.pdf
console.log(path.basename(archivo, '.pdf'))  // reporte.final
console.log(path.extname(archivo))           // .pdf
console.log(path.dirname(archivo))           // /usuarios/ana/proyectos

// join une segmentos con el separador correcto
console.log(path.join('datos', 'clientes', 'lista.json'))
// datos/clientes/lista.json  (o datos\clientes\lista.json en Windows)

// resolve devuelve una ruta ABSOLUTA a partir de la carpeta actual
console.log(path.resolve('datos', 'lista.json'))
// C:\Users\tu\proyecto\datos\lista.json

// Ruta relativa al archivo actual (no a donde ejecutas node)
const rutaSegura = path.join(import.meta.dirname, 'datos', 'lista.json')

// Descomponer una ruta
console.log(path.parse(archivo))
// { root: '/', dir: '/usuarios/ana/proyectos', base: 'reporte.final.pdf', ext: '.pdf', name: 'reporte.final' }

Error común

Usar rutas relativas como './datos.json' con fs. Son relativas a la carpeta desde donde ejecutas node (process.cwd()), no al archivo. Si ejecutas desde otra carpeta, falla. Usa path.join(import.meta.dirname, 'datos.json').

fs/promises: leer y escribir ​

js
// archivos.js
import { readFile, writeFile, appendFile, mkdir, readdir, stat, rm, rename, copyFile, access } from 'node:fs/promises'
import path from 'node:path'

const carpeta = path.join(import.meta.dirname, 'salida')

// Crear carpeta (recursive: no falla si ya existe y crea las intermedias)
await mkdir(carpeta, { recursive: true })

// Escribir (sobrescribe si existe)
const rutaNotas = path.join(carpeta, 'notas.txt')
await writeFile(rutaNotas, 'Primera línea\n', 'utf8')

// Agregar al final
await appendFile(rutaNotas, 'Segunda línea\n')

// Leer
const contenido = await readFile(rutaNotas, 'utf8')
console.log(contenido)

// Listar archivos de una carpeta
const archivos = await readdir(carpeta)
console.log(archivos) // [ 'notas.txt' ]

// Información de un archivo
const info = await stat(rutaNotas)
console.log(info.size, 'bytes', info.isFile(), info.mtime)

// Copiar, renombrar/mover
await copyFile(rutaNotas, path.join(carpeta, 'copia.txt'))
await rename(path.join(carpeta, 'copia.txt'), path.join(carpeta, 'respaldo.txt'))

// ¿Existe?
async function existe(ruta) {
  try {
    await access(ruta)
    return true
  } catch {
    return false
  }
}
console.log(await existe(rutaNotas)) // true

// Borrar archivo o carpeta completa
await rm(carpeta, { recursive: true, force: true })

Manejar errores ​

js
import { readFile } from 'node:fs/promises'

try {
  const datos = await readFile('no-existe.json', 'utf8')
} catch (error) {
  if (error.code === 'ENOENT') {
    console.log('El archivo no existe')
  } else if (error.code === 'EACCES') {
    console.log('Sin permisos')
  } else {
    throw error
  }
}

Códigos frecuentes: ENOENT (no existe), EACCES (permisos), EEXIST (ya existe), EISDIR (es una carpeta).

Trabajar con JSON ​

js
// db-json.js — una "base de datos" mínima en un archivo JSON
import { readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'

const RUTA = path.join(import.meta.dirname, 'tareas.json')

export async function leerTareas() {
  try {
    return JSON.parse(await readFile(RUTA, 'utf8'))
  } catch (error) {
    if (error.code === 'ENOENT') return [] // primera vez
    throw error
  }
}

export async function guardarTareas(tareas) {
  // null, 2 = formato legible con 2 espacios
  await writeFile(RUTA, JSON.stringify(tareas, null, 2))
}

export async function agregarTarea(titulo) {
  const tareas = await leerTareas()
  const nueva = { id: crypto.randomUUID(), titulo, hecha: false }
  tareas.push(nueva)
  await guardarTareas(tareas)
  return nueva
}

Vigilar cambios ​

js
import { watch } from 'node:fs/promises'

const watcher = watch('./src', { recursive: true })
for await (const evento of watcher) {
  console.log(`${evento.eventType}: ${evento.filename}`)
}

os ​

js
// sistema.js
import os from 'node:os'

const gb = (bytes) => (bytes / 1024 ** 3).toFixed(2) + ' GB'

console.log('Sistema:', os.type(), os.release())   // Windows_NT 10.0...
console.log('Plataforma:', os.platform())          // win32 | linux | darwin
console.log('Arquitectura:', os.arch())            // x64 | arm64
console.log('CPU:', os.cpus()[0].model, `(${os.cpus().length} núcleos)`)
console.log('Memoria total:', gb(os.totalmem()))
console.log('Memoria libre:', gb(os.freemem()))
console.log('Usuario:', os.userInfo().username)
console.log('Carpeta personal:', os.homedir())
console.log('Temporales:', os.tmpdir())
console.log('Encendido hace:', (os.uptime() / 3600).toFixed(1), 'horas')
console.log('Salto de línea:', JSON.stringify(os.EOL))

Caso real: organizador de descargas ​

Un script que ordena una carpeta en subcarpetas según el tipo de archivo:

js
// organizar.js
import { readdir, mkdir, rename, stat } from 'node:fs/promises'
import path from 'node:path'
import os from 'node:os'

const CATEGORIAS = {
  imagenes: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'],
  documentos: ['.pdf', '.docx', '.xlsx', '.pptx', '.txt', '.md'],
  comprimidos: ['.zip', '.rar', '.7z'],
  videos: ['.mp4', '.mov', '.mkv'],
  codigo: ['.js', '.html', '.css', '.json', '.php']
}

function categoriaDe(extension) {
  for (const [categoria, extensiones] of Object.entries(CATEGORIAS)) {
    if (extensiones.includes(extension)) return categoria
  }
  return 'otros'
}

// Carpeta a organizar: argumento o Descargas por defecto
const carpeta = process.argv[2] ?? path.join(os.homedir(), 'Downloads')
const simular = process.argv.includes('--simular')

const nombres = await readdir(carpeta)
let movidos = 0

for (const nombre of nombres) {
  const origen = path.join(carpeta, nombre)
  const info = await stat(origen)
  if (!info.isFile()) continue

  const categoria = categoriaDe(path.extname(nombre).toLowerCase())
  const destinoCarpeta = path.join(carpeta, categoria)
  const destino = path.join(destinoCarpeta, nombre)

  console.log(`${nombre} → ${categoria}/`)
  if (!simular) {
    await mkdir(destinoCarpeta, { recursive: true })
    await rename(origen, destino)
  }
  movidos++
}

console.log(`\n${movidos} archivos ${simular ? 'por mover (simulación)' : 'movidos'}.`)
bash
node organizar.js ./prueba --simular
node organizar.js ./prueba

Buena práctica

Agrega siempre un modo --simular (dry run) a los scripts que mueven o borran archivos. Primero ves qué pasaría y luego lo ejecutas de verdad.

Error común

Usar readFileSync dentro de una ruta de un servidor web. Cada petición bloquea el event loop. En servidores usa siempre fs/promises.

Ejercicios ​

  1. Escribe contar.js que reciba una carpeta y muestre cuántos archivos hay por extensión y el tamaño total en KB.
  2. Escribe log.js que exporte registrar(mensaje) y agregue líneas con fecha ISO a logs/app.log (crea la carpeta si no existe).
Ver solución
js
// contar.js
import { readdir, stat } from 'node:fs/promises'
import path from 'node:path'

const carpeta = process.argv[2] ?? '.'
const conteo = {}
let total = 0

for (const nombre of await readdir(carpeta)) {
  const info = await stat(path.join(carpeta, nombre))
  if (!info.isFile()) continue
  const ext = path.extname(nombre).toLowerCase() || '(sin extensión)'
  conteo[ext] = (conteo[ext] ?? 0) + 1
  total += info.size
}

console.table(conteo)
console.log(`Tamaño total: ${(total / 1024).toFixed(1)} KB`)
js
// log.js
import { appendFile, mkdir } from 'node:fs/promises'
import path from 'node:path'

const carpeta = path.join(import.meta.dirname, 'logs')

export async function registrar(mensaje) {
  await mkdir(carpeta, { recursive: true })
  const linea = `[${new Date().toISOString()}] ${mensaje}\n`
  await appendFile(path.join(carpeta, 'app.log'), linea)
}

// Prueba rápida
await registrar('La aplicación arrancó')

Resumen ​

  • Usa node:fs/promises con async/await; maneja errores por error.code.
  • path.join y import.meta.dirname crean rutas seguras y multiplataforma.
  • mkdir con recursive: true y rm con recursive, force simplifican el manejo de carpetas.
  • os da información del sistema: memoria, CPU, carpetas del usuario.

Academia Web — aprende, copia y construye.