ExpressJS mixing middleware arguments with custom arguments - express

I'm writing a function to create errors and I want to use express' next() function. I have seen working examples using a nested function but my code isn't reached. This is what I have:
/middleware.js
import { errorCreator } from './helper.js';
const middleWare = async (req, res, next) => {
if (!req.headers.header) {
errorCreator('Not Authorized', 401);
}
// More async code here
};
and
/helpers.js
export const errorCreator = (message, statusCode) => {
// this is reached <-----------------
return (req, res, next) => {
// this is not reached <---------------
const error = new Error(message);
error.status = statusCode;
throw error;
};
};
Why do I not reach the nested function?

Related

Express error handler not catching an error properly

I'm working on a small express project to create and check keys. I have 2 custom errors that I throw when needed, InvalidKeyError and NotFoundError. I am using an error handling middleware:
import { Request, Response, NextFunction } from 'express';
import { CustomError } from '../errors/custom-error';
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
if (err instanceof CustomError) {
console.log(err);
return res.status(err.statusCode).send({ errors: err.serialiseErrors() });
}
res.status(400).send({
errors: [{ message: 'Something went wrong' }]
});
};
When NotFoundError is called I get the following JSON (which is what I want):
{
"errors": [
{
"message": "Page does not exist"
}
]
}
But when I throw InvalidKeyError my app crashes, but it does show me the error message in the console. I want it to throw like my NotFoundError.
My InvalidKeyError and NotFoundError both extend my abstract class CustomError. I throw a NotFoundError in my index.ts file when a route that does not exist is being requested. I call my InvalidKeyError from a router that my index.ts uses.
This is index.ts:
import express from 'express';
import { json } from 'body-parser';
import mongoose from 'mongoose';
import { createAPIKeyRouter } from './routes/create-api-key';
import { checkAPIKeyRouter } from './routes/check-api-key';
import { errorHandler } from './middlewares/error-handler';
import { NotFoundError } from './errors/not-found-error';
const app = express();
app.use(json());
app.use(createAPIKeyRouter);
app.use(checkAPIKeyRouter);
app.all('*', (req, res) => {
throw new NotFoundError();
});
app.use(errorHandler);
const start = async () => {
try {
await mongoose.connect("mongodb://localhost:27017/coupons");
console.log("Connected to MongoDb");
} catch (err) {
console.error(err);
}
app.listen(4000, () => {
console.log("Listening on port 4000");
});
};
start();
and the code for the router that my app uses:
import express from 'express';
import { InvalidKeyError } from '../errors/invalid-api-key-error';
import { APIKey } from '../models/api-key-model';
import { Hash } from '../services/hash';
const router = express.Router();
router.get('/api/checkkey', async (req, res) => {
const suppliedKey = req.get('key');
const suppliedSecret = req.get('secret');
if (!suppliedKey || !suppliedSecret)
throw new InvalidKeyError('Key or secret not passed');
const storedAPIKey = await APIKey.findOne({ key: suppliedKey });
if (!storedAPIKey)
throw new InvalidKeyError('Key does not exist');
if (!Hash.compareKeys(suppliedKey, suppliedSecret, storedAPIKey.salt))
throw new InvalidKeyError('Key and secret do not correspond')
res.send('Hi there');
});
export { router as checkAPIKeyRouter };
You're declaring your route handler async and then throwing inside of it. That means your route handler will return a promise that gets rejected when you throw.
BUT, Express doesn't pay any attention at all to the promise that your route handler returns - in fact, Express doesn't pay any attention to any value returned by your route handler. So, when you throw and cause that returned promise to get rejected, nobody is listening for that rejection.
You will need to either use your own try/catch inside the route handler to catch your throws or you will need to wrap your route handler in a utility function that catches rejections from the promise you're returning.
Here's using your own try/catch:
router.get('/api/checkkey', async (req, res, next) => {
try {
const suppliedKey = req.get('key');
const suppliedSecret = req.get('secret');
if (!suppliedKey || !suppliedSecret)
throw new InvalidKeyError('Key or secret not passed');
const storedAPIKey = await APIKey.findOne({ key: suppliedKey });
if (!storedAPIKey)
throw new InvalidKeyError('Key does not exist');
if (!Hash.compareKeys(suppliedKey, suppliedSecret, storedAPIKey.salt))
throw new InvalidKeyError('Key and secret do not correspond')
res.send('Hi there');
} catch(err) {
next(err);
}
});
And, here's using your own wrapper function (which you can share with many methods like this:
function asyncHandler(fn) {
return async function(req, res, next) {
try {
await fn(req, res, next);
} catch(err) {
next(err);
}
}
}
router.get('/api/checkkey', asyncHandler(async (req, res) => {
const suppliedKey = req.get('key');
const suppliedSecret = req.get('secret');
if (!suppliedKey || !suppliedSecret)
throw new InvalidKeyError('Key or secret not passed');
const storedAPIKey = await APIKey.findOne({ key: suppliedKey });
if (!storedAPIKey)
throw new InvalidKeyError('Key does not exist');
if (!Hash.compareKeys(suppliedKey, suppliedSecret, storedAPIKey.salt))
throw new InvalidKeyError('Key and secret do not correspond')
res.send('Hi there');
}));
And, here's how you could add methods to Express that are promise-aware: Async/Await in Express Middleware so you can just do this:
// note this is router.getP()
router.getP('/api/checkkey', async (req, res) => {
const suppliedKey = req.get('key');
const suppliedSecret = req.get('secret');
if (!suppliedKey || !suppliedSecret)
throw new InvalidKeyError('Key or secret not passed');
const storedAPIKey = await APIKey.findOne({ key: suppliedKey });
if (!storedAPIKey)
throw new InvalidKeyError('Key does not exist');
if (!Hash.compareKeys(suppliedKey, suppliedSecret, storedAPIKey.salt))
throw new InvalidKeyError('Key and secret do not correspond')
res.send('Hi there');
});

