Original article: How to Build a CRUD Command Line Application With Node.js

Si estás buscando un tutorial que te enseñe lo básico de Nodejs y la línea de comando, estás en el lugar correcto.

Puedes usar JavaScript para construir casi cualquier software (web, móvil, bots, etcétera). La razón es que las computadoras ya no dependen en los buscadores solamente para entender código de JavaScript.

Node.js es usado para ejecutar aplicaciones backend que son escritos en JavaScript.

Te enseñare cómo construir una aplicación de línea de comando que puede leer, escribir, editar y eliminar datos usando Node.js. No requerirá conectar a bases de datos externos como MySQL, MongoDB, PostgreSQL, etcétera. Puedes ver el proyecto aquí.

Al final de este artículo, serías capaz de configurar un proyecto básico de Node, manipular archivos, usar módulos, navegar promesas, recoger entradas, etcétera.

También agregué videos para mejorar tu aprendizaje.

Prerequisitos

No necesitas ningún conocimiento de programación previo para entender este tutorial.

Cómo funcionará el Proyecto Finalizado

La aplicación que vamos a construir será capaz de hacer lo siguiente:

  • Chequear si existe una base de datos
  • Recuperar datos de una base de datos
  • Agregar nuevos datos a la base de datos
  • Actualizar la base de datos con nuevos datos
  • Remover datos de la base de datos

Cómo Instalar Node y NPM

Por favor ve a la página de Nodejs y descarga la versión recomendada para todos los usuarios. Instálalo después de que la descarga esté completa.

Usa los comandos debajo para chequear si la instalación fue exitosa:

  • Para Node
node -v
  • Para npm
npm -v

La instalación es exitosa si cada comando regresa un número de versión.

Cómo Configurar un Proyecto de Node.js

Los pasos de abajo te ayudarán a configurar tu proyecto:

Abre la terminal o CMD y crea el directorio del proyecto:

mkdir node_CLI_app

Navega hacia el directorio:

cd node_CLI_app

Inicializa el proyecto:

npm init	

Traerá algunas preguntas. Presionar el botón Enter para cada indicación.

Un nuevo archivo (package.json) debería haber sido agregado en el directorio de tu proyecto. Usaremos el archivo para agregar código externo (módulo) al proyecto.

Agregar la siguiente línea antes de la llave (}) para habilitar import de ES6:

"type": "module"

¡Eso completa la configuración del proyecto!

Cómo Instalar Dependencias

Recordarás que el archivo package.json es para agregar código externo al proyecto. Este código externo también es llamado Dependencias, Módulos, o Paquetes. Está escrito por otros programadores (usualmente gratis) para ayudar a otros construir aplicaciones más rápido. Encontrarás muchos de estos en la página de npm.

Necesitamos dos (2) paquetes para este proyecto: inquirer y uuid. Te mostraré cómo puedes instalarlos en esta sección.

La instalación de paquetes sigue este patrón:

npm install <nombre_de_paquete>

Podemos instalar más de un paquete a la vez separando los nombres de los paquetes con un espacio:

npm install <nombre_de_paquete_1> <nombre_de_paquete_2> <nombre_de_paquete_3>

El comando install puede ser reemplazado con i para conveniencia.

Así que ejecuta el siguiente comando para instalar los paquetes:

npm i inquirer uuid

Tu archivo package.json debería tener las siguientes líneas de código agregadAs cuando la instalación esté completa:


  "dependencies": {
    "inquirer": "^9.1.4",
    "uuid": "^9.0.0"
  }

Los números de versión podrían diferir, pero no hay problema.

También notarás que un archivo (package.lock.json) y una carpeta (node_modules) han sido agregados. No tienes que preocuparte por ellos. Sólo ten en mente que te ayudan a gestionar código externo que agregamos.

¡Eso completa la instalación de dependencias!

Cómo crear un Nuevo Archivo desde la Terminal

Puedes crear un archivo desde la terminal usando el siguiente comando:

  • En Mac:
touch <nombre_archivo>
  • En Windows:
echo.><nombre_archivo>

