No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Crear, actualizar y eliminar

11/27
Recursos

Aportes 36

Preguntas 22

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Como se está estructurando nuestro Proyecto?


.
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!!

para el reto hice un middleware como menciono el profe el código quedo así

function queryErrorHandler(err, req, res, next) {
  if (err.parent) {
    const { fields, parent } = err;
    res.status(500).json({
      field: fields,
      message: parent.detail,
    });
  }
  next(err);
}

la propiedad fields y parent vienen dentro del error que se genera cuando se hace la consulta a la base de datos

la respuesta se ve así

{
  "field": {
    "email": "[email protected]"
  },
  "message": "Key (email)=([email protected]) already exists."
}

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

Resolví el middleware de esta manera:

const { ValidationError } = require('sequelize')
function handleSQLError (err, req, res, next) {
  if (err instanceof ValidationError) {
    throw boom.conflict(err.errors[0].message)
  }
  next(err)
}

Y lo añadí en mi index.js

app.use(logErrors)
app.use(handleSQLError)
app.use(boomErrorHandler)
app.use(errorHandler)

Para el reto este es el middleware que hice:

export function sequelizeErrorHandler (error: ValidationError, req: Request, res: Response, next: NextFunction) {
  if (error instanceof ValidationError) {
    res.status(409).json({
      'statusCode': 409,
      'error': error.errors[0].type,
      'message': error.errors[0].message
    });
  }
}

El tipado es que estoy haciendo el proyecto en TypeScript, el formato que mando es exactamente igual que el que manda Boom

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
     */
    async findAll() {
        return await this.client.query(`SELECT * FROM ${this.table}`);
    }
    /**
     * @description create a register in table
     * @param {object} body - body request
     * @returns {array} - response query postgresDB as array
     */
    async create(body) {
        const { fields, values } = this.#formatStructure(body);
        const request = format(
            `INSERT INTO ${this.table}(${fields}) VALUES(%L) RETURNING *`,
            values
        );
        return await this.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
     */
    async update({ id }, body) {
        const { tuples } = this.#formatStructure(body);
        const request = format(
            `UPDATE ${this.table} SET ${tuples} WHERE id = ${id} RETURNING *`
        );
        return await this.client.query(request);
    }
    /**
     * @description delete a register in table
     * @param {object} param - param request
     * @returns {array} - response query postgresDB as array
     */
    async delete({ id }) {
        const request = format(
            `DELETE FROM ${this.table} WHERE id = ${id} RETURNING *`
        );
        return await this.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:

createdAt: {
    allowNull: false,
    type: DataTypes.DATE,
    field: 'create_at',
    defaultValue: Sequelize.fn("NOW")
  }

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

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');

class UserService {
  constructor() {}
  async create(data) {
    const newUser = await models.User.create(data);
    return newUser;
  }

  async find() {
    const rta = await models.User.findAll();
    return rta;
  }

  async findOne(id) {
    const user = await models.User.findByPk(id);
    if (!user) {
      throw boom.notFound('User not found');
    }
    return user;
  }

  async update(id, changes) {
    const user = await this.findOne(id);
    const rta = await user.update(changes);
    return rta;
  }

  async delete(id) {
    const user = await this.findOne(id);
    await user.destroy();
    return { id };
  }
}

module.exports = UserService;

Si alguien tiene problemas con el Update y el destroy en la documentación de Sequelize se utilizan estos 2 metodos

  const rta = models.User.update(changes,{
      where:{id}
    })
    const rta = await models.User.destroy({where: {id}})
    return rta

Mi solución al reto con middlewares:

function validationErrorHandler (err,req,res,next) {
  if (err.message === "Validation error") {
    res.status(406).json({
      message: err.errors[0].message,
    });
  }
  next(err);
}

en el archivos index.js:

app.use(logErrors);
app.use(validationErrorHandler);
app.use(boomErrorHandler);
app.use(errorsHandler);

Esto entrega el siguiente mensaje:

{
    "message": "email must be unique"
}

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:

Hola, yo lo realice parecido a la consulta de user.

 async update(id, changes) {
    const user = await this.findOne(id);
    const email = await models.User.findOne({
      where: { email: changes.email },
    });
    if (email) {
      throw boom.badData('duplicated unique email');
    }
    const rta = await user.update(changes);
    return rta;
  }

Para capturar los errores cuando se corren las consultas del orm y hay un error:

1)En los middlewares, hago la función en el error.handler.js y export mi función

function ormErrorHandler(err, req, res, next) {
  if (err instanceof ValidationError) {
    res.status(409).json({
      statusCode: 409,
      message: err.name,
      errors: err.errors
    });
  }
  next(err);
}


module.exports = { logErrors, errorHandler, boomErrorHandler, ormErrorHandler }

2)En el index.js declaro el middleware para poder utilizarlo

