Saltar al contenido
Curso de Node.js y APIs
Todos los cursos Ir al ejercicio →

Módulos: CommonJS, ES Modules, fs y path

  • ⏱ 50 minutos
  • 📶 Nivel: principiante
  • 🧩 Incluye ejercicio

💡 🎯 Al terminar esta lección podrás…

  • Distinguir CommonJS (require) de ES Modules (import) y activar el segundo.
  • Exportar e importar funciones entre archivos.
  • Leer y escribir archivos de texto y JSON con fs/promises.
  • Construir rutas seguras con path.
  • Leer argumentos con process.argv.

1.Por qué existen los módulos

Un programa real no cabe en un solo archivo. Se divide en módulos: archivos pequeños, cada uno con una responsabilidad, que se exportan piezas (funciones, objetos, constantes) y se importan unos a otros. Así el código se ordena, se reutiliza y se prueba por partes.

En Node conviven dos sistemas de módulos, por razones históricas:

CommonJS (CJS)ES Modules (ESM)
Sintaxisrequire() y module.exportsimport y export
OrigenEl sistema original de Node (2009)El estándar de JavaScript; el mismo que usan los navegadores y React
Se activa conEs el valor por defecto de los archivos .js si no indicas lo contrario"type": "module" en package.json, o la extensión .mjs
Se cargaDe forma síncronaDe forma asíncrona (permite await en el nivel superior)

⚠️ Importante · En este curso usaremos ES Modules

Es la sintaxis moderna y la misma que ya usas en React. Verás CommonJS en muchísimos proyectos y tutoriales antiguos, así que conviene reconocerlo. Lo explicamos primero para que no te sorprenda.

2.CommonJS: require y module.exports

Imagina dos archivos en la misma carpeta. En mates.js decides qué se comparte con module.exports; en app.js lo recoges con require:

mates.js (CommonJS)
function sumar(a, b) {
  return a + b;
}

function multiplicar(a, b) {
  return a * b;
}

module.exports = { sumar, multiplicar };
app.js (CommonJS)
const { sumar, multiplicar } = require("./mates.js");

console.log(sumar(2, 3));
console.log(multiplicar(4, 5));
Salida esperada
5
20

Lo que no está dentro de module.exports queda privado en su archivo. Las rutas que empiezan por ./ son relativas al archivo actual. Los nombres sin ruta (require("fs")) buscan módulos integrados o paquetes instalados.

📌 Nota · Por qué lo ves en tantos sitios

Durante más de diez años CommonJS fue la única opción. Por eso la mayoría de tutoriales antiguos y muchos proyectos existentes usan require.

3.ES Modules: import y export

Con ES Modules el mismo ejemplo se escribe con export e import. Primero se activa el modo módulo en package.json:

package.json
{
  "name": "curso-node",
  "version": "1.0.0",
  "type": "module"
}
mates.js (ESM)
export function sumar(a, b) {
  return a + b;
}

export function multiplicar(a, b) {
  return a * b;
}

// Una exportación por defecto: solo puede haber una por archivo
export default function restar(a, b) {
  return a - b;
}
app.js (ESM)
import restar, { sumar, multiplicar } from "./mates.js";

console.log(sumar(2, 3));
console.log(multiplicar(4, 5));
console.log(restar(9, 4));
Salida esperada
5
20
5
FormaSe exporta conSe importa con
Con nombre (varias por archivo)export function sumar() {}import { sumar } from "./mates.js"
Por defecto (una por archivo)export default restarimport restar from "./mates.js" (el nombre lo eliges tú)
Todo junto—import * as mates from "./mates.js"

⚠️ Importante · En ESM la extensión es obligatoria

En imports relativos debes escribir la extensión completa: "./mates.js", no "./mates". Es una diferencia frecuente con lo que se hace en React con Vite, donde se puede omitir.

Diferencias que te encontrarás

💡 Consejo · Cómo saber en qué modo estás

Si ves SyntaxError: Cannot use import statement outside a module, tu proyecto está en CommonJS: añade "type": "module" a package.json. Si ves require is not defined in ES module scope, es lo contrario: estás en ESM y usaste require.