También puedes crear mas de un archivo a la vez separando los archivos con un espacio. Así es como generaremos los archivos para este proyecto:

touch agregarDatos.js quitarDatos.js recuperarDatos.js actualizarDatos.js consultarBD.js bdArchivoChequeo.js

Cada uno de esos archivos jugarán un rol único en la aplicación.

  • agregarDatos.js agrega datos a la Base de Datos. También fabrica el archivo de la Base de Datos si éste no existe.
  • quitarDatos.js quita datos seleccionados.
  • recuperarDatos.js busca todos los datos.
  • actualizarDatos.js edita los datos.
  • consultarBD.js chequea si la Base de Datos existe y ejecuta una función que se le pasa.
  • bdArchivoChequeo.js confirma si el archivo de la Base de Datos ha sido creado.

Hay un archivo más que no creamos: el archivo de la base de datos (bd.json). Lo auto-generaremos usando nuestro código.

Cómo Comprobamos si un Archivo existe

Node.js provee un módulo integrado para manipular archivos – file system (fs). Uno de los métodos que el módulo contiene es el método existsSync. Regresa true o false dependiendo en si el archivo ha sido creado. Lo usaré para verificar si el archivo bd.json está en el proyecto. Vea el código de abajo:

  import fs from "fs";
  import { exit } from "process";  
  
  if (fs.existsSync("bd.json")) {
    console.log("El archivo existe!");
    exit(1);
  }

El método exit termina un proceso.

Así es como queremos hacer en el archivo bdArchivoChequeo.js. Sin embargo, queremos invertir el resultado agregando un ! antes del método fs.existsSync así:

  import fs from "fs";
  import { exit } from "process";  
  
  if (!fs.existsSync("bd.json")) {
    console.log("El archivo no existe!");
    exit(1);
  }

Esto será necesario cuando se construye otras funcionalidades como actualizar y eliminar datos.

Así que, ¿cómo ganarán acceso esos archivos a este código? Es a través de una estructura modular. Eso implica empaquetar este código y hacerlo accesible a través de otros archivos en el proyecto. El comando export lo hace posible:

import fs from "fs";
import { exit } from "process";

export default function dbFileCheck() {
  if (!fs.existsSync("bd.json")) {
    console.log("La Base de Datos está vacía. ¡Crea algún dato!");
    exit(1);
  }
}

El código de arriba pone el código en una función (bdArchivoChequeo) y lo exporta usando el comando export. Esta función ahora puede ser importado y usado en otros archivos dentro de este proyecto.

Ten en cuenta que la palabra clave default es necesario para el primer export de un archivo.

Cómo Consultar la Base de Datos

Otro método que file system tiene es el método readFile. Regresa el contenido de cualquier archivo que se le pase.

El método readFile toma dos parámetros: el archivo a ser leído y una función callback que regresa el resultado de la operación.

Usaremos el siguiente código para recuperar datos desde nuestra base de datos:

import fs from "fs";

fs.readFile("bd.json", function (err, datos) {
  if (err) {
    console.log(err);
  }
  console.log(datos.toString());
});

El código de arriba importa el módulo file system e intenta leer el archivo de la base de datos. Si hay un error, regresará el error. Si no hay errores, regresará los datos que obtuvo.

El método .toString() adjuntado a los datos regresados (datos.toString()) es porque los datos recuperados es del tipo buffer por defecto, lo cual no es legible.

s_D592C23061BDAFBA9E611AEDC8048F685A5679FCF2C57746CD5AE80A3DAD15B0_1675074450995_Screenshot+2023-01-30+at+11.25.25
salida de buffer

Abre tu proyecto en un editor de código y pega ese código dentro del archivo consultarBD.js.

Ejecuta el comando de abajo para ver si está funcionando:

node consultarBD

El comando de arriba ejecutará cada código en el consultarBD.js. La extensión .js es opcional. Lo regresará de todas maneras.

El resultado de esa operación será un error porque no tenemos el archivo.