Cleaner way to write api route handlers in NextJS

Currently most of the api route handlers are in the following shape(api/test.js):
export default function handler(req, res) {
if (req.method === 'POST') {
// Process a POST request
} else {
// Handle any other HTTP method
}
}
where we constantly compare req.method with ifs
Is there a way to write it similar to ExpressJS:
app.get(...)
import nextConnect from 'next-connect';
const handler = nextConnect();
//handler.use(middleware);
handler.get(async (req, res) => {
...your code
})
...
handler.post(async (req, res) => {
...your code
})
...
So in theory you can have /api/product where you have .get .post .delete (etc) in 1 api route
Clean solution (/api/product.js)
const handler = async (req, res) => {
try {
}
catch(e){
}
}

express-jwt - Show permission for debug

It's possible show in error middleware what permissions are needed?
//User don't have this permission
app.post("/", guard.check(permissions.$("admin")), (req, res) => {
return new Area(req.body)
.save()
.then(area => {
///....
})
.catch(err => next(err))
})
Error middleware
I would like to show permission here in a console.log()
app.use(function (err, req, res, next) {
if (err.code === "invalid_token") {
return res.status(401).send("...")
}
if (err.code === "credentials_required") {
return res.status(401).send("...")
}
//...
})
You can wrap guard.check middleware into another middleware, and you can put the required permissions to req object, and then you can get it in the error handler middleware.
const customGuard = (permissions) => { // middleware factory
return (req, res, next) => { // return a middleware function
req.requiredPermissions = permissions; // store `permissions` in `req.requiredPermissions`
guard.check(permissions)(req, res, next); // check your permissions with express-jwt-permissions's guard
}
}
Usage, replace
app.post("/", guard.check(permissions.$("admin"))
...
by
app.post("/", customGuard(permissions.$("admin"))
...
Then, in your Error middleware
app.use(function (err, req, res, next) {
console.log("Required permissions: ", req.requiredPermissions); // here
if (err.code === "invalid_token") {
return res.status(401).send("...")
}
if (err.code === "credentials_required") {
return res.status(401).send("...")
}
//...
})

Axios interceptor use express req object

I have an express route in which I send a header from the front end, in this route I'm making a GET request using axios. I created an interceptor with axios, but I would like to be able to read the req object from the activated route in order to add the header to the axios GET call.
// Example Interceptor
axios.interceptors.request.use(
config => {
// How to get req.headers from the route here?
return config;
},
error => {
return Promise.reject(error);
}
);
// Exemple GET route
router.get('/get', async (req, res, next) => {
try {
const { data } = await axios.get('https://kjhf.fsadjhfewq.....');
} catch (error) {
console.log(error)
}
res.status(200).json({});
});
Is it possible to do this?
So I think the way to do this is to use a middleware to set the headers, and pass on the axios instance
// apiSetHeader.js middleware
exports.default = (req, res, next) => {
req.CustomAxios = axios.create({
headers: { 'HeaderForTheApi': req.headers.apiHeader'}
})
next()
}
And then use that in your route
// Exemple GET route
router.get('/get', apiSetHeaderMiddleware, async (req, res, next) => {
try {
const { data } = await req.CustomAxios.get('https://kjhf.fsadjhfewq.....');
} catch (error) {
console.log(error)
}
res.status(200).json({});
});
Hope this helps!

call app.get inside response.render

How can I call another express route inside response.render. Following is my code snippet. I would like to render performance.jade when /pages/performance is requested and populate the jade with data returned from /api/notifications
module.exports = function(app){
app.get('/pages/performance', function(req, res){
res.render("performance", {results: app.get("/api/notifications", function (request, response) {return response.body;}), title: "Performance"});
});
};
/api/notifications will return json data which is then used in jade as follows:
block pageContent
for result in results
p #{result.message}
Make a function that gets the notifications and passes them to a callback. Then use that function in both routes. You can code that either as a pure function or as a connect middleware.
Pure Function
function loadNotifications(callback) {
database.getNotificiations(callback)
}
app.get('/api/notifications', function (req, res) {
loadNotifications(function (error, results) {
if (error) { return res.status(500).send(error);
res.send(results);
}
});
app.get('/pages/performance', function (req, res) {
loadNotifications(function (error, results) {
if (error) { return res.status(500).send(error);
res.render('performance', {results: results});
});
});
Middleware
function loadNotifications(req, res, next) {
database.getNotificiations(function (error, results) {
if (error) { return next(error);}
req.results = results;
next();
});
}
app.get('/api/notifications', loadNotifications, function (req, res) {
res.send(req.results);
});
app.get('/pages/performance', loadNotifications, function (req, res) {
res.render('performance', {results: req.results});
});