4.Módulos integrados y el prefijo node:

Node incluye muchos módulos sin necesidad de instalar nada: fs (archivos), path (rutas), http (servidores), os (sistema), crypto (criptografía)… Se importan con el prefijo node:, que deja claro que son del propio Node y no paquetes de terceros:

Importar módulos integrados
import fs from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import os from "node:os";

Por ahora usarás dos de ellos: fs para leer y escribir archivos y path para construir rutas. En la lección 4 llegará el servidor web.

5.Leer y escribir archivos con fs

El módulo fs (file system) trabaja con archivos y carpetas. Hoy se usa su versión basada en promesas, node:fs/promises, con await. Crea un archivo notas.txt con dos líneas y este script leer.js:

notas.txt
Comprar pan
Estudiar Node
leer.js
import { readFile } from "node:fs/promises";

const texto = await readFile("notas.txt", "utf8");
console.log(texto);
console.log("Líneas:", texto.split("\n").length);
Salida esperada
Comprar pan
Estudiar Node
Líneas: 2

El segundo argumento, "utf8", indica que quieres texto. Sin él, readFile devuelve un Buffer (bytes sin interpretar). Escribir es igual de directo:

escribir.js
import { writeFile, appendFile } from "node:fs/promises";

// Crea el archivo o lo SOBRESCRIBE si ya existe
await writeFile("saludo.txt", "Hola desde Node\n", "utf8");

// Añade al final sin borrar lo anterior
await appendFile("saludo.txt", "Otra línea\n", "utf8");

console.log("Archivos escritos");
Salida esperada
Archivos escritos
FunciónQué hace
readFile(ruta, "utf8")Lee un archivo completo como texto
writeFile(ruta, contenido)Crea o sobrescribe un archivo
appendFile(ruta, contenido)Añade contenido al final
readdir(ruta)Lista los nombres de una carpeta
mkdir(ruta, { recursive: true })Crea una carpeta (y las intermedias)
rm(ruta)Borra un archivo (con { recursive: true }, también carpetas)

Archivos JSON: la base de datos más simple

Como JSON es texto, guardar y leer datos estructurados es combinar fs con JSON.stringify y JSON.parse:

guardar-json.js
import { readFile, writeFile } from "node:fs/promises";

const tareas = [
  { id: 1, texto: "Instalar Node", hecha: true },
  { id: 2, texto: "Aprender módulos", hecha: false }
];

// null, 2 → JSON con sangría, fácil de leer
await writeFile("tareas.json", JSON.stringify(tareas, null, 2), "utf8");

const leidas = JSON.parse(await readFile("tareas.json", "utf8"));
console.log(leidas.length, "tareas;", "primera:", leidas[0].texto);
Salida esperada
2 tareas; primera: Instalar Node

Cuando el archivo no existe

Si pides un archivo que no existe, la promesa se rechaza con un error cuyo código es ENOENT (no such file or directory). Se atrapa con try/catch:

seguro.js
import { readFile } from "node:fs/promises";

try {
  const texto = await readFile("no-existe.txt", "utf8");
  console.log(texto);
} catch (error) {
  if (error.code === "ENOENT") {
    console.log("Ese archivo no existe");
  } else {
    throw error; // otro problema: no lo ocultes
  }
}
Salida esperada
Ese archivo no existe

⚠️ Importante · Las rutas relativas dependen de dónde ejecutas

readFile("notas.txt") busca el archivo en la carpeta desde la que lanzaste el comando, no en la carpeta del script. Si ejecutas node src/leer.js desde la raíz, buscará notas.txt en la raíz. Para anclar la ruta al script usa path.join(import.meta.dirname, "notas.txt").

6.Construir rutas con path

Las rutas se escriben distinto según el sistema: datos/notas.txt en macOS y Linux, datos\notas.txt en Windows. Concatenar textos a mano con "/" se rompe tarde o temprano. El módulo path resuelve esas diferencias:

rutas.js
import path from "node:path";

const ruta = path.join("datos", "2026", "notas.txt");
console.log(ruta);
console.log(path.basename(ruta));
console.log(path.basename(ruta, ".txt"));
console.log(path.extname(ruta));
console.log(path.dirname(ruta));
Salida esperada
datos/2026/notas.txt
notas.txt
notas
.txt
datos/2026

