Introducción
En la entrada de hoy, vamos a programar un CRUD básico con javascript.
Será independiente del sistema de persistencia que utilicemos, aunque en este caso vamos a utilizar las variables de sesión de javascript (sessionStorage).
Existen métodos más óptimos según el sistema de persistencia que tengamos y el número de datos a trabajar. Pero como guía para que podáis construir vuestro propio CRUD os puede valer.
Si os estáis preguntando que es CRUD, sus siglas corresponden a las 4 operaciones básicas que se hacen al trabajar con sistema de persistencia Create, Read, Update y Delete, o sea, crear, leer, actualizar y borrar.
Sin más demora vamos primero a crear nuestro CRUD. Para hacerlo más fácil y no tener que instalar node.js, vamos a correr nuestro CRUD en el navegador usando HTML.
Primero creamos nuestro index.html
<!DOCTYPE html> <html> <head> <title>CRUD JD</title> </head> <body> <script src="./js/index.js" type="module"></script> </body> </html>
Recuerda que es una buena práctica añadir el script de js antes de finalizar el body, para que así ya esté cargado todo el DOM. Como vamos a utilizar módulos no olvides añadir el type=»module» al script, sino, nos sale un error como este en el inspector de nuestro navegador:
Ahora procedemos a crear nuestro index.js, que generalmente tendremos dentro de la carpeta /js
function app(){ debugger } app();
Creamos la funcion app() que será la función que dará paso a toda la ejecución de javascript, y usamos debugger, para comprobar que todo esté correctamente enlazado. Si todo esta correcto, al correr todo en el navegador se activará el modo debugger
Perfecto!
Ahora vamos a crear nuestro archivo CRUD.js que contendrá nuestra clase CRUD, la cual manejará el sistema de persistencia.
export class CRUD{}
Usamos export delante de la declaración de la clase para poder importarla como módulo en index.js
import { CRUD } from "./CRUD.js"; function app(){ debugger } app();
Ahora lo ideal es que cada vez que instanciemos la clase CRUD, le digamos el nombre de la tabla con la que queremos trabajar, por tanto ese argumento se lo pasamos por constructor a la clase CRUD, el cual será asignado a un atributo privado para poder acceder a él desde cualquier método interno de la clase CRUD, pero no desde fuera de la clase.
export class CRUD{ #tableName = null; constructor(tableName){ this.tableName = tableName; } }
Por cuestiones de integridad, deberíamos obligar a que cada vez que se instancie la clase CRUD, se tenga que pasar el nombre de la tabla obligatoriamente, para así no tener instanciaciones inconsistentes dentro de nuestra aplicación. Vamos a añadir una validación que lance un error en caso de no pasar ningún argumento. Cabe destacar que podríamos añadir más condiciones de validación, así como una manera de capturar ese error, pero no es objetivo del curso, simplemente queremos detener el flujo de nuestra aplicación en caso de que alguien instancie la clase CRUD sin el nombre de la tabla.
export class CRUD{ #tableName = null; constructor(tableName){ this.#setTableName(tableName); } #setTableName(tableName){ this.#tableNameValidate(tableName); this.#tableName = tableName; } #tableNameValidate(tableName){ if(tableName == undefined) throw new Error("Table name required"); } }
Como podemos ver hemos creado el método privado setTableName(), cuya función como dice su nombre es la de asignar un valor al atributo privado tableName de la clase CRUD. Para separar responsabilidades (Principio de Responsabilidad Única), separamos la asignación de la validación, así en el momento que queramos modificar los criterios de validación solo tenemos que tocar el método cuya responsabilidad es la de validar tableNameValidate().
Ahora vamos a crear nuestros cuatro métodos CRUD.
export class CRUD{ #tableName = null; constructor(tableName){ this.#setTableName(tableName); } #setTableName(tableName){ this.#tableNameValidate(tableName); this.#tableName = tableName; } #tableNameValidate(tableName){ if(tableName == undefined) throw new Error("Table name required"); } create(data){} read(id){} update(id, data){} delete(id){} }
Como podemos ver, al método create() le pasamos como argumento la información que queremos guardar. Recuerda que cada información que guardamos debemos asignarle un id, en ese caso hemos decidido que ese id lo genere nuestra propia clase CRUD, en otros casos, y es muy buena práctica generar el id desde fuera de la clase CRUD, a través de algún sistema generador de id como uuid (pero eso da para otra entrada).
El método read() nos revolverá la información con el id que le pasemos por argumento. Update() nos actualiza la información que tenga el id que recibe por argumento con la data que también recibe por argumento y delete(), simplemente borrará la información que tenga el id que recibe por argumento.
Para obtener toda la información de la tabla podríamos utilizar el método read(), evaluando que no se le pase argumento, y así devolver todos los registro de la tabla, pero preferimos respectar el Principio de responsabilidad única y el Principio Abierto Cerrado, así que crearemos un 5 método que nos devuelva todos los registros readAll().
Para ir almacenando todos los registros de la tabla, vamos a crear otro atributo privado llamado data con el cual interactuarán nuestros 5 métodos públicos.
export class CRUD{ #tableName = null; #data = null; constructor(tableName){ this.#setTableName(tableName); this.#setData(); } #setTableName(tableName){ this.#tableNameValidate(tableName); this.#tableName = tableName; } #setData(){ this.#data = []; } #tableNameValidate(tableName){ if(tableName == undefined) throw new Error("Table name required"); } create(data){} read(id){} update(id, data){} delete(id){} }
Como podemos ver hemos declarado el atributo privado data en la línea 3, y luego le asignamos un valor durante la instanciación de la clase en la línea 7 usando el método privado setData(). Por ahora ese método solo asigna un array en blanco, aunque más adelante hará más cosas.
BIEN!!! Ahora que ya tenemos la variable con la que va a interactuar nuestro CRUD, vamos a crear la lógica de cada uno de nuestros métodos.
Con el método create() simplemente tenemos que añadir la variable data que recibe por argumento al atributo privado data, por tanto podemos hacerlo con el método push() el cual añade la información al final del array y le asigna un id incremental. Como nuestro método es el que asigna un id al registro insertado, es buena práctica que devuelva dicho id, por si quien ha llamado a nuestro método create() lo necesita para seguir con su ejecución.
El método read() nos tiene que devolver el elemento del atributo privado data que tenga el id que ha recibido por parámetro.
El método readAll() nos tiene que devolver directamente el atributo privado data.
El método update() simplemente tiene que asignarle el valor data que recibe por parámetro al elemento del atributo privado data. Es buena práctica que si el proceso de actualización es correcto este método devuelva un valor booleano true.
Por último el método delete() elimina el registro del atributo privado data que tenga el id que recibe por parámetro. Al igual que update(), es buena práctica que si el proceso de borrado ha sido exitoso, este método nos devuelva un valor booleano true.
VAMOS AVANZANDO!!!!
Ahora seguramente te estarás preguntado. Hasta aquí todo muy bien, pero NO HEMOS GUARDADO LOS DATOS TODAVÍA EN NINGÍN SISTEMA DE PERSISTENCIA.
Efectivamente, y en eso radica todo esto, en que toda la lógica del CRUD que estamos haciendo es independiente del sistema de persistencia que vayamos a usar, ya sean variables de sesion, una SQL, una no SQL, una api externa, un archivo .json o un .txt. NO GUARDÉIS INFORMACIÓN EN UN .json NI EN UN .txt.
Trabajar así generalmente es bueno, aunque depende de cada caso, hay que tener en cuenta la escalabilidad y el rendimiento.
¿cómo añadimos el sistema de persistencia?
Una rápida solución es crear dos método, uno que guarde en el sistema de persistencia usando una clave y un valor, donde la clave sea el nombre de la tabla(tableName) y el valor sería todo el contenido de el método privado data, lo llamaremos save(), y otro método que obtenga el valor(data) dada una clave (tableName) lo llamaremos get().
El proceso es el siguiente, una vez instanciada la clase CRUD, obtenemos la data del sistema de persistencia y la guardamos en el atributo privado data (en el caso que no tenga información asignamos un array vacío), y después de hacer cualquier cambio en el atributo privado data, guardamos el atributo privado data en el sistema de persistencia.
Como podemos ver hemos modificado setData(), donde obtenemos la data del sistema de persistencia también llamado repositorio, si nos retorna un null, es que no existe la tabla y por tanto inicializamos un array vacio.
También podemos observar que despues de los métodos create(), update() y delete() que son los que modifican el atributo privado data, siempre llamamos a save() para que actualice la información en el sistema de persistencia.
Hasta aquí tendriamos lo que es un CRUD básico, aunque nos dejamos muuuuchas cosas en el tintero para optimizar mejor este CRUD, si que podemos añadirle algunas restricciones, por ejemplo, ¿que pasa si intentamos leer, actualizar o eliminar un elemento con un id que no existe?
Vamos a añadir un método que devuelva true si existe un elemento con un id determinado y false en caso contrario.
Y añadimos esta validación siempre que usemos un elemento con id.
Como vemos hemos encapsulado la validación en una funcion checkThatElementExistWithId() que verifica que existe un elemento con el id pasado por el argumento, lanzando una excepción en caso incorrecto.
Nuestra clase CRUD quedaría así:
Nuestro index.js quedaría así añadiendo una implementación de CRUD:
Y tendríamos este resultado en la consola del navegador:
Bueno, nos dejamos muchas cosas pero como ejercicio creo que cumple si objetivo.
Hemos desarrollado un CRUD, donde nos acoplamos lo mínimo posible al sistema de persistencia (repositorio), desarrollando toda la lógica principal del CRUD nosotros mismo y de esta manera tener el control. Esto nos permite cambiar el sistema de persistencia del CRUD cambiando como mucho 2 líneas de código.
Como siempre, no estoy diciendo que sea el método más efectivo, ya que generalmente los sistemas de persistencia ya traen CRUD quizá más optimizados, sacrificando que si en algún momento tenemos que cambiar el sistema de persistencia tendríamos que modificar muchas más líneas de código O PUEDE QUE NO NECESARIAMENTE JEJE (pero eso lo veremos en otra entrada).
Quizás te interese esta otra entrada sobre como hacer un Crud en PHP