s_D592C23061BDAFBA9E611AEDC8048F685A5679FCF2C57746CD5AE80A3DAD15B0_1675074558543_Screenshot+2023-01-30+at+11.28.47

Puedes crear el archivo bd.json, agregar algún contenido, y verificar la salida.

Pero no queremos que nuestra app intente leer el archivo de la base de datos si este no ha sido generado. Así que utiliza el método existsSync para verificar si el archivo ha sido creado. Mira cómo lo uso en el código debajo:

    import fs from "fs";

    if (fs.existsSync("bd.json")) {
      fs.readFile("bd.json", function (err, datos) {
        if (err) {
          console.log(err);
        }
        console.log(datos.toString());
      });
    } else {
      console.log("¡Datos no disponibles!");
    }

El código de arriba verífica si el archivo existe pasando el nombre del archivo (bd.json) al método fs.existsSync. Si es verdadero, entonces lee el archivo. Si es falso, regresa una cadena.

Ahora que tenemos ese código limpio, queremos hacerlo aún más robusto.

Siendo que estamos construyendo una aplicación CRUD, debemos tener acceso y hacer un seguimiento de los datos regresados. Para lograr eso, he introducido una nueva variable en el código de abajo:

    import fs from "fs";

    let info = [];
    if (fs.existsSync("bd.json")) {
      fs.readFile("bd.json", function (err, datos) {
        if (err) {
          console.log(err);
        }
        info = JSON.parse(datos.toString());
        console.log(info);
      });
    } else {
      console.log("¡Datos no disponibles!");
    }

El código de arriba ahora tiene una variable, info. Hace un seguimiento de los datos regresados. Pasé los datos a través del método JSON.parse para convertir los datos de una cadena a un arreglo.

Ahora podemos manipular el info según lo consideremos. Tenemos que exportar el código y aceptar una función para hacer esto posible. Esa función tomará la variable info como entrada y luego usarlo cuando sea requerido.

import fs from "fs";

export default async function consultarBD(funcionExterna) {
  try {
    let info = [];

    if (fs.existsSync("bd.json")) {
      await fs.readFile("bd.json", function (err, datos) {
        info = JSON.parse(datos.toString());
        console.log(info);

        if (err) {
          console.log(err);
          return;
        }

        if (funcionExterna && !err) {
          funcionExterna(info);
          return;
        }
      });
    } else {
      if (funcionExterna) {
        funcionExterna(info);
        return;
      }
    }
  } catch (error) {
    console.error(`Algo ocurrió: ${error.message}`);
  }
}

Este código toma una función y lo ejecuta solamente si no hay errores. Sin embargo, habrá una excepción – cuando se agreguen datos. En ese caso, la base de datos será creado.

El bloque try...catch... ayuda a recoger errores, y las palabras claves async await son usados cuando se ejecuta código que tomará mucho tiempo en correr.

El consultarBD.js ya no regresará ninguna salida cuando ejecutemos el comando node consultarBD. Pero está bien. Lo dispararemos desde otros archivos.

Cómo Agregar Datos a un Archivo

En esta sección, te enseñaré cómo agregar datos al almacén. El archivo para esta sección es el archivo agregarDatos.js.

Empezaremos por importar todos los módulos necesarios:

import inquirer from "inquirer";
import fs from "fs";
import { v4 as uuidv4 } from "uuid";
import consultarBD from "./consultarBD.js";

Siguiente, crea el boilerplate para la función:

export default async function agregarDatos(info) {
  try {
    
  } catch (error) {
    console.log("¡Algo salió mal!", error);
  }
}

Esto es similar a lo que hemos hecho antes de ahora. Todo el código que escribiríamos luego irá dentro del bloque try.... El arreglo info pasado a la función viene del archivo consultarBD.

La primera cosa por hacer en el bloque try... es recolectar los datos que serán almacenados. Usaremos inquirer para hacer eso con el código de abajo:

