Skip to content

FileSystem

En esta práctica vamos a trabajar con el módulo fs de Node.js. Este módulo nos permite trabajar con el sistema de archivos del sistema operativo.

Queda perfecto para aprender algunos conceptos claves como:

  • Configurar Typescript con Node.js
  • Módulo nativos de Node.js
  • Rutas relativas y absolutas
  • Creación y lectura de archivos
  • CRUD: Simular una base de datos con archivos JSON

Configurar Typescript con Node.js

En el artículo anterior vimos cómo configurar Typescript en un proyecto de Node.js. Si no lo has hecho, te recomiendo que le eches un vistazo. Revisa el artículo Configurar Typescript en Node.js.

Directorios

sh
.
├── node_modules
├── data
   └── books.json
├── src
   └── index.ts
├── package.json
└── tsconfig.json
└── .gitignore

Módulo nativos de Node.js

Node.js es un entorno de ejecución de JavaScript que nos permite ejecutar código JavaScript en el servidor. Además, nos provee de un conjunto de módulos nativos que nos permiten interactuar con el sistema operativo, el sistema de archivos, el sistema de red, etc.

En este artículo vamos a trabajar con el módulo fs que nos permite trabajar con el sistema de archivos del sistema operativo.

node: protocol

Los módulos integrados de Node.js pueden tener como prefijo node:protocol para que sean explícitos:

ts
import fs from "node:fs";

La ventaja de esto es que podemos distinguir entre módulos nativos y módulos de terceros.

TIP

Esto es opcional, pero es una buena práctica. Pero funciona para versiones de Node.js 12 en adelante.

Rutas

Cuando trabajas con rutas de archivos en Node.js, puedes usar rutas relativas y absolutas para ubicar el archivo que deseas manipular.

Ejemplo fs.readFile:

ts
import fs from "node:fs/promises";

try {
  const data = await fs.readFile("/Users/joe/test.txt", { encoding: "utf8" });
  console.log(data);
} catch (err) {
  console.log(err);
}

En Node.js, la diferencia entre "/data" y "data" es significativa en términos de cómo se interpretan las rutas:

  1. "/data":
    • En Node.js, una ruta que comienza con una barra / se interpreta como una ruta absoluta en el sistema de archivos.
    • Esto significa que "data" se buscará directamente en la raíz del sistema de archivos, como /data en sistemas Unix/Linux o C:\data en Windows. Si el archivo no existe en esa ubicación exacta, Node.js no lo encontrará y generará un error.
  2. "data":
    • Una ruta que no comienza con / (por ejemplo, "data" o "../data" o ./) se interpreta como una ruta relativa al directorio de trabajo actual (es decir, process.cwd()).
    • "data" significa que buscará en un subdirectorio data dentro del directorio actual desde el que ejecutas el comando node.

__dirname + path.join

Para evitar problemas con las rutas, puedes usar __dirname y path.join para construir rutas de archivos de forma segura.

path.join es una función que concatena las rutas de forma segura, independientemente del sistema operativo.

import.meta.dirname es una variable global que te da la ruta absoluta del directorio actual del archivo en el que se encuentra.

Creación y lectura de archivos

Leer un archivo con fs.readFile:

ts
import fs from "node:fs/promises";
import path from "node:path";

// console.log(process.cwd());
const __dirname = import.meta.dirname;
const filePath = path.resolve(__dirname, "../data/books.json");

const readFile = async () => {
  try {
    const data = await fs.readFile(filePath, "utf-8");
    console.log(data);
  } catch (error) {
    console.log(error);
  }
};

readFile();

CRUD: Simular una base de datos con archivos JSON

ts
import fs from "node:fs/promises";
import path from "node:path";

interface Book {
  id: string;
  title: string;
  price: number;
}

const __dirname = import.meta.dirname;
const filePath = path.resolve(__dirname, "../data/books.json");

const readFile = async () => {
  const data = await fs.readFile(filePath, "utf-8");
  return JSON.parse(data);
};

const writeFile = async (books: Book[]) => {
  await fs.writeFile(filePath, JSON.stringify(books, null, 2));
};

const getBooks = async () => {
  try {
    const books = await readFile();
    console.log(books);
  } catch (error) {
    console.log(error);
  }
};

const addBook = async (book: Book) => {
  try {
    const books = await readFile();
    books.push(book);
    await writeFile(books);
    console.log("Book added successfully");
  } catch (error) {
    console.log(error);
  }
};

const deleteBook = async (id: string) => {
  try {
    const books = await readFile();
    const newBooks = books.filter((book: Book) => book.id !== id);
    await writeFile(newBooks);
    console.log("Book deleted successfully");
  } catch (error) {
    console.log(error);
  }
};

const updateBook = async (updateBook: Book) => {
  try {
    const books = await readFile();
    const book = books.find((book: Book) => book.id === updateBook.id);

    if (!book) {
      console.log("Book not found");
      return;
    }

    const index = books.indexOf(book);
    books[index] = { ...book, ...updateBook };
    await writeFile(books);

    console.log("Book updated successfully");
  } catch (error) {
    console.log(error);
  }
};

// await addBook({ id: nanoid(), title: "Book 2", price: 30 });
// await updateBook({
//   id: "9dB57-ZX8X0kDkgVLH3gL",
//   title: "Book 4 Updated",
//   price: 40,
// });
// await deleteBook("9dB57-ZX8X0kDkgVLH3gL");
await getBooks();

En las próximas lecciones complementaremos este ejercicio con express.