Using Nest.js I would like to trim() all #body() input values - node.js

I would like to trim (empty white spaces at the beggining / end of a input field) of all body values. I don't want to have to loop all body elements
for each API request to clean the fields up.
I was wondering if I can overwrite the #body() annotation, and put the code in there, or if there's a input formatter or pipe that does that.
At the moment, I'm doing this:
createAccount(#Body() body: any) {
this.account.create(body.map(s => s.trim()))
}
Thanks

Thank you Uroš Anđelić for your advise. I created a PipeTransform to take care of this:
import { Injectable, PipeTransform,
ArgumentMetadata, BadRequestException } from '#nestjs/common'
#Injectable()
export class TrimPipe implements PipeTransform {
private isObj(obj: any): boolean {
return typeof obj === 'object' && obj !== null
}
private trim(values) {
Object.keys(values).forEach(key => {
if (key !== 'password') {
if (this.isObj(values[key])) {
values[key] = this.trim(values[key])
} else {
if (typeof values[key] === 'string') {
values[key] = values[key].trim()
}
}
}
})
return values
}
transform(values: any, metadata: ArgumentMetadata) {
const { type } = metadata
if (this.isObj(values) && type === 'body') {
return this.trim(values)
}
throw new BadRequestException('Validation failed')
}
}
And this is how to use it
#UsePipes(new TrimPipe())
createAccount(#Body() body: any) {
this.account.create(body)
}
You can also set it up as a global pipe:
app.useGlobalPipes(new TrimPipe());

I made an interceptor just for that. I use it globally but you can use it wherever you want with #UseInterceptors decorator. Here is the base class that can be extended for other body transformations also:
import { CallHandler, ExecutionContext, NestInterceptor } from '#nestjs/common'
import { Observable } from 'rxjs'
export abstract class TransformRequest implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
this.cleanRequest(context.switchToHttp().getRequest())
return next.handle()
}
cleanRequest(req: any): void {
req.query = this.cleanObject(req.query)
req.params = this.cleanObject(req.params)
// If you use express adapter you will have
// req.method
// If you use fastify adapter you will have
// req.raw.method
if (req.raw.method !== 'GET') {
req.body = this.cleanObject(req.body)
}
}
cleanObject(obj: object | null | undefined) {
if (!obj) {
return obj
}
for (const key in obj) {
// Prototype of obj is null
// if (!obj.hasOwnProperty(key)) {
// continue
// }
const value = obj[key]
// If the value is another nested object we need to recursively
// clean it too. This will work for both array and object.
if (value instanceof Object) {
this.cleanObject(value)
} else {
// If the value is not an object then it's a scalar
// so we just let it be transformed.
obj[key] = this.transform(key, value)
}
}
return obj
}
abstract transform(key: string | number, value: boolean | number | string | null | undefined): any
}
And here is the trim strings class:
import { Injectable } from '#nestjs/common'
import { TransformRequest } from './transform.request'
#Injectable()
export class TrimStrings extends TransformRequest {
private except = ['password']
transform(key: string | number, value: any) {
if (this.isString(value) && this.isString(key) && !this.except.includes(key)) {
return value.trim()
}
return value
}
isString(value: any): value is string {
return typeof value === 'string' || value instanceof String
}
}
You can also find it in this repository.
I actually think it's a better idea to trim the body in the front-end side if that is an option.

You can extend NestMiddleware class for that also:
import { NestMiddleware } from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
export class TrimMiddleware implements NestMiddleware {
use(req: Request, _res: Response, next: NextFunction) {
const requestBody = req.body;
if (this.isObj(requestBody)) {
req.body = this.trim(requestBody);
}
next();
}
private isObj(obj: any): boolean {
return typeof obj === 'object' && obj !== null;
}
private trim(value: unknown) {
if (typeof value === 'string') {
return value.trim();
}
if (Array.isArray(value)) {
value.forEach((element, index) => {
value[index] = this.trim(element);
});
return value;
}
if (this.isObj(value)) {
Object.keys(value).forEach(key => {
value[key] = this.trim(value[key]);
});
return value;
}
return value;
}
}
And then in NestModule you can use apply and forRoutes methods to decide on which endpoint apply middleware.

Related

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

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?
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.
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('*');
}
}

