Como desarrolladores, todos nos esforzamos por conseguir bases de código eficientes y robustas que sean fáciles de entender, modificar y ampliar. Al adoptar las mejores prácticas y explorar técnicas avanzadas, podemos desbloquear el verdadero potencial de NodeJS y mejorar significativamente la calidad de nuestras aplicaciones.

Añadir middleware


En lugar de añadir el middleware a cada ruta, añádelo al principio de la lista de rutas utilizando el método use. De esta forma, cualquier ruta definida por debajo del middleware pasará automáticamente por el middleware antes de llegar a sus respectivos manejadores de ruta.

Recuerda que si tu quieres aprender más acerca de NodeJS te dejo el siguiente recurso, o si es de tu elección puedes aquirir el curso 👉 aquí

const route = express.Router();
const {login} = require("../controllers/auth");

route.get('/login', login)

// isAuthenticated is middleware that checks whether 
// you are authenticated or not
// // ❌ Avoid this: middleware on each route
route.get('/products', isAuthenticated, fetchAllProducts);
route.get('/product/:id', isAuthenticated, getProductById)
// ✅ Instead, do this
// Route without middleware
route.get('/login', login)

// Middleware function: isAuthenticated
// This will be applied to all routes defined after this point
route.use(isAuthenticated);

// Routes that will automatically check the middleware
route.get('/products', fetchAllProducts);
route.get('/product/:id', getProductById);

Este enfoque ayuda a mantener el código organizado y evita repetir el middleware para cada ruta individualmente.

Utilizar la gestión global de errores


En lugar de estructurar la respuesta de error en cada controlador, podemos utilizar la función de gestión global de errores de NodeJS. En primer lugar, cree una clase AppError personalizada derivada de la clase Error incorporada. Esta clase personalizada te permite personalizar el objeto de error con propiedades adicionales como statusCode y status.

// Custom Error class
module.exports = class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.status = statusCode < 500 ? "error" : "fail";

    Error.captureStackTrace(this, this.constructor);
  }
};

Una vez que haya creado una clase de error personalizada, añada un middleware controlador de errores global dentro de su archivo de enrutador raíz. Esta función middleware toma cuatro parámetros (err, req, res, next) y maneja errores en toda la aplicación.

Dentro del manejador global de errores, usted formatea la respuesta de error basándose en las propiedades statusCode, status y message del objeto error. Puede personalizar este formato de respuesta para adaptarlo a sus necesidades. Además, se incluye la propiedad stack para entornos de desarrollo.

// Express setup
const express = require('express');

const app = express();

app.use('/', (req, res) => {
  res.status(200).json({ message: "it works" });
});

app.use('*', (req, res) => {
    res.status(404).json({
        message: `Can't find ${req.originalUrl} this route`,
    });
});

// 👇 add a global error handler after all the routes.
app.use((err, req, res, next) => {
  err.status = err.status || "fail";
  err.statusCode = err.statusCode || 500;

  res.status(err.statusCode).json({
    status: err.status,
    message: transformMessage(err.message),
    stack: process.env.NODE_ENV === "development" ? err.stack : undefined,
  });
});

Después de añadirlo, puede lanzar un error utilizando next (new AppError(message, statusCode)). La función next pasa automáticamente el error al middleware global de gestión de errores.

// inside controllers

// route.get('/login', login);

exports.login = async (req, res, next) => {
  try {
    const { email, password } = req.body;
  
    const user = await User.findOne({ email }).select("+password +lastLoginAt");
  
    if (!user || !(await user.correctPassword(password, user.password))) {
      // 👇 like this
      return next(new AppError("Invalid Email / Password / Method", 404));
    }
  
     // Custom logic for generating a token
    const token = 'generated_token';

    res.status(200).json({ token });
  } catch(error) {
      next(error
  }
});

En general, este enfoque simplifica la gestión de errores centralizándola en un solo lugar, lo que facilita el mantenimiento y la personalización de las respuestas a errores en toda la aplicación.

Utilizar una función Try-Catch personalizada


En lugar de envolver manualmente cada función del controlador con un bloque try-catch, podemos utilizar una función personalizada que consiga el mismo propósito.

// ❌ Avoid this
// Using try-catch block each controllers
exports.login = async (req, res, next) => {
  try {
    // logic here
  } catch(error) {
      res.status(400).json({ message: 'You error message'}
  }
});

La función tryCatchFn acepta una función (fn) como entrada y devuelve una nueva función que envuelve la función original con un bloque try-catch. Si se produce un error dentro de la función envuelta, se captura utilizando el método catch, y el error se pasa a la siguiente función para ser manejado por el controlador de errores Global.

// ✅ Instead, do this
const tryCatchFn = (fn) => {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
}

// To use this custom function, you can wrap your controller 
// functions with tryCatchFn:
exports.login = tryCatchFn(async (req, res, next) => {
  // logic here
});

Envolviendo sus funciones de controlador con tryCatchFn, se asegura de que cualquier error lanzado dentro de esas funciones será automáticamente capturado y pasado al manejador de errores global, eliminando la necesidad de añadir bloques try-catch individualmente.

Este enfoque ayuda a centralizar el manejo de errores de una manera más limpia y concisa, haciendo que su código sea más fácil de mantener y reduciendo el código repetitivo de manejo de errores.

Separa el archivo principal en dos partes.


Cuando se desarrolla una aplicación NodeJS usando Express, es común tener un archivo principal que contenga toda la lógica de negocio, definiciones de rutas y configuración del servidor. Sin embargo, la gestión y el mantenimiento de un único archivo que se encarga de todo puede llegar a ser difícil a medida que la aplicación crece.

Una técnica recomendada para resolver este problema y mantener el código base más limpio y organizado es separar el archivo principal en dos partes: una para las rutas y otra para la configuración del servidor. He aquí un ejemplo:

// app.js
const express = require('express');
const app = express();

/* Middlewares */

app.get('/', (req, res) => {
  res.status(200).json({ message: "it works" });
})

app.use(/* Global Error Handler */);
module.exports = app;

// server.js
const app = require('./app');
const port = process.env.PORT || 5001;

app.listen(port, () => console.log('Server running at', port));

Separar rutas de controladores


Para conseguir un código más organizado y modular, recomiendo separar las rutas de los controladores. Esta práctica ayuda a mantener una clara separación de preocupaciones y mejora la legibilidad y mantenibilidad del código. Aquí hay un ejemplo que demuestra la separación de rutas y controladores.

// ❌ Avoid this
const route = express.Router();

route.get('/login', tryCatchFn(req, res, next) => {
  // logic here
}))

// ✅ Do this
const route = express.Router();
const {login} = require("../controllers/auth");

route.get('/login', login);

Conclusión


En este artículo, hemos discutido diferentes técnicas avanzadas para escribir código NodeJS que sea limpio y fácil de mantener. Hay muchas mejores prácticas disponibles que pueden mejorar significativamente la calidad del código de tu aplicación. Siéntete libre de explorar estas técnicas y aplicarlas para mejorar tu código base.

Espero que hayas disfrutado con este artículo.

Mantén la curiosidad y sigue programando.

Fuente

Plataforma de cursos gratis sobre programación