const respuestas = await inquirer.prompt([
      {
        type: "input",
        name: "nombre",
        message: "¿Cómo te llamas?",
      },
      {
        type: "number",
        name: "telefono",
        message: "¿Cuál es tu teléfono?",
      },
      {
        type: "list",
        name: "edad",
        message: "¿Eres un adulto?",
        choices: [
          { name: "S", value: "Adulto" },
          { name: "N", value: "Menor" },
        ],
      },
    ]);

El paquete inquirer ayuda en la construcción de interfaces de línea de comandos interactivos. Contiene un método llamado prompt usado para pedir datos. Toma un arreglo de preguntas en formato de objeto. Cada objeto debe tener las claves name, type y message.

La clave choices es opcional. Es usado cuando hay una lista de opciones.

Así que el código de arriba recolecta tres (3) entradas (nombre, teléfono, y edad) y los almacena en una variable llamada respuestas.

Luego, asignamos a este conjunto de entradas un ID único llamando la función uuidv4() y empujar todo dentro del arreglo info:

    const datos = {
      id: uuidv4(),
      nombre: answers.nombre,
      telefono: answers.telefono,
      edad: answers.edad,
    };
    info.push(datos);

Finalmente, verificamos si el archivo de la base de datos existe. Actualizaremos la base de datos con los nuevos datos si el archivo ha sido creado o crearlo y agregar los nuevos datos si es falso.

    if (fs.existsSync("bd.json")) {
      crearDetalles(info);
    } else {
      fs.appendFile("bd.json", "[]", (err) => {
        if (err) {
          console.log("No se pudo crear bd.json", err);
          return;
        }
        crearDetalles(info);
      });
    }

La función crearDetallesserá usado para sobreescribir los datos de la base de datos. Lo crearé en un momento.

En el bloque else (si el archivo no existe), creé el archivo usando el método appendFile del sistema. Este método es usado para crear un archivo si no está allí todavía y agregar o adjuntar datos al final del archivo.

Estoy adjuntando [] al archivo. Así que el nuevo archivo creado tendrá sólo [] dentro. Llamé a la función crearDetalles luego para agregar los datos coleccionados desde el CLI.

Por favor escribe el siguiente código para la función crearDetalles la función de agregarDatos abajo:

async function crearDetalles(info) {
  await fs.writeFile("bd.json", JSON.stringify(info), function (err) {
    if (err) {
      console.log(err);
    }
    console.log("¡Guardado!");
  });
}

Esta función usa el método writeFile del sistema de archivos para sobreescribir la base de datos con datos actuales. Estoy usando JSON.stringify para convertir la variable info a una cadena porque es el formato aceptable cuando se escribe a un archivo. Eso también explica por qué usé JSON.parse para convertirlo al tipo original cuando lo recuperé en la sección previa.

writeFile es diferente desde appendFile porque writeFile sobreescribe un archivo mientras que appendFile agrega al contenido existente. Son similares en tanto que ambos métodos creará el archivo si este no ha sido creado.

Lo último para hacer en este archivo es invocar o llamar a la función. La manera de hacerlo es escribir su nombre seguido de abrir y cerrar paréntesis de esta manera:

agregarDatos();

Esto podría funcionar bien, pero no haría lo que queremos. Necesitamos pasar la función como un argumento dentro de consultarBD de esta manera:

consultarBD(agregarDatos);

Ahora, el agregarDatos será capaz de comunicarse con la función consultarBD.

Cómo Testear el Archivo agregarDatos

Ejecuta el comando de abajo para testear si el archivo agregarDatos funciona como se espera:

node agregarDatos

Te mostrará mensajes para responder. Completa las respuestas. Después de eso, tu pantalla debería parecerse como el mío:

s_D592C23061BDAFBA9E611AEDC8048F685A5679FCF2C57746CD5AE80A3DAD15B0_1675241012883_Screenshot+2023-02-01+at+09.42.57
salida de agregarDatos

También debería haber auto-generado un archivo db.json con los datos que agregaste.

Y eso es todo para el archivo agregarDatos!

Cómo Recuperar Datos

Esta sección te muestra cómo obtener el contenido de la base de datos. Todo lo que necesitas para lograr esta funcionalidad es el código de abajo:

