skip to Main Content

As for now, I’m using mongoose middleware to handle Mongoose specific errors (validation, cast, ….).
I’m using the following code in all of my schemas:

schema.post('save', handleValidationError);
schema.post('findOneAndUpdate', handleValidationError);
schema.post(['findOne', 'deleteOne'], handleCastError);

Is there anyway to make this global in all schemas without repeating the code?
I tried to use plugins as the following but they don’t get triggered if an error happens.

const errorsPlugin = (schema: any, _options: any): void => {
    schema.post('save', handleValidationError);
    schema.post('findOneAndUpdate', handleValidationError);
    schema.post(['findOne', 'deleteOne'], handleCastError);
};

const connection = await mongoConnect(url);
plugin(errorsPlugin);
logger.info(`MongoDB connected: ${connection.connection.name}`);

Edit 1: error handler function

const handleValidationError = (error: NodeJS.ErrnoException, _res: Response, next: (err?: Error) => void): void => {
    if (error.code?.toString() === '11000') {
        const { keyPattern, keyValue } = error as Error & {
            keyPattern: { [key: string]: number };
            keyValue: { [key: string]: string };
        };
        const key = Object.keys(keyPattern)[0];
        const value = Object.values(keyValue)[0];
        throw new DuplicatedKeyError(key as string, value as string);
    } else if (error instanceof mongooseError.ValidationError) {
        throw new ValidationError(error.errors);
    } else if (error instanceof mongooseError.CastError) {
        throw new CastError(error.kind, error.value);
    }
    next();
};

4

Answers


  1. You may consider this approach:

    Create an ErrorResponse Class

    class ErrorResponse extends Error {
      constructor(message, statusCode) {
        super(message)
        this.statusCode = statusCode
      }
    }
    

    Create an error handler middleware

    const errorHandler = (err, req, res, next) => {
      let error     = { ...err }
      error.message = err.message
    
      // Validation error
      if (err.name === 'ValidatorError') {
        error = new ErrorResponse(Object.values(err.error).map(({ message }) => message), 400)
      }
    
      // Wrong ObjectId
      if (err.name === 'CastError') {
        error = new ErrorResponse(`Resource not found with id of ${err.value}`, 404)
      }
    
      // Duplicated field
      if (err.code === 11000) {
        error = new ErrorResponse('Duplicate field value entered', 400)
      }
    
      res
        .status(error.statusCode || 500)
        .json({ error: error.message || 'Internal Server Error' })
    }
    

    Change your controllers

    try{
       //... you code ... //
    } catch (err) {
       next(err)
    }
    

    Use middleware after defining your routes

    app.use(errorHandler)
    
    Login or Signup to reply.
  2. When any errors will be thrown from any where they will go to errorhandler(error.js) from their they will return to front-side and we can handle any kind of error using any custom messages or status codes with below method.

    app.js

    // your server(app.js code)
    // add bellow line to your app.js
    const errorhandler = require("./Middleware/error");
    app.use(errorhandler);
    

    Middleware/error.js

    const { giveresponse } = require("../helper/res_help");
    
    const errorHandler = (err, req, res, next) => {
    let message;
    //Mongoose Bad Object
    if (err.name === "CastError") {
        message = `Resourse not found`;
        //error = new ErrorResponse(message, 404);
    }
    
    //Mongoose Duplicate key
    if (err.code === 11000) {
        message = "Duplicate field Value entered";
        //error = new ErrorResponse(message, 400);
    }
    
    //Mongoose validation Error
    if (err.name === "ValidationError") {
        message = Object.values(err.errors).map((val) => val.message)[0];
        //error = new ErrorResponse(message, 400);
    }
    
    console.log(err);
    return giveresponse(res, 201, false, message);
    };
    
    module.exports = errorHandler;
    

    helper/res_help.js;

    exports.giveresponse = function (
    res,
    status_code,
    success,
    message,
    data = null
    ) {
    var data = data == null ? {} : data;
    data["success"] = success;
    data["message"] = message;
    data["status_code"] = status_code;
    var json_to_send = { success: success, message: message, data: data };
    
    return res.status(status_code).json(json_to_send);
    };
    

    your controllers //wrap your code in asyncHandler you want even need to use try-catch

    const asyncHandler = require("../Middleware/async");
    exports.login = asyncHandler(async (req, res) => {
         //your login api code
    });
    

    Middleware/async.js

    const asyncHandler = (fn) => (req, res, next) =>
    Promise.resolve(fn(req, res, next)).catch(next);
    
    module.exports = asyncHandler;
    
    Login or Signup to reply.
  3. You can define the middleware functions in a separate file and then import them into your schemas.

    Create a new file errorHandler.js and declare your middleware functions inside:

    const handleValidationError = (error, doc, next) => {
      if (error.name === 'ValidationError') {
        // handle validation error
      }
      next(error);
    };
    
    const handleCastError = (error, doc, next) => {
      if (error.name === 'CastError') {
        // handle cast error
      }
      next(error);
    };
    
    module.exports = {
      handleValidationError,
      handleCastError
    };
    

    Then import file in node.js schemas by:

    const { handleValidationError, handleCastError } = require('./errorHandler');
    
    const schema = new mongoose.Schema({
      // place your schema fields here
    });
    
    schema.post('save', handleValidationError);
    schema.post('findOneAndUpdate', handleValidationError);
    schema.post(['findOne', 'deleteOne'], handleCastError);
    

    When you have multiple schemas in your application, defining the same middleware functions in every schema file can lead to code duplication and make your code harder to maintain. In such cases, it’s a good practice to define the middleware functions in a separate file and import them into your schema files.

    Login or Signup to reply.
  4. This is an interesting insight and a nice approach you are taking, to solving the problem globally for all schemas.

    Looking at the documentation, it looks like you are 90% there. The only thing I can spot that differs with the documentation is how you are applying your plugin.

    To apply the plugin to a single schema you should be making the following call:

    // mySchema.js : the same file where you define your schema
    mySchema.plugin(errorsPlugin);
    

    To apply the plugin to all schemas in the project, you can use the global mongoose object:

    mongoose.plugin(errorsPlugin)
    

    The above call should happen before you create any models, for example with mongoose.model("MyModel", mySchema). If you are creating all your models in a single file, it could look like this:

    // models.js
    mongoose.plugin(errorsPlugin)
    const User = mongoose.model('User', userSchema)
    const Car = mongoose.model('Car', carSchema)
    // etc.
    

    However if you are co-locating the model creation with the schema definition, for example:

    const userSchema = new Schema({...})
    const User = mongoose.model('User', userSchema);
    

    After, you should make the call before requiring/exporting any models at all so that the plugin statement is evaluated before the model files are executed. For example:

    // models/User.ts
    const userSchema = new Schema({...})
    export const User = mongoose.model('User', userSchema)
    
    // models/Car.ts
    const carSchema = new Schema({...})
    export const Car = mongoose.model('Car', carSchema)
    
    // models/index.ts
    // Register all Global Plugins
    mongoose.plugin(errorsPlugin)
    
    // Export all models after plugins have been registered
    export * from  './User'
    export * from './Car'
    

    I hope this helps 🙂

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search