La salida de arriba es la de macOS y Linux; en Windows las barras aparecen como \. Resumen:

FunciónPara qué sirveEjemplo → resultado
path.join(...partes)Une partes con el separador correctojoin("a", "b", "c.txt") → a/b/c.txt
path.resolve(...partes)Construye una ruta absolutaresolve("datos") → /tu/carpeta/datos
path.basename(ruta)Nombre del archivobasename("/a/b.txt") → b.txt
path.extname(ruta)Extensión (con punto)extname("b.txt") → .txt
path.dirname(ruta)La carpeta que lo contienedirname("/a/b.txt") → /a

7.Argumentos de la línea de comandos: process.argv

Un script puede recibir datos al ejecutarlo: node saludo.js Ana. Esos datos llegan en process.argv, un array cuyos dos primeros elementos son la ruta de node y la del script; los tuyos empiezan en la posición 2.

saludo.js
console.log(process.argv);

const [nombre = "Mundo"] = process.argv.slice(2);
console.log(`Hola, ${nombre}`);
Terminal
node saludo.js Ana
Salida esperada
[
  '/usr/local/bin/node',
  '/home/ana/curso-node/saludo.js',
  'Ana'
]
Hola, Ana

Con slice(2) te quedas solo con lo que escribió la persona. Para terminar el programa indicando que hubo un problema, asigna un código de salida: 0 significa éxito y cualquier otro número, error.

validar.js
const [archivo] = process.argv.slice(2);

if (!archivo) {
  console.error("Uso: node validar.js <archivo>");
  process.exitCode = 1; // termina con error cuando acabe lo pendiente
} else {
  console.log("Procesando", archivo);
}

Ahora la parte práctica: un analizador de argumentos de estilo --clave valor es JavaScript puro y puedes probarlo aquí mismo, en el navegador.

▶ Un mini analizador de argumentos
Consola

💡 Consejo · Existe una utilidad integrada

Node incluye parseArgs en el módulo node:util, que hace este trabajo con más garantías. Aquí lo escribimos a mano para entender qué ocurre por dentro.

8.Errores frecuentes

📒 Apuntes para llevar

  • Un módulo es un archivo que exporta piezas para que otros las importen.
  • CommonJS: require() y module.exports. ESM: import y export.
  • Para usar ESM pon "type": "module" en package.json (o usa .mjs).
  • En ESM los imports relativos llevan extensión y no existen __dirname ni require.
  • Los módulos propios de Node se importan con el prefijo node: (node:fs, node:path).
  • readFile, writeFile y appendFile de fs/promises trabajan con await.
  • Usa path.join en lugar de concatenar rutas a mano.
  • process.argv.slice(2) contiene los argumentos del usuario.

9.Glosario

TérminoSignificado
MóduloArchivo de código que exporta piezas para reutilizarlas desde otros archivos.
CommonJSSistema de módulos original de Node: require / module.exports.
ES Modules (ESM)Sistema de módulos estándar de JavaScript: import / export.
fsMódulo integrado para trabajar con archivos y carpetas.
pathMódulo integrado para construir y analizar rutas.
BufferBloque de bytes sin interpretar; es lo que devuelve readFile si no indicas la codificación.
ENOENTCódigo de error que significa «el archivo o carpeta no existe».
process.argvArray con los argumentos con los que se lanzó el script.

🧠 Comprueba lo aprendido

Elige una respuesta en cada pregunta. Verás al instante si acertaste y por qué.

¿Qué sintaxis pertenece a ES Modules?

¿Cómo activas ES Modules en un proyecto?

En ESM, ¿cuál es la forma correcta de importar un archivo local?

¿Qué devuelve await readFile("a.txt", "utf8")?

Ejecutas node app.js uno dos. ¿Qué es process.argv.slice(2)?

Tu progreso se guarda en este navegador y se refleja en el índice del curso.

💡 🚀 ¡Ahora te toca!

La práctica fija lo aprendido. Pasa al ejercicio de esta lección.

Ir al ejercicio