import consultarBD from "./consultaBD.js";

consultarBD();

Lo que el código hace es importar la función consultarBD e invocarla. Pega el código en el archivo recuperarDatos.js y ejecuta el archivo con:

node recuperarDatos

Regresará una salida similar a esto:

s_D592C23061BDAFBA9E611AEDC8048F685A5679FCF2C57746CD5AE80A3DAD15B0_1675319554801_Screenshot+2023-02-02+at+07.29.43
salida de recuperarDatos.js

Te preguntarás: ¿por qué no llamé esta función en el archivo consultarBD? La razón es que el archivo realiza más que sólo recuperar datos. Llamar la función consultarBD en su archivo alterará el resultado de los otros archivos.

Cómo Editar Datos

Esta sección ahora se enfocará en cómo actualizar datos. El patrón a seguir aquí es:

  • Recoger el ID del usuario.
  • Buscar al usuario.
  • Regresar los datos del usuario si el usuario existe, y establecerlo como la opción por defecto.
  • Pedir por información actualizada. El valor inicial será guardada si el usuario presiona la tecla Enter.
  • Finalmente, sobrescribir la base de datos con la información actualizada.

Hagámoslo...

Navega dentro del archivo actualizarDatos.js y pega el siguiente código:

import inquirer from "inquirer";
import fs from "fs";
import consultarBD from "./consultarBD.js";
import bdArchivoChequeo from "./bdArchivoChequeo.js";

export default async function actualizarDatos(info) {
  bdArchivoChequeo();

  try {
    
  } catch (error) {
    console.log("Algo salió mal!", error);
  }
}

Este es el código para la función actualizarDatos. Lo nuevo aquí es la función bdArchivoChequeo (hecho hace unos momentos atrás) para terminar una operación si la base de datos no ha sido creada.

El resto del código estará dentro del bloque try....

Lo primero es recoger el ID del usuario con este código:

    const respuestas = await inquirer.prompt([
      {
        type: "input",
        name: "registroID",
        message: "Ingresa Registro ID",
      },
    ]);

El segundo es buscar al usuario:

    let actual;

    info.forEach((elemento) => {
      if (elemento.id === respuestas.registroID) {
        actual = elemento;

        actualizarDetalles(actual, info);
      }
    });

El código de arriba busca a través de los usuarios (info) y coloca al usuario hallado en la variable actual. Finalmente, la función actualizarDetalles es llamado para recoger la información actualizada y sobrescribir la base de datos con los nuevos datos.

Cómo construir la función actualizarDetalles

Esta parte mostrará cómo hacer un seguimiento de los datos iniciales de un usuario mientras se recogen nuevos datos. Actualizará la base de datos con los nuevos datos después.

El siguiente código es la estructura para la función:

async function actualizarDetalles(actual, info) {
  try {
    
  } catch (error) {
    console.log("¡Algo salió mal!", error);
  }
}

Este código va debajo de la operación actualizarDatos.

El código de abajo va dentro del bloque try.... Es para recoger información actualizada del usuario.

  const devoluciones = await inquirer.prompt([
      {
        type: "input",
        default: actual.nombre,
        name: "nombre",
        message: "¿Cuál es tu nombre?",
      },
      {
        type: "number",
        default: actual.telefono,
        name: "telefono",
        message: "¿Cuál es tu teléfono?",
      },
      {
        type: "list",
        default: actual.edad,
        name: "edad",
        message: "¿Eres un adulto?",
        choices: [
          { name: "S", value: "Adulto" },
          { name: "N", value: "Menor" },
        ],
      },
    ]);

La clave default es nuevo. Retiene dato que será usado si el usuario no provee una. Hace un seguimiento de los datos actuales del usuario para este código. Así, el usuario puede presionar el botón Enter en vez de ingresar el valor anterior otra vez.

Los detalles del usuario debería ser actualizado respectivamente usando este código:

    actual.nombre = devoluciones.nombre;
    actual.telefono = devoluciones.telefono;
    actual.edad = devoluciones.edad;

