El manejo de errores es una parte crítica en el desarrollo de aplicaciones con NodeJS, especialmente al trabajar con clases de servicio y controladores. A continuación, se presenta un resumen del problema y una solución que mantiene la capacidad de retornar códigos de error adecuados.
Problema
Al extraer la lógica de obtención de datos en una clase de servicio, se pierde la capacidad de generar códigos de error HTTP informativos. Cuando se lanzan errores en la clase de servicio, el controlador los captura y, por defecto, devuelve un código de error 500, lo que puede ser confuso para el cliente que realiza la solicitud.
Solución Propuesta
-
Definición de Errores Personalizados: Se pueden crear errores personalizados para diferenciar entre los distintos tipos de problemas que puedan ocurrir. Por ejemplo, se puede definir un error para ID inválidos, no encontrados o prohibiciones basadas en la edad.
-
Uso de Errores Personalizados en la Clase de Servicio: En lugar de lanzar un
Error
genérico, lanzaremos errores personalizados que incluyan información sobre el código de estado que se debe retornar. - Manejo de Errores en el Controlador: En el controlador, se capturarán estos errores personalizados y se responderá con el código de estado correspondiente.
Implementación
Definición de Errores Personalizados
class InvalidIdError extends Error {
constructor(message) {
super(message);
this.name = "InvalidIdError";
this.statusCode = 400;
}
}
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = "NotFoundError";
this.statusCode = 404;
}
}
class MinorsProhibitionError extends Error {
constructor(message) {
super(message);
this.name = "MinorsProhibitionError";
this.statusCode = 403;
}
}
Clase de Servicio con Errores Personalizados
import Person from "../models/person.js";
import mongoose from "mongoose";
import { InvalidIdError, NotFoundError, MinorsProhibitionError } from "../errors/customErrors.js";
class PersonService {
async getPerson(id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
throw new InvalidIdError('Invalid ID');
}
const person = await Person.findById(id);
if (!person) {
throw new NotFoundError('Not found');
}
if (person.Age < 18) {
throw new MinorsProhibitionError('Cannot query for minors');
}
return person;
}
}
export default PersonService;
Controlador con Manejo de Errores
import PersonService from "../services/PersonService.js";
async function getPerson(req, res) {
const personService = new PersonService();
try {
const personData = await personService.getPerson(req.params.id);
res.status(200).send(personData);
} catch (error) {
console.error(error);
if (error instanceof InvalidIdError || error instanceof NotFoundError || error instanceof MinorsProhibitionError) {
res.status(error.statusCode).send(error.message);
} else {
res.status(500).send("Error retrieving media");
}
}
}
export { getPerson };
Conclusiones
Con esta implementación, se logra mantener la separación de responsabilidades entre la clase de servicio y el controlador. La clase de servicio se encarga de la lógica del negocio y puede lanzar errores personalizados que son manejados adecuadamente por el controlador. Esto permite retornar códigos de error HTTP informativos y mejorar la experiencia del cliente al interactuar con la API.