app.use(logErrors);
app.use(ormErrorHandler);
app.use(boomErrorHandler);
app.use(errorHandler);

Cuando se crea registros en la base de datos y el campo id es un numero, las validaciones de esquema con Joi pueden dar problemas, entonces es mejor permitir los numeros asi:

const id = Joi.alternatives().try(Joi.string().uuid(), Joi.number());
¿prodiamos dificar el codigo? ```js async update(id, changes) { const user = await models.User.findByPk(id); const rta = await user.update(changes); return rta; } ```por: ```js async update(id, changes) { const user = await models.User.findByPk(id); await user.update(changes); return user; } ```
No sé si a alguien le pasó el siguiente error: "message": "user.update is not a function" me podrían ayudar por favor
Esta interesante el echo del manejo de errores cuando se usan OMR's, estuve un rato tratando de hacerlo con buenas practicas pero aun no doy hehe, que bonito es aprender a aprender :)

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. Alguien sabe que puede estar pasando?

Los que tienen problemas con el update y destroy, recuerden usar await al usar la función findOne, ya que es una promesa.

 async update(id, changes) {
    const user = await this.findOne(id);
    const rta = await user.update(changes);
    return rta;
  }

  async delete(id) {
    const user = await this.findOne(id);
    await user.destroy();
    return { id };
  }

para el reto propuesto por el profesor, hice este middleware en el archivo error.handler.js

function sequelizeErrorHandler(err, req, res, next) {
    if(err.parent) {
        res.status(500).json({
            name: err.name,
            message: err.errors[0].message,
            detail: err.parent.detail
        });
    }
    next(err);
}

lo exporté, lo importé en el archivo index.js y lo usé luego del middleware de logError

//Middleware
app.use(logError);
//sequelizeErrorHandler
app.use(sequelizeErrorHandler);
app.use(boomErrorHandler);
app.use(errorHandler);

todo esto generando una respuesta así

{
	"name": "SequelizeUniqueConstraintError",
	"message": "email must be unique",
	"detail": "Key (email)=([email protected]) already exists."
}

Lo que no me ha gustado para nada de este curso es que no tenemos un modelo de entidad relación, básicamente lo estamos haciendo en base al repositorio no tenemos un modelo a seguir.

Ya puedo modificar datos de mi tabla con el crud!! genial

Por convencion estaba yo llamando a la funcion de findOne cuando el profe comienza a hacerlo :3 Muy bien por el profe

queria saber si las consultas se realiza con el modelo como seria el proceso de realizar una consulta pero que en postgres este como una funcion ya que se puede optimizar la consulta como seria el proceso ya que incialmente se realizar   async find() {    const movimientos = await models.Movimiento.findAll();    return movimientos;  }

Por si acaso a alguien le pasa, el update y el delete solo me funcionarion poniendo eso entre paréntesis

(await user).update(changes)
(await user).delete()

Yo tuve problemas con el actualizando a user, el error decía user.update() is not a function.
Lo resolví de la siguiente manera

Mi errorHandler con Typescript

import { Request, Response, NextFunction } from "express";
import { Boom } from "@hapi/boom";
import { BaseError } from "sequelize";

// export function logErrors(
//   err: Error,
//   req: Request,
//   res: Response,
//   next: NextFunction
// ) {
//   console.log(err);
//   next(err);
// }