Using Fastify preHandler middleware

Passing a middleware to authenticate user before accessing this route.
When I'm passing tokenController.authUser as a middleware tokenService inside tokenController is undefined. However when I run this method as a function inside the route instead of a middleware it works fine.
server.post('/api/admin/test', { preHandler: [tokenController.authUser] }, async (request: any, reply: any) => {
return null
});
Token Controller :-
import { Users } from "#prisma/client";
import ITokenService from "../../services/tokenService/ITokenService";
import ITokenController from "./ITokenController";
export default class TokenController implements ITokenController {
private readonly tokenService: ITokenService;
constructor(_tokenService: ITokenService) {
this.tokenService = _tokenService;
}
async authUser(request: any, reply: any): Promise<Users | Error> {
const authHeader = request.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token === null)
return reply.code(401);
try {
const result = await this.tokenService.verifyToken(token);
console.log(result);
return result;
}
catch (e) {
reply.code(401);
return new Error("Error");
}
}
}
Token Service :-
import { Users } from "#prisma/client";
import ITokenService from "./ITokenService";
export default class TokenService implements ITokenService {
private readonly sign: Function;
private readonly verify: Function;
private readonly secretKey: string;
constructor(sign: Function, verify: Function, _secretKey: string) {
this.sign = sign;
this.verify = verify;
this.secretKey = _secretKey;
}
public async generateToken(user: Users): Promise<string> {
return await this.sign({ user }, this.secretKey);
}
public async verifyToken(token: string): Promise<Users | Error> {
const result = await this.verify(token, this.secretKey);
return result;
}
}
For some reason making a separate middleware function and calling tokenController.authUser inside that method works fine.
const middleware = (_req, _res, next) => {
console.log('middleware');
next()
}
server.post('/api/admin/test', { preHandler: [middleware] }, async (request: any, reply: any) => {
return null
});

Apply an interface in Typescript

I am creating a project with Node JS and Typescript, I have a class and an interface where I type all the data. What I need to know is how to apply it to the classroom.
This is my interface:
export interface IController {
url: string,
api: string
}
This is my class where I want to apply it:
import { Request, Response } from 'express';
import { constUtils } from '../utils/const.utils';
import { IController } from '../utils/model.utils';
import { utilsController } from '../utils/index.utils';
class IndexController {
public async index(req: Request, res: Response): Promise<IController> {
try {
let api = req.query.api;
let constUt = new constUtils();
let url = constUt.conf.API_MOCS[`${api}`].url;
await utilsController.utilsCsvConverter(api, url);
} catch (error) {
console.log(error);
}
}
}
export const indexController = new IndexController();
This assumes that the local variables url and api should be returned in a Promise resolving to an object specified the by IController interface:
import { Request, Response } from 'express';
import { constUtils } from '../utils/const.utils';
import { IController } from '../utils/model.utils';
import { utilsController } from '../utils/index.utils';
class IndexController {
public async index(req: Request, res: Response): Promise<IController> {
try {
let api = req.query.api;
let constUt = new constUtils();
let url = constUt.conf.API_MOCS[`${api}`].url;
await utilsController.utilsCsvConverter(api, url);
return {url, api};
} catch (error) {
console.log(error);
}
}
}
export const indexController = new IndexController();
If you want use or apply interface to the class you should have to implement it within the applied class. Below code might give you clear idea.
class IndexController implements csvModel {
url: string;
api: string;
public async index(req: Request, res: Response): Promise<IController> {
try {
this.api = req.query.api;
let constUt = new constUtils();
this.url = constUt.conf.API_MOCS[`${api}`].url;
} catch (error) {
console.log(error);
}
}
}
export const indexController = new IndexController();

Convert stringified JSON to Object using class-transformer

There is a nest.js project, where in the request body we expect an object, one property of this object contains stringified JSON value. The idea is to convert this string to an object, validate it and pass to controller as an object
ValidationPipe set up:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
}),
);
DTO:
#Transform(parseJson, { toClassOnly: true })
#Type(() => AdditionalInfo)
#IsNotEmptyObject()
#ValidateNested()
additionalInfo: AdditionalInfo;
parseJson function
export function parseJson(options: {
key: string;
value: string;
obj: string | Record<string, any>;
}): Record<string, any> {
try {
return JSON.parse(options.value);
} catch (e) {
throw new BadRequestException(`${options.key} contains invalid JSON `);
}
}
For some reason in the controller the parsed value gets lost, and we receive an empty object.
Looks like #Transform works well with primitives only.
Decided to create ParseJsonPipe and use it instead.
Usage (in the controller):
#Body('additionalInfo', new ParseJsonPipe(), new ValidationPipe(AdditionalInfoDto)) additionalInfo: AdditionalInfo,
ParseJsonPipe:
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '#nestjs/common';
#Injectable()
export class ParseJsonPipe implements PipeTransform<string, Record<string, any>> {
transform(value: string, metadata: ArgumentMetadata): Record<string, any> {
const propertyName = metadata.data;
try {
return JSON.parse(value);
} catch (e) {
throw new BadRequestException(`${propertyName} contains invalid JSON `);
}
}
}
ValidationPipe implements PipeTransform from #nestjs/common, transform function looks like that:
async transform(value: any): Promise<any> {
if (!this.metaType) { // AdditionalInfoDto
return value;
}
const object = plainToClass(this.metaType, value);
const errors = await validate(object);
if (errors.length > 0) {
const message = this.getErrorMessages(errors);
throw new BadRequestException({ message });
}
return value;
}

validate nested objects using class-validator in nest.js controller

I want to validate body payload using class-validator in a nest.js controller. My currency.dto.ts file is like this:
import {
IsNotEmpty,
IsString,
ValidateNested,
IsNumber,
IsDefined,
} from 'class-validator';
class Data {
#IsNotEmpty()
#IsString()
type: string;
#IsNotEmpty()
#IsNumber()
id: number;
}
export class CurrencyDTO {
#ValidateNested({ each: true })
#IsDefined()
data: Data[];
}
and in my nest.js controller, I use it like this.
#Post()
#UseGuards(new AuthTokenGuard())
#UsePipes(new ValidationPipe())
addNewCurrency(#Req() req, #Body() data: CurrencyDTO) {
console.log('data', data);
}
my validation pipe class is like this:
import {
PipeTransform,
Injectable,
ArgumentMetadata,
BadRequestException,
HttpException,
HttpStatus,
} from '#nestjs/common';
import { validate, IsInstance } from 'class-validator';
import { plainToClass, Exclude } from 'class-transformer';
#Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, metadata: ArgumentMetadata) {
if (value instanceof Object && this.isEmpty(value)) {
throw new HttpException(
`Validation failed: No Body provided`,
HttpStatus.BAD_REQUEST,
);
}
const { metatype } = metadata;
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errorsList = await validate(object);
if (errorsList.length > 0) {
const errors = [];
for (const error of errorsList) {
const errorsObject = error.constraints;
const { isNotEmpty } = errorsObject;
if (isNotEmpty) {
const parameter = isNotEmpty.split(' ')[0];
errors.push({
title: `The ${parameter} parameter is required.`,
parameter: `${parameter}`,
});
}
}
if (errors.length > 0) {
throw new HttpException({ errors }, HttpStatus.BAD_REQUEST);
}
}
return value;
}
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find(type => metatype === type);
}
private isEmpty(value: any) {
if (Object.keys(value).length > 0) {
return false;
}
return true;
}
}
This validation pipe works fine for all except for nested objects. Any idea what am I doing wrong here?
My body payload is like this:
{
"data": [{
"id": 1,
"type": "a"
}]
}
Try specifying the nested type with #Type:
import { Type } from 'class-transformer';
export class CurrencyDTO {
#ValidateNested({ each: true })
#Type(() => Data)
data: Data[];
}
For a nested type to be validated, it needs to be an instance of a class not just a plain data object. With the #Type decorator you tell class-transformer to instantiate a class for the given property when plainToClass is called in your VaildationPipe.
If you are using the built-in ValidationPipe make sure you have set the option transform: true.
At least in my case, the accepted answer needed some more info. As is, the validation will not run if the key data does not exist on the request. To get full validation try:
#IsDefined()
#IsNotEmptyObject()
#ValidateNested()
#Type(() => CreateOrganizationDto)
#ApiProperty()
organization: CreateOrganizationDto;

Resources