Home > Mobile >  How to use mongoose.isValidObjectId as a middleware in nestjs?
How to use mongoose.isValidObjectId as a middleware in nestjs?

Time:06-17

I have an issue with repetitive requests for checking an Order id, if it is valid ObjectId or not. I got this error:

CastError: Cast to ObjectId failed for value "629b9fbd620dbc419a52e8" (type string) at path "_id" for model "Order"

After a lot of Googling, I found two approaches to tackle the problem, however I'll have to duplicate these codes for each service, which isn't a good idea.

First approach:

 if (!mongoose.Types.ObjectId.isValid(req.params.id)) {
      throw new HttpException('Not a valid ObjectId!', HttpStatus.NOT_FOUND);
    } else {
      return id;
    }

Second approach:

  if (!mongoose.isValidObjectId(req.params.id)) {
    throw new BadRequestException('Not a valid ObjectId');
    } else {
      return id;
    }

I used below codes for making and using a middleware, thus I could check ID whenever a service using an id parameter.

validateMongoID.ts

import {
  BadRequestException,
  Injectable,
  NestMiddleware,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import mongoose from 'mongoose';

@Injectable()
export class IsValidObjectId implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // Validate Mongo ID
    if (req.params.id) {
      if (!mongoose.isValidObjectId(req.params.id)) {
        throw new BadRequestException('Not a valid ObjectId');
      }
    }
    next();
  }
}

orders.module.ts

export class OrdersModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(IsValidObjectId).forRoutes('/');
  }
}

After trying as a middleware in the orders.modules.ts, I got the same error mentioned above. So, any idea to use it as a middleware?

CodePudding user response:

I had to do this exact thing a couple of weeks ago. Here is my solution. Works perfectly fine. Not a middleware, though.

id-param.decorator.ts

import { ArgumentMetadata, BadRequestException, Param, PipeTransform } from '@nestjs/common';
import { Types } from 'mongoose';

class ValidateMongoIdPipe implements PipeTransform<string> {
  transform(value: string, metadata: ArgumentMetadata) {
    if (!Types.ObjectId.isValid(value)) {
      throw new BadRequestException(`${metadata.data} must be a valid MongoDB ObjectId`);
    }

    return value;
  }
}


export const IdParam = (param = '_id'): ParameterDecorator => (
  Param(param, new ValidateMongoIdPipe())
);

Usage

// If param is called _id then the argument is optional
@Get('/:_id')
getObjectById(@IdParam() _id: string) {
  return this.objectsService.getById(_id);
}

@Get('/:object_id/some-relation/:nested_id')
getNestedObjectById(
  @IdParam('object_id') objectId: string,
  @IdParam('nested_id') nestedId: string,
) {
  return this.objectsService.getNestedById(objectId, nestedId);
}

How it works

When using the @Param decorator you can give it transform pipes that will validate and mutate incoming value.
@IdParam decorator is just a @Param with the ValidateMongoIdPipe provided as a second argument.

CodePudding user response:

I have found another way to solve it with the help of Lhon (tagged in comments).

create a file (I named it globalErrorHandler.ts) as follows:

import {
  ArgumentsHost,
  ExceptionFilter,
  HttpException,
  HttpStatus,
  InternalServerErrorException,
} from '@nestjs/common';

export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: InternalServerErrorException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;
    /**
     * @description Exception json response
     * @param message
     */
    const responseMessage = (type, message) => {
      response.status(status).json({
        statusCode: status,
        path: request.url,
        errorType: type,
        errorMessage: message,
      });
    };
    // Throw an exceptions for either
    // MongoError, ValidationError, TypeError, CastError and Error
    if (exception.message) {
      const newmsg: any = exception;
      responseMessage(
        'Error',
        newmsg.response?.message ? newmsg.response.message : exception.message,
      );
    } else {
      responseMessage(exception.name, exception.message);
    }
  }
}

add below line to main.ts

app.useGlobalFilters(new AllExceptionsFilter());

create another file (I named it validateMongoID.ts) as follows:

import {
  BadRequestException,
  Injectable,
  NestMiddleware,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class IsValidObjectId implements NestMiddleware {
  async use(req: Request, res: Response, next: NextFunction) {
    // Validate Mongo ID

    if (req.params.id) {
      if (!/^[a-fA-F0-9]{24}$/.test(req.params.id)) {
        throw new BadRequestException('Not a valid ObjectId');
      }
    }
    next();
  }
}

last step: import it as a middleware in app.module.ts

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(IsValidObjectId).forRoutes('*');
  }
}
  • Related