Usar un ORM (Object-Relational Mapping) para gestionar un CRUD (Crear, Leer, Actualizar y Eliminar) transforma la manera de manipular bases de datos. El uso de un ORM facilita las operaciones sin tener que escribir SQL directamente, acercándonos mucho más a la programación orientada a objetos. Aquí te explicaré cómo se realiza cada una de estas operaciones utilizando un ORM.
¿Cómo crear un registro?
Para empezar a crear un registro, primero verificamos nuestro esquema de datos. Debemos asegurarnos de que los campos esenciales, como email y password, estén presentes. Luego, utilizando el método create del ORM, enviamos la información al servicio, donde se valida la integridad de los datos antes de insertarlos en la base de datos.
La actualización es un poco más compleja, ya que debemos buscar primero el registro por su ID. Una vez encontrado, aplicamos los cambios deseados utilizando el método update.
const user =awaitUser.findByPk(userId);if(user){await user.update({email:'newemail@example.com'});}else{thrownewError('User not found');}
¿Cómo eliminar un registro?
La eliminación también requiere que primero busquemos el registro. Una vez confirmado que existe, utilizamos el método destroy para eliminarlo.
const user =awaitUser.findByPk(userId);if(user){await user.destroy();}else{thrownewError('User not found');}
¿Qué considerar en operaciones de búsqueda?
Uso de findOne y manejo de errores
Podemos usar findOne para buscar un registro específico. Sin embargo, es crucial manejar el caso donde el registro no se encuentre, devolviendo un error correspondiente.
const user =awaitUser.findOne({where:{email:'example@example.com'}});if(!user){thrownewError('User not found');}
¿Cómo realizar pruebas y validaciones en un CRUD?
Para garantizar el funcionamiento correcto de nuestro CRUD, es esencial realizar pruebas en cada endpoint. Pruebas básicas como intentar crear un usuario con un password inválido o buscar un usuario que no existe son necesarias para validar la robustez de nuestras soluciones.
Validación de datos con middlewares
Es importante tener middleware de validación que asegure la integridad de los datos entrantes, como el uso de Joi para validar tipos de datos y longitud.
Una buena práctica es tener una capa de middleware para manejar errores, especialmente los derivados de violar restricciones en nuestra base de datos, como intentos de crear registros con emails duplicados.
Sugerencias para el manejo de errores
Crear funciones centralizadas para manejar los errores puede ayudar a una mejor gestión de errores a nivel de aplicación, capturando excepciones como las violaciones de restricciones de unicidad.
¿Cómo seguir avanzando?
Si bien el ORM abstracta muchas de las complejidades del SQL, no debe ser una razón para dejar de aprender SQL. El conocimiento fundamental del SQL sigue siendo crucial para entender cómo funcionan realmente las cosas tras bambalinas. No olvides explorar y practicar cómo estructurar un mejor manejo de errores utilizando middlewares. Esto te permitirá construir aplicaciones más robustas y mantener un código organizado.
Sigue practicando y mejorando tus habilidades con ORMs, ¡tienes un gran camino por delante!
.
Hice este diagrama para entender mejor como funciona el flujo de operaciones dentro de nuestro Backend con Node JS. Utilizamos Express.js para el routing, Sequelize como ORM y PostgreSQL como base de datos.
.
.
En resumen:
.
El frontend envía datos a la API con un verbo HTTP.
El router (cuyas rutas de API diseñamos usando Express.js) recibe los datos y los verifica usando un Middleware de validación.
Si todo está bien, el router llama a los servicios.
Los servicios usan un método de POO incluido en la ORM para ejecutar una acción con la DB.
La ORM traduce a SQL para comunicarse con la DB.
La respuesta de la DB se propaga hasta llegar al router.
El router detecta si es un error y llama a un Middleware de gestión de errores (aquí usamos el paquete boom).
La respuesta, exitosa o de error se envía como respuesta al Frontend.
Y así es como está funcionando nuestro backend hasta el momento!!
Muchas gracias por hacer este diagrama, ¡viene como anillo al dedo!
muchas gracias
para el reto hice un middleware como menciono el profe el código quedo así
Saludos 👋
.
♻️Complementando tu código, te doy unas recomendaciones:
statusCode. Trata de mantener un orden en tus códigos de erro en función de los HttpStatusCode o en su defecto, crear un diccionario de errores pero no dejarlos expuestos en tu handler.
message. Te sugiero que establezcas una respuesta para tus clientes (web o móvil) y otra como logs, dividiendo tu abstracción de producción y de pruebas o local. Para ello te recomiendo Sentry.
Buena
No olviden levantar la base de datos y el administrador gráfico cada vez que vayan a lanzar el proyecto.
- docker-compose up -d pgadmin
- docker-compose up -d postgres
docker-compose up -d
para levantar todos los servicios
Igual de impotante es correr "node index.js" en consola
Estoy revisando bien el código y lo tengo igual, pero al momento de crear un usuario me indica que el id no debe null, tengo los modelos y schemas iguales, no entiendo por qué no le pasa a Nicolas.
Hola,
Que raro 🤔 ¿ya pudiste resolverlo?
Sugiero que vueltas a revisar el user.schema.js y validator.handler.js middleware o comparte tu código para por revisarlo :)
El tipado es que estoy haciendo el proyecto en TypeScript, el formato que mando es exactamente igual que el que manda Boom
Saludos 👋
.
♻️Complementando tu código, te doy unas recomendaciones:
statusCode. Trata de mantener un orden en tus códigos de erro en función de los HttpStatusCode o en su defecto, crear un diccionario de errores pero no dejarlos expuestos en tu handler.
error y message. Te sugiero que establezcas una respuesta para tus clientes (web o móvil) y otra como logs, dividiendo tu abstracción de producción y de pruebas o local. Para ello te recomiendo Sentry.
CRUD Operations
En mi caso, sigo ocupando node-postgres para el desarrollo del CRUD.
ℹ️ Repositorio: Link
.
✨Adicionamos un formato para dar estructura a nuestro request. Con ello, podremos acomodar la instancia para su uso.
/**
* @private * @description format data structure
* @param{object}body - body request
* @returns{object} - { fields, values, tuples } */#formatStructure(body){return{fields:Object.keys(body).join(),values:Object.values(body),tuples:Object.entries(body).map(([key, value])=>format(`${key} = %L`, value)),};}
.
♻️ Modificamos nuestras funcionalidades para dar servicio a las demás operaciones de CRUD
/**
* @description find all registers in table
* @returns{array} - response query postgresDB as array
*/asyncfindAll(){returnawaitthis.client.query(`SELECT * FROM ${this.table}`);}/**
* @description create a register in table
* @param{object}body - body request
* @returns{array} - response query postgresDB as array
*/asynccreate(body){const{ fields, values }=this.#formatStructure(body);const request =format(`INSERT INTO ${this.table}(${fields}) VALUES(%L) RETURNING *`, values
);returnawaitthis.client.query(request);}/**
* @description updates parcials of a register in table
* @param{object}param - param request
* @param{object}body - body request
* @returns{array} - response query postgresDB as array
*/asyncupdate({ id }, body){const{ tuples }=this.#formatStructure(body);const request =format(`UPDATE ${this.table} SET ${tuples} WHERE id = ${id} RETURNING *`);returnawaitthis.client.query(request);}/**
* @description delete a register in table
* @param{object}param - param request
* @returns{array} - response query postgresDB as array
*/asyncdelete({ id }){const request =format(`DELETE FROM ${this.table} WHERE id = ${id} RETURNING *`);returnawaitthis.client.query(request);}
SOLUCION AL PROBLEMA:
ID NOT NULL
CREATE_AT NOT NULL
.
He visto en muchos comentarios (sin respuesta) que tenían inconvenientes con el id y create_at de su DB. Y es en que en la configuración de user.model.js esta diseñada de manera que asigne valores por default al id y create_at pues el cliente no debe ser capaz de añadir esos datos en el método POST.
.
El detalle es que al crear la tabla no se asigna ningún defaultValue. Esto pasa porque las propiedades de Sequelize no están funcionando correctamente, por lo que recomiendo:
.
1 - Actualizar todas las dependencias
2 - Borrar la tabla Users desde pgadmin
3 - Correr nuestra aplicación.
4 - Verificar desde Pgadmin que id y create_at tienen sus respectivos defaultValues asignados.
(P.D: Así solucione el problema con el id).
.
En caso de que create_at siga arrojando problemas, recomiendo utilizar la configuracion para createdAt en user.model.js:
Sequelize.fn("NOW") es una poor solution para el problema de Sequelize.NOW del que podras encontrar mas información aquí.
.
Luego de aplicar estos cambios, repetir los pasos 2, 3 y 4.
.
Espero les sirva
thanks brooo 👊
esa no es la solucion para el id, el problema es que se puso mal la propiedad
autoIncrement , quizas se coloco autoInCrement
Me parece excelente la explicación de la validación que hace el profe al inicio de la clase.
Son muchas cosas y conceptos que se van acumulando.
users.service.js:
const boom =require('@hapi/boom');const{ models }=require('../libs/sequelize');classUserService{constructor(){}asynccreate(data){const newUser =await models.User.create(data);return newUser;}asyncfind(){const rta =await models.User.findAll();return rta;}asyncfindOne(id){const user =await models.User.findByPk(id);if(!user){throw boom.notFound('User not found');}return user;}asyncupdate(id, changes){const user =awaitthis.findOne(id);const rta =await user.update(changes);return rta;}asyncdelete(id){const user =awaitthis.findOne(id);await user.destroy();return{ id };}}module.exports=UserService;
Gracias por tu aporte, me ayudo a encontrar un error en mi servicio para actualizar un usuario!
Cuando crea el usuario con id número 6, luego intenta crear el 7 pero falla por ser el mismo correo, y vuelve a crear un usuario lo crea con el id 8, yo me imagino que es porque igual incrementa el id aunque no se registró el usuario por el error, hay alguna forma para que eso no ocurra?
Saludos 👋
Quitando el autoicremental y delegando que tu backend gestione la manera de utilizar el id, te podría recomendar UUID para el caso y, si gustas, generarlo mediante la librería.
Hola, tengo este error, como sería la solución?
Si alguien tiene problemas con el Update y el destroy en la documentación de Sequelize se utilizan estos 2 metodos
con respecto al update() y delete que dan el error de function no foud, en la documentacion esta la forma de hacerlo y como devolvia solo el id reutilizamos el findOne(id) y se modifico la respuesta del res en users.router:
muchas gracias
Hola, yo lo realice parecido a la consulta de user.
Este comentario no tiene que ver con la clase, pero ¿Es normal que tarde tanto en cargar (casi 1 minuto)?
.
.
.
¡Hola!
No es normal, tienes instalado wsl2?
Si instalaste wsl2 lo mejor es que guardes tus archivos en el sistema de archivos de linux, en ~/, esto es porque wsl2 está optimizado para ello. Si necesitas forzosamente guardar en /mnt/c, entonces lo mejor es que cambies a WSL1.
Para trabajar con una interfaz gráfica para administrar tus archivos puedes usar el comando explorer.exe <file-path>, esto te va a abrir una ventana del explorador de archivos de Windows en la carpeta que le indiques; explorer.exe . abre el directorio en el que estás parado. De esta forma podrás mover tus archivos, pero te recomiendo hacer el esfuerzo por acostumbrarte con la terminal de comandos.
Bueno, es normal porque estás guardando en /mnt/c y seguramente tienes wsl2, pero no debería actuar así, por eso te recomiendo guardar en linux, debería mejorar después de hacerlo.
Alguien sabe como puedo quitar estos log que me salen en la terminal cada que realizo una accion? Gracias.
buenas comunidad, tengo una duda de porque no me permite cambiar la contraseña con el putch si me permite cambiar el correo pero cuando intento la contraseña me sale que no esta permitido como puedo cambiar esto
.json(newUser) para
transformar la data, yo esperaría recibir el objeto que mando
router.post('/',validatorHandler(createUserSchema,'body'),async(req, res, next)=>{try{const body = req.body;const newUser =await service.create(body); res.status(201).json(newUser);}catch(error){next(error);}});