No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Crear, actualizar y eliminar

11/27
Recursos

Aportes 31

Preguntas 22

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

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."
}

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

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
.
鉁ˋdicionamos 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(鈥淣OW鈥) 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);

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

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)
    }
})

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()