export function errorHandler(
  err: Error | Boom | BaseError | any,
  req: Request,
  res: Response,
  next: NextFunction
) {
  let errorResponse: { status: number; message: string };

  if (err.isBoom) {
    const { statusCode, payload } = err.output;
    errorResponse = {
      status: +statusCode,
      message: payload.message,
    };
  } else if (err instanceof BaseError) {
    const _err: any = err;

    if (_err.errors) {
      errorResponse = {
        status: 500,
        message: _err.errors[0].message,
      };
    } else {
      errorResponse = {
        status: 500,
        message: "Server error :c",
      };
    }
  } else {
    console.log(err);
    errorResponse = {
      status: 500,
      message: "Server error :c",
    };
  }

  res.status(errorResponse.status).json({ message: errorResponse.message });
}

Hola por aca dejo un problema que se me presento por un tipo, el problema era que estaba retornado en findOne() un objeto en vez de un parametro solo

 async findOne(id) {
    const user = await models.User.findByPk(id);
    if (!user) {
      throw boom.notFound('user not found');
    }
    return {user};
  }

pero deberia ser de esta forma

  async findOne(id) {
    const user = await models.User.findByPk(id);
    if (!user) {
      throw boom.notFound('user not found');
    }
    return user;
  }

que tal una clase DDD o arqutectura hexagonal

Dejaré esto aquí en caso de que a alguien más les pase. Y es muy importante que haya concordancia entre nuestro esquema y nuestro modelo. Yo no la tenía y esto me daba errores en el programa.

user.schema.js:

const Joi = require('joi')

const id = Joi.number().integer()
const email = Joi.string().min(6) //@gmail.com
const password = Joi.string().min(6)

const createUserSchema = Joi.object({
  email: email.required(),
  password: password.required()
})

const updateUserSchema = Joi.object({
  email,
  password
})

const getUserSchema = Joi.object({
  id: id.required()
})

module.exports = { createUserSchema, updateUserSchema, getUserSchema }

user.model.js:

const {
  Model,
  DataTypes,
  Sequelize
} = require('sequelize')

const USER_TABLE = 'users';

const UserSchema = {
  id: {
    allowNull: false,
    autoIncrement: true,
    primaryKey: true,
    type: DataTypes.INTEGER
  },
  email: {
    allowNull: false,
    type: DataTypes.STRING,
    unique: true
  },
  password: {
    allowNull: false,
    type: DataTypes.STRING
  },
  createdAt: { //* nombre en JS
    allowNull: false,
    type: DataTypes.DATE,
    field: 'created_at', //* nombre en PostgreSQL
    defaultValue: Sequelize.NOW
  }
}

class User extends Model {
  static associate() {

  }

  static config(sequelize){
    return {
      sequelize,
      tableName: USER_TABLE,
      modelName: 'User',
      timestamps: false,
    }
  }
}

module.exports = { USER_TABLE, UserSchema, User }

en services para updateUser

const userUpdated = await models.User.update({
      ...data
    }, {
      where: {
        id: userId
      }
    })
    return userUpdated

Para el getUser usé models.User.findByPk(userId) en lugar de findOne

[
	{
		"id": 1,
		"email": "[email protected]",
		"password": "121331dd1dfdffd",
		"createdAt": "2022-09-21T15:15:15.000Z"
	},
	{
		"id": 2,
		"email": "[email protected]",
		"password": "55asadd5",
		"createdAt": "2022-09-21T22:13:29.399Z"
	}
]

Sentí mucha satisfacción de ver como funciona todo en esta clase, el profesor ayuda a entender muy bien el procedimiento.

Middleware

const boom = require('@hapi/boom');
const { models } = require('../libs/sequelize');

function queryValidatorHandler () {
  return async (req, res, next) => {
    const data = req.body;
    const user = await models.User.findOne({ where: { email: data.email }});

    if (user) {
      return next(boom.conflict('User already exists'));
    }
    next()
  }
}

module.exports = queryValidatorHandler;

En userRouter importo la función y

router.post('/',
  validatorHandler(createUserSchema, 'body'),
  queryValidatorHandler(),
  async (req, res, next) => {
    try {
      const body = req.body;
      const newUser = await service.create(body);
      res.status(201).json(newUser)
    } catch (err) {
      next(err)
    }
})