Why Node js joi validation is not working as expected? - node.js

I have implemented joi validation for my project but it's not working for any single field. whatever you pass it's get stored in database it doesn't validate any field even though i did a code for validation
Here is a code for validation
import * as Joi from "joi";
import { Request, Response, NextFunction } from 'express';
import { StatusCodes } from 'http-status-codes';
import { sendError } from "../responseHelper";
import { validationOptions } from "./_index";
export class CountryValidator {
public async createCountryValidator(req: Request, res: Response, next: NextFunction) {
try {
const schema = Joi.object({
id: Joi.number().required(),
name: Joi.string().required(),
code: Joi.string().required(),
status: Joi.number().valid(0, 1).required(),
});
schema.validate(req.body, validationOptions);
next();
} catch (error) {
sendError(res, error, error.code, StatusCodes.INTERNAL_SERVER_ERROR);
}
}
}
And this is my route path
adminRoute.route('/country/create')
.post(countryValidator.createCountryValidator, countryController.createCountry);
And on this path I'm posting below data is which totally wrong as per validation but still it accepts all the data and not throwing any validation error
{
"name":"BR1Z",
"code":100,
"status":"1"
}
Can any one help me to resolve this issue ?

schema.validate returns object with error filed (instead of throwing error).
...
const joiRes = schema.validate(req.body, validationOptions);
if(joiRes.error){
sendError(res, error, error.code, StatusCodes.INTERNAL_SERVER_ERROR);
}
...
see: https://joi.dev/api/?v=17.4.1

Related

Problem trying to show error message with prism and node

I'm doing the backend of a ToDo's project, and for that I'm using the prism orm, the node framework, the typescript language and to test the project I'm using insomnia. i already did all the methods i wanted (create ToDo, get ToDo's, delete ToDo, change ToDo task, change ToDo title) and they are all working, but when i went to test the error message that would occur when i tried to create a ToDo that already exists, in insomia it gives the following message:
Error: Server returned nothing (no headers, no data)
And in VsCode:
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "#<AppError>".] {
code: 'ERR_UNHANDLED_REJECTION'
}
I made the code as follows:
export class AppError {
public readonly message: string;
public readonly statusCode: number;
constructor(message: string, statusCode = 400) {
this.message = message
this.statusCode = statusCode
}
}
import { AppError } from "../../errors/AppError";
import { prisma } from "../../client/prismaClient";
import { CreateToDoType } from "../Types/CreateToDoType";
export class createToDoUseCase {
async execute({title, task}: CreateToDoType) {
const todoAlreadyExists = await prisma.toDo.findUnique({
where: {
title: title
}
})
if (!todoAlreadyExists) {
const newToDo = await prisma.toDo.create({
data: {
title: title,
task: task
}
})
return newToDo
} else {
console.log("01 4")
throw new AppError("Error! ToDo already exists") //aqui está o erro
}
}
}
import {Request, Response} from "express"
import { createToDoUseCase } from "../UseCases/CreateToDoUseCases"
export class createToDoController {
async handle(req: Request, res: Response) {
const { title, task } = req.body
const CreateToDoUseCases = new createToDoUseCase()
const result = await CreateToDoUseCases.execute({title, task}) // aqui está o erro
return res.status(201).json(result)
}
}

Error: Cannot get orders: TypeError: Cannot read properties of undefined (reading 'connect')

I'm trying to build an API with express and pg. Whenever I try to access an endpoint that is related to a query to the database I get the error above.
My handler function is as follows:
import { Request, Response, Router, NextFunction } from 'express';
import { Orders } from '../models/order';
const orders = new Orders;
const index = async (_req: Request, res: Response, next: NextFunction) => {
try {
const ordersList = await orders.index();
res.json(ordersList);
} catch (err) {
next(err)
}
}
const ordersRoute = Router();
ordersRoute.get('/', index);
This handler refers to the following model:
import { Pool } from 'pg';
client = new Pool({
host: POSTGRES_HOST,
database: POSTGRES_DB,
user: POSTGRES_USER,
password: POSTGRES_PASSWORD,
port: parseInt(POSTGRES_PORT as string, 10)
export class Orders {
async index(): Promise<Order[]> {
try {
const conn = await client.connect();
const sql = 'SELECT * FROM orders';
const result = await conn.query(sql);
conn.release();
return result.rows;
} catch (err) {
throw new Error(`Cannot get orders: ${err}`);
}
}
}
anytime I try to access the endpoint I get
Error: Cannot get orders: TypeError: Cannot read properties of
undefined (reading 'connect')
in the console.
any idea how to fix ?
So how things work in your case.
All modules are read by nodejs starting from your index one by one from top to bottom.
In your script, you declare client and then export a class. In that case, your client is setup, then you export a class, and that file is completed, meaning that the only thing that remains is the exported thing. So when you try to use the exported class, you'll not have the same context as in your module.
You need to export the client too and import it where you need that class or to include client definition inside the class

Typescript express typing on Request object

I seem to have an issue with Typescript typings on my Express Request object. The project for now exists out of 2 sub-projects (user-service and a common project which includes reusable Errors and Middlewares)
The common folder is installed as a dependency in the user-service like:
"#myPackage/common": "file:../common",
In there I have a current-user middleware:
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface UserPayload {
id: string;
email: string;
}
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
const currentUser = (
req: Request,
res: Response,
next: NextFunction,
) => {
if (!req.session?.jwt) {
return next();
}
try {
const payload = jwt.verify(
req.session.jwt,
process.env.JWT_KEY!,
) as UserPayload;
req.currentUser = payload;
} catch (err) {
console.error(err);
}
return next();
};
export default currentUser;
with a declared global for the currentUser property on the Request object.
In my user-service project I have the following route
import express, { Request, Response } from 'express';
import { Middlewares } from '#myPackage/common';
const router = express.Router();
router.get('/api/users/currentuser', Middlewares.currentUser, (
req: Request,
res: Response,
) => {
res.send({ currentUser: req.currentUser || null });
});
export default router;
On req.currentUser I get the following error message:
Property 'currentUser' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
Shouldn't the package typings automatically be taken over in the code in which you import it? I hope I made myself clear on what the problem is :)
I've also always had trouble declaring a global namespace to attach types to express's request object. Found myself a solution using "declare module" instead of "declare global". So instead of
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
maybe give the following approach a try:
declare module "express-serve-static-core" {
interface Request {
currentUser?: UserPayload;
}
}
Normally your currentUser property should also be available in other files with this approach, but you can of course export the manipulated Request interface if not.
Note that in most cases you will need to reference the "express-serve-static-core" module as this is where the Request interface is declared.

TypeError: Router.use() requires a middleware function but got a undefined with two middlewares involved

I have to run test on my ts-node app and i am having this problem when running the test, for context the app has routes that has to pass two middlewares, one of authorization and another of valid request i have this config route
const router = express.Router();
router.use('/cars', requiredAuthorization, requiredHeadersValidator, cars);
router.use('/healthcheck', healthcheck());
export default router;
and i get this error while running the test:
● Test suite failed to run
TypeError: Router.use() requires a middleware function but got a undefined
8 | const cars = require('../routes/cars');
9 |
> 10 | router.use('/cars', requiredAuthorization, requiredHeadersValidator, cars);
| ^
11 | router.use('/healthcheck', healthcheck());
12 |
13 | export default router;
at Function.use (node_modules/express/lib/router/index.js:458:13)
at Object.<anonymous> (src/web-server/routes/index.ts:10:8)
at Object.<anonymous> (src/web-server/index.ts:7:1)
at Object.<anonymous> (src/web-server/middlewares/required-authorization.ts:3:1)
at Object.<anonymous> (src/web-server/middlewares/__tests__/required-authorization.test.ts:4:1)
i have tried almost every answer here and in other parts but none of them seems to help (in every file of routing there is a export default router line)
Update
The middlewares are:
import { Request, Response, NextFunction } from 'express';
import { StatusCodes } from 'http-status-codes';
import { autho } from '../index';
const requiredAuthorization = async (req: Request, res: Response, next: NextFunction) => {
if (!req.headers.authorization) {
return res.status(StatusCodes.FORBIDDEN)
.json({
errors: [{
status: StatusCodes.FORBIDDEN, detail: 'Unauthorized'
}]
});
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
idToken = req.headers.authorization.split('Bearer ')[1];
} else {
return res.status(StatusCodes.FORBIDDEN)
.json({
errors: [{
status: StatusCodes.FORBIDDEN, detail: 'Unauthorized'
}]
});
}
try {
await autho.auth().verifyIdToken(idToken);
next();
return;
} catch (error) {
return res.status(StatusCodes.FORBIDDEN)
.json({
errors: [{
status: StatusCodes.FORBIDDEN, detail: 'Unauthorized'
}]
});
}
};
export default requiredAuthorization;
and:
import { Request, Response, NextFunction } from 'express';
import { ReasonPhrases, StatusCodes } from 'http-status-codes';
const requiredHeadersValidator = (req: Request, res: Response, next: NextFunction) => {
if (req.header('Content-Type') !== 'application/vnd.api+json') {
return res.status(StatusCodes.UNSUPPORTED_MEDIA_TYPE)
.json({
errors: [{
status: StatusCodes.UNSUPPORTED_MEDIA_TYPE, detail: ReasonPhrases.UNSUPPORTED_MEDIA_TYPE
}]
});
}
if (req.header('Accept') !== 'application/vnd.api+json') {
return res.status(StatusCodes.NOT_ACCEPTABLE)
.json({
errors: [{
status: StatusCodes.NOT_ACCEPTABLE, detail: ReasonPhrases.NOT_ACCEPTABLE
}]
});
}
next();
};
export default requiredHeadersValidator;
the imports:
import * as express from 'express';
import * as healthcheck from 'express-healthcheck';
import requiredAuthorization from '../middlewares/required-authorization';
import requiredHeadersValidator from '../middlewares/required-headers-validator';
import cars from './cars';
the cars.ts file:
import * as express from 'express';
import { BrandController } from '../controllers/brand-controller';
import { ModelController } from '../controllers/model-controller';
import { VehicleController } from '../controllers/vehicle-controller';
const router = express.Router();
// brands
router.get('/brands', BrandController.findAll);
router.get('/brands/:id', BrandController.findById);
router.get('/brands/:id/models', BrandController.findModelsByBrandId);
router.post('/brands', BrandController.create);
router.patch('/brands/:id', BrandController.updateBrandById);
router.delete('/brands/:id', BrandController.deleteBrandById);
// models
router.get('/models', ModelController.findAll);
router.get('/models/:id', ModelController.findById);
router.get('/models/:id/brands', ModelController.findBrandByModelId);
router.post('/models', ModelController.create);
router.patch('/models/:id', ModelController.updateModelById);
router.delete('/models/:id', ModelController.deleteModelById);
// vehicles
router.get('/:plate', VehicleController.findByPlate);
export default router;
i got this resolved with a coworker by accident, his IDE autocompleted the file with the imports in different order, to be more precise the requiredAuthorization was the last import in the router and in the test and there the test passed without problem.
import * as express from 'express';
import * as healthcheck from 'express-healthcheck';
import requiredHeadersValidator from '../middlewares/required-headers-validator';
import cars from './cars';
import requiredAuthorization from '../middlewares/required-authorization';
Like this the error dissapeared (the tests were made with Jest), i suppose this could help to someone in the future to considerate the import order in case of some strange behaviour
P.D: if someone has some idea of why the order affected the behavior of this, i would be happy to know it

Class-Validator node.js provide custom error

I have a custom validator constraint and annotation created for checking whether entity with given property already exists or not, here is the code
import { Inject, Injectable } from '#nestjs/common';
import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint } from 'class-validator';
import { ValidatorConstraintInterface } from 'class-validator/types/validation/ValidatorConstraintInterface';
import { Connection } from 'typeorm';
import { InjectConnection } from '#nestjs/typeorm';
#ValidatorConstraint({ async: true })
#Injectable()
export class EntityExistsConstraint implements ValidatorConstraintInterface {
constructor(#InjectConnection() private dbConnection: Connection) {
}
defaultMessage(validationArguments?: ValidationArguments): string {
return `${validationArguments.constraints[0].name} with ${validationArguments.property} already exists`;
}
validate(value: any, validationArguments?: ValidationArguments): Promise<boolean> | boolean {
const repoName = validationArguments.constraints[0];
const property = validationArguments.property;
const repository = this.dbConnection.getRepository(repoName);
return repository.findOne({ [property]: value }).then(result => {
return !result;
});
}
}
export function EntityExists(repoName, validationOptions?: ValidationOptions) {
return function(object: any, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [repoName],
validator: EntityExistsConstraint,
});
};
}
Everything works fine, but I receive this response when the validation fails
{
"statusCode": 400,
"message": [
"User with email already exists"
],
"error": "Bad Request"
}
I want the error be Conflict Exception=> statusCode 409, how can I achieve this?
class-validator doesn't do anything with the http codes. It only validates and returns a list of errors or an empty array.
What you need to do is to check framework you use, I assume it's nestjs or routing-controllers.
In the case of routing-controllers you need to write own after middleware and disable default middleware (it converts validation errors to 400 bad requests).
More info is here: https://github.com/typestack/routing-controllers#error-handlers
In the case of nestjs - the same steps.
More info you can find here: https://docs.nestjs.com/exception-filters#catch-everything

Resources