Finalmente, sobrescribir la base de datos con el nuevo valor:

await fs.writeFile("bd.json", JSON.stringify(info), function (err) {
      if (err) {
        console.log(err);
      }
      console.log("actualizado");
    });

Llama la función actualizarDatos para traer todo junto:

consultarBD(acutalizarDatos)

Cómo testear el archivo actualizarDatos

Ejecuta el comando debajo para ver cómo actúa la función actualizarDatos:

node actualizarDatos

Te mostrará un mensaje pidiendo un ID. Ingresa el ID de cualquier registro en la base de datos.

Luego te mostrará un mensaje por información actualizada sobre el usuario. Rellena la información y presiona Enter por cualquier detalle que no necesite una actualización. La terminal debería lucir así al final:

s_D592C23061BDAFBA9E611AEDC8048F685A5679FCF2C57746CD5AE80A3DAD15B0_1675324018105_Screenshot+2023-02-02+at+08.45.07
salida de actualizarDatos

Eso concluye para el archivo actualizarDatos. Los registros pueden ser actualizados ahora.

Cómo Eliminar Datos

Una aplicación CRUD no puede ser completado sin la parte de DELETE (ELIMINAR). Ese será el enfoque aquí. Tomará un poco de las secciones anteriores. Así no será mucho para captar.

Empezarás por escribir el siguiente código en el archivo removerDatos.js:

import inquirer from "inquirer";
import fs from "fs";
import consultarBD from "./consultarBD.js";
import bdArchivoChequeo from "./bdArchivoChequeo.js";

export default async function removerDatos(info) {
  bdArchivoChequeo();

  try {
    const respuestas = await inquirer.prompt([
      {
        type: "input",
        name: "registroID",
        message: "Ingresa el ID del Registro",
      },
    ]);

    let datosRestantes = [];
    info.forEach((elemento) => {
      if (elemento.id !== respuestas.registroID) {
        datosRestantes.push(elemento);
      }
    });

    fs.writeFile("bd.json", JSON.stringify(datosRestantes), function (err) {
      if (err) {
        console.log(err);
      }
      console.log("¡Eliminado!");
    });
  } catch (error) {
    console.log("¡Algo salió mal!", error);
  }
}

Primero, el código de arriba recoge un ID.

Luego, usa el ID para buscar los datos de la base de datos y retiene solamente los datos con un ID distinto en el arreglo datosRestantes.

Finalmente, sobrescribe la base de datos con los datos actualizados.

Llama la función removerDatos al final para poner todo junto de esta manera:

consultarBD(removerDatos)

Ahora intenta testear el archivo usando este comando:

node removerDatos

Esta es mi salida:

s_D592C23061BDAFBA9E611AEDC8048F685A5679FCF2C57746CD5AE80A3DAD15B0_1675329363353_Screenshot+2023-02-02+at+10.15.12
salida de removerDatos

Conclusión

La mejor manera para aprender a programar es haciendo. Y algunos de los mejores proyectos es construir aplicaciones CRUD porque cubren lo básico de lo que un proyecto profesional requiere. Esto es lo que he hecho en este tutorial.

Te he enseñado lo básico de Node.js construyendo una aplicación que crea, lee, actualiza, y elimina registros. Cubrí conceptos tales como leer archivos, escribir a archivos, bucles, sentencias condicionales, módulos y operaciones del CLI.

Todos los archivos para este tutorial están en GitHub.

Ahora tienes todo lo básico necesario para empezar a construir aplicaciones usando Node.js. Algo que me gustaría que intentaras es actualizar o eliminar más de un registro a la vez.

Tengo otros tutoriales en Node.js, y te sugiero que los sigas en este orden para hacer crecer tus habilidades:

  • Cómo Construir un Servidor Seguro con Node.js y Express y Subir Imágenes con Cloudinary
  • Cómo Construir y Desplegar una App Backend con Express, Postgres, Github, y Heroku
  • Cómo Construir una App de Autenticación Full-Stack con React, Express, MongoDB, Heroku y Netlify

¡Feliz día construyendo!