Let's say that I have an API enpoint /register
that registers a new user in my application. The function is asynchronous, because I want to use an asynchronous function inside of an AuthController when hashing the password like this:
authRouter.post('/register', async (req: Request, res: Response, next: NextFunction) => {
const { username, password } = req.body;
const hashedPassword = await AuthController.hashPassword(password, next);
// do something else
Then, inside my AuthController I test error handling by throwing an error like this:
const hashPassword = async (password: string, next: NextFunction): Promise<void> => {
try {
throw new Error('test error');
//return await bcrypt.hash(password, 10);
} catch(err) {
next(err);
}
}
However, when I am testing the API the server crashes completely, here's the log:
[1] Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
[1] at new NodeError (node:internal/errors:400:5)
[1] at ServerResponse.setHeader (node:_http_outgoing:663:11)
[1] at ServerResponse.header (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\node_modules\express\lib\response.js:794:10)
[1] at ServerResponse.send (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\node_modules\express\lib\response.js:174:12)
[1] at ServerResponse.json (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\node_modules\express\lib\response.js:278:15)
[1] at ServerResponse.send (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\node_modules\express\lib\response.js:162:21)
[1] at C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\dist\src\routes\auth\auth.js:72:25
[1] at Generator.next (<anonymous>)
[1] at fulfilled (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\dist\src\routes\auth\auth.js:28:58)
[1] at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
[1] code: 'ERR_HTTP_HEADERS_SENT'
[1] }
[1]
[1] Node.js v18.13.0
I assume what causes the error is the fact that the function does not stop at catch, but instead continues to the '// do something else' part, where at the end it actually tries to send a response. I could probably place the try {} catch(err){} block in the scope of the router function but I don't want that for two reasons:
- In my opinion it's bad design - it's easier to read code when each try {} catch{} block is situated in a seperate function
- I would have to place one big try {} catch{} block for the whole function, since I can't read the variables from outside of try{} catch{} scopes
EDIT: Here's the '// do something else'' code block for clarity
try {
await User.create({ username, password: hashedPassword ?? 's' });
res.send({ username, password, hashedPassword });
} catch (error) {
console.error('Something went wrong when adding a new user', error);
res.status(400).send({
message: 'User already exists in the database',
});
}