Express error handler not catching an error properly - express

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');
});

Related

Shopify node.js and react.js plugin with vite.js not working

I've created a plugin in shopify using node.js & vite.js.
shopify app create node
After running using npm run dev, it generates a url like this: https://b136-0000-7400-56-bc78-5000-178b-d6f3-6000.ngrok.io/login?shop=shopname.myshopify.com
When I open this link, it start reloading infinite with error
This is my index.js:
import { resolve } from "path";
import express from "express";
import cookieParser from "cookie-parser";
import { Shopify, LATEST_API_VERSION } from "#shopify/shopify-api";
import "dotenv/config";
import applyAuthMiddleware from "./middleware/auth.js";
import verifyRequest from "./middleware/verify-request.js";
const USE_ONLINE_TOKENS = true;
const TOP_LEVEL_OAUTH_COOKIE = "shopify_top_level_oauth";
const PORT = parseInt(process.env.PORT || "8081", 10);
const isTest = process.env.NODE_ENV === "test" || !!process.env.VITE_TEST_BUILD;
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
API_VERSION: LATEST_API_VERSION,
IS_EMBEDDED_APP: true,
// This should be replaced with your preferred storage strategy
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};
Shopify.Webhooks.Registry.addHandler("APP_UNINSTALLED", {
path: "/webhooks",
webhookHandler: async (topic, shop, body) => {
delete ACTIVE_SHOPIFY_SHOPS[shop];
},
});
// export for test use only
export async function createServer(
root = process.cwd(),
isProd = process.env.NODE_ENV === "production"
) {
const app = express();
app.set("top-level-oauth-cookie", TOP_LEVEL_OAUTH_COOKIE);
app.set("active-shopify-shops", ACTIVE_SHOPIFY_SHOPS);
app.set("use-online-tokens", USE_ONLINE_TOKENS);
app.use(cookieParser(Shopify.Context.API_SECRET_KEY));
applyAuthMiddleware(app);
app.post("/webhooks", async (req, res) => {
try {
await Shopify.Webhooks.Registry.process(req, res);
console.log(`Webhook processed, returned status code 200`);
} catch (error) {
console.log(`Failed to process webhook: ${error}`);
if (!res.headersSent) {
res.status(500).send(error.message);
}
}
});
app.get("/products-count", verifyRequest(app), async (req, res) => {
const session = await Shopify.Utils.loadCurrentSession(
req,
res,
app.get("use-online-tokens")
);
const { Product } = await import(
`#shopify/shopify-api/dist/rest-resources/${Shopify.Context.API_VERSION}/index.js`
);
const countData = await Product.count({ session });
res.status(200).send(countData);
});
app.post("/graphql", verifyRequest(app), async (req, res) => {
try {
const response = await Shopify.Utils.graphqlProxy(req, res);
res.status(200).send(response.body);
} catch (error) {
res.status(500).send(error.message);
}
});
app.use(express.json());
app.use((req, res, next) => {
const shop = req.query.shop;
if (Shopify.Context.IS_EMBEDDED_APP && shop) {
res.setHeader(
"Content-Security-Policy",
`frame-ancestors https://${shop} https://admin.shopify.com;`
);
} else {
res.setHeader("Content-Security-Policy", `frame-ancestors 'none';`);
}
next();
});
app.use("/*", (req, res, next) => {
const { shop } = req.query;
// Detect whether we need to reinstall the app, any request from Shopify will
// include a shop in the query parameters.
if (app.get("active-shopify-shops")[shop] === undefined && shop) {
res.redirect(`/auth?${new URLSearchParams(req.query).toString()}`);
} else {
next();
}
});
/**
* #type {import('vite').ViteDevServer}
*/
let vite;
if (!isProd) {
vite = await import("vite").then(({ createServer }) =>
createServer({
root,
logLevel: isTest ? "error" : "info",
server: {
port: PORT,
hmr: {
protocol: "ws",
host: "localhost",
port: 64999,
clientPort: 64999,
},
middlewareMode: "html",
},
})
);
app.use(vite.middlewares);
} else {
const compression = await import("compression").then(
({ default: fn }) => fn
);
const serveStatic = await import("serve-static").then(
({ default: fn }) => fn
);
const fs = await import("fs");
app.use(compression());
app.use(serveStatic(resolve("dist/client")));
app.use("/*", (req, res, next) => {
// Client-side routing will pick up on the correct route to render, so we always render the index here
res
.status(200)
.set("Content-Type", "text/html")
.send(fs.readFileSync(`${process.cwd()}/dist/client/index.html`));
});
}
return { app, vite };
}
if (!isTest) {
createServer().then(({ app }) => app.listen(PORT));
}
The app is installing fine but it's getting refreshed again and again due to failed connection to ws (as mentioned in the screenshot). I tried a few things around changing the settings of the HMR but doesn't seem to be connecting.

ExpressJS mixing middleware arguments with custom arguments

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?

Error handling middleware thenables nodejs express

So I have this error handler middleware
class ErrorHandler extends Error {
constructor(statusCode, message) {
super();
this.statusCode = statusCode;
this.message = message;
}
}
const handleError = (err, res) => {
const { statusCode, message } = err;
res.status(statusCode).json({
status: "error",
statusCode,
message: "resource not found"
});
};
module.exports = {
ErrorHandler,
handleError
}
calling it in index.js
app.use((err, req, res, next) => {
handleError(err, res);
})
And I want to use it in all my methods, but I cant figure it out how to use it with catch. it does not seem very clear for me. For the 404 error works just fine, but If I'm trying to throw a 500 error, e.g. a ReferenceError I dont know how to use my function.
exports.getAll = function(req, res, next) {
User.find({})
.exec()
.then(users => {
if (users.length === 0) {
throw new ErrorHandler(404, "No users found");
} else {
res.json(users);
next;
}
})
.catch(error => {
res.status(500).send({
message: "Error finding users",
error: error
})
})
};
in .catch I want to use my error handler like I did in .then
in catch block, you can use next to go handle error like with next(err)
.catch(err=>{
let error = new ErrorHandler(500,"Error finding users");
next(error);
})

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!

Error handling in Express using Axios for API call

I am trying to set up a basic express app to get some API data using axios. I want to do things the right way but I am a bit lost with error handling. Ideally, if there is an error I want to communicate it to users which I could do if the API call was within it the route. But how do you do it if it's a separate function?
axios call function using async:
const getForm = async () => {
try {
const config = {
method: 'get',
url: 'https://api.something.org/niceform'
}
}
const response = await axios(config)
return response
} catch (error) {
return error.message
}
}
express route:
app.get('/niceform', async (req, res) => {
try {
const data = await getForm()
res.send(data)
} catch (error) {
???
}
})
If I understand it correctly the getForm() function will return either the response or the error and then the route will send whatever comes back. But then what does the route's catch block do and how should I use it?
Is this setup considered to be a good practice?
Any advice would be appreciated, I am still learning.
The catch block can be removed from the getForm function. An error will be caught anyways in the get route.
const getForm = async () => {
const config = {
method: 'get',
url: 'https://api.something.org/niceform'
};
const response = await axios(config);
return response;
}
Or the error can be caught inside getForm, in order to do something in that catch block, and be thrown:
const getForm = async () => {
const config = {
method: 'get',
url: 'https://api.something.org/niceform'
};
try {
const response = await axios(config);
return response;
} catch (err) {
// log the error
// add extra information to the error
// else
// (see the attached answer)
throw err;
}
}
Consequently, in the catch block in the get route, an error can be responded:
app.get('/niceform', async (req, res) => {
try {
const data = await getForm();
res.send(data);
} catch (error) {
res.error(error);
}
})
Reference:
https://stackoverflow.com/a/42171508/3563737
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw