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

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

Related

how to prevent file upload when body validation fails in nestjs

I have the multipart form to be validated before file upload in nestjs application. the thing is that I don't want the file to be uploaded if validation of body fails.
here is how I wrote the code for.
// User controller method for create user with upload image
#Post()
#UseInterceptors(FileInterceptor('image'))
create(
#Body() userInput: CreateUserDto,
#UploadedFile(
new ParseFilePipe({
validators: [
// some validator here
]
})
) image: Express.Multer.File,
) {
return this.userService.create({ ...userInput, image: image.path });
}
Tried so many ways to turn around this issue, but didn't reach to any solution
Interceptors run before pipes do, so there's no way to make the saving of the file not happen unless you manage that yourself in your service. However, another option could be a custom exception filter that unlinks the file on error so that you don't have to worry about it post-upload
This is how I created the whole filter
import { isArray } from 'lodash';
import {
ExceptionFilter,
Catch,
ArgumentsHost,
BadRequestException,
} from '#nestjs/common';
import { Request, Response } from 'express';
import * as fs from 'fs';
#Catch(BadRequestException)
export class DeleteFileOnErrorFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const getFiles = (files: Express.Multer.File[] | unknown | undefined) => {
if (!files) return [];
if (isArray(files)) return files;
return Object.values(files);
};
const filePaths = getFiles(request.files);
for (const file of filePaths) {
fs.unlink(file.path, (err) => {
if (err) {
console.error(err);
return err;
}
});
}
response.status(status).json(exception.getResponse());
}
}

Mocking (actually, spying on) Sentry in a jest test in a Node.js project

I'm trying to mock (well really, spyOn) Sentry with jest in a Node.js project and it is not working.
The /debug-sentry route just throws an exception, and interactively it correctly sends an exception to Sentry. When I try to mock it like this, though, the expect...toHaveBeenCalled fails. Am I not mocking this right? Suggestions?
import * as Sentry from '#sentry/node';
...
describe('failure modes', () => {
it('send an exception to Sentry if exception thrown', async () => {
const mock = jest.spyOn(Sentry, 'captureException');
const res = await request(server).get('/debug-sentry').expect('Content-Type', /text/).expect(500);
expect(mock).toHaveBeenCalled(); // This does not succeed, not sure why...
...
The code under test (I just added the /debug-sentry route to my server temporarily for testing):
import express from 'express';
import { Express } from 'express-serve-static-core';
import { requestLogger } from '#/utils/request_logger';
import {
initialize_exception_reporting,
initialize_error_reporting,
send_exception_message
} from '#/utils/exception_reporting';
export async function createServer(): Promise<Express> {
const server = express();
initialize_exception_reporting(server);
server.use(requestLogger);
// error customization, if request is invalid
// eslint-disable-next-line #typescript-eslint/no-explicit-any,#typescript-eslint/no-unused-vars
server.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
res.status(err.status).json({
error: {
type: 'request_validation',
message: err.message,
errors: err.errors,
},
});
});
server.get("/debug-sentry", (req, res) => {
throw new Error("My first Sentry error!");
});
initialize_error_reporting(server);
return server;
}
and the code setting up Sentry:
import Express from 'express';
import * as Sentry from "#sentry/node";
import * as Tracing from "#sentry/tracing";
export function initialize_exception_reporting(server: Express.Express) {
Sentry.init({
integrations: [],
});
server.use(Sentry.Handlers.requestHandler());
}
export function initialize_error_reporting(server: Express.Express) {
server.use(Sentry.Handlers.errorHandler());
}
export function send_exception_message(message: string) {
Sentry.captureMessage(message);
}

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.

Why Node js joi validation is not working as expected?

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

Using Service in Express Router

I am pretty new in the NodeJS but I would like to learn something new. I came from .NET fancy dependency injection, inversion of controll, microservice shiny world so I am trying write some service in TypeScript based on my previous experiences.
I am using express and express router to create some api. I have some methods in router which handles api calls and I want to use some kind of service object for data retrieving and manipulation.
I inject the service into the router using constructor injection but if I want to use my service it throws an error:
TypeError: Cannot read property 'layoutService' of undefined
I understood that the methods were called withouth context so I added .bind(this) to the each method regsitration and it works, but I dont know if it is the best way how to do it.
Does anyone have a better idea?
simplified server.ts
import express, { Router } from "express";
// inversion of controll
import container from "./ioc";
import { TYPE } from "./constants";
import IMyService from "./abstract/IMyService";
// import routers
import MyRouter from "./api/MyRouter";
app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const router: Router = express.Router();
const myRouter: MyRouter = new MyRouter(container.get<IMyService>(TYPE.IMyService));
app.use("/", router);
app.use("/api/v1/layouts", layoutRouter.router);
MyRouter.ts
import IMyService from "./abstract/IMyService";
import { Router, Request, Response } from "express";
import { inject } from "inversify";
import { TYPE } from "../constants";
export default class MyRouter {
public readonly router: Router;
private readonly myService: IMyService;
constructor(
#inject(TYPE.IMyService) myService: IMyService
) {
this.myService = myService;
this.router = Router();
this.routes();
}
public GetAll(req: Request, res: Response): void {
this.myService.getAll()
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
public GetOne(req: Request, res: Response): void {
const id: string = req.params.id;
this.myService.getOne(new ObjectID(id))
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
routes(): void {
this.router
.get("/", this.GetAll)
.get("/:id", this.GetOne);
}
}
If you define your function with the arrow syntax (ES6), it will "bind" the context to it automatically and you won't need to bind them. But it will depends on your use case (ou might need to bind a different context)

Resources