Geek Logbook

Tech sea log book

Handling the “ERR_HTTP_HEADERS_SENT” Error in Node.js Express

When building REST APIs with Node.js and Express, one common error that developers encounter is ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client. This error can be frustrating to debug, especially if you’re not sure what’s causing it. In this post, I’ll explain the root cause of this error and walk through an example of how to resolve it.

Understanding the Error

The error message "Cannot set headers after they are sent to the client" typically occurs when your Express application tries to send more than one response to a single request. Once the server sends an HTTP response to the client, headers are set, and the connection is closed. Any attempt to modify the headers or send another response will throw this error.

Here’s what the error might look like in a real application:

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (node:_http_outgoing:659:11)
    at ServerResponse.header (/path/to/node_modules/express/lib/response.js:794:10)
    at ServerResponse.send (/path/to/node_modules/express/lib/response.js:174:12)
    at ServerResponse.json (/path/to/node_modules/express/lib/response.js:278:15)
    at /path/to/controllers/auth.js:118:21
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

The Scenario

Suppose you’re building an authentication system and have a forgotPassword controller that handles password reset requests. The function is supposed to:

  1. Find the user by email.
  2. Generate a password reset token.
  3. Send an email with the reset link.
  4. Return a success message to the client.

However, if there’s a mistake in how the responses are handled, this error can occur.

Example Code

Here’s an example of the forgotPassword function that causes the error:

exports.forgotPassword = asyncHandler(async (req, res, next) => {
    const user = await User.findOne({ email: req.body.email });

    if (!user) {
        return next(new ErrorResponse('There is no user with that email', 404));
    }

    const resetToken = user.getResetPasswordToken();

    await user.save({ validateBeforeSave: false });

    const resetUrl = `${req.protocol}://${req.get('host')}/api/v1/resetpassword/${resetToken}`;

    const message = `You are receiving this email because you (or someone else) has requested the reset of a password. Please make a PUT request to: \n\n ${resetUrl}`;

    try {
        await sendEmail({
            email: user.email,
            subject: 'Password reset token',
            message
        });
        res.status(200).json({ success: true, data: 'Email sent' });
    } catch (err) {
        console.log(err);
        user.resetPasswordExpire = undefined;
        user.resetPasswordToken = undefined;

        await user.save({ validateBeforeSave: false });

        return next(new ErrorResponse('Email could not be sent', 500));
    }

    // This second response causes the error
    res.status(200).json({
        success: true,
        data: user
    });
});

What’s Happening?

The issue here is that we’re attempting to send two responses:

First response is sent when the email is successfully sent:

    res.status(200).json({ success: true, data: 'Email sent' });

    Second response is sent after the try-catch block:

    res.status(200).json({ success: true, data: user });

    The second response causes the ERR_HTTP_HEADERS_SENT error because the headers and body were already sent with the first response. You can’t send another response for the same request.

    Solution: Ensuring Only One Response is Sent

    To fix this, you need to ensure that only one response is sent, either after successfully sending the email or in case of an error.

    Here’s the corrected version of the forgotPassword function:

    exports.forgotPassword = asyncHandler(async (req, res, next) => {
        const user = await User.findOne({ email: req.body.email });
    
        if (!user) {
            return next(new ErrorResponse('There is no user with that email', 404));
        }
    
        const resetToken = user.getResetPasswordToken();
    
        await user.save({ validateBeforeSave: false });
    
        const resetUrl = `${req.protocol}://${req.get('host')}/api/v1/resetpassword/${resetToken}`;
    
        const message = `You are receiving this email because you (or someone else) has requested the reset of a password. Please make a PUT request to: \n\n ${resetUrl}`;
    
        try {
            await sendEmail({
                email: user.email,
                subject: 'Password reset token',
                message
            });
            res.status(200).json({ success: true, data: 'Email sent' });
        } catch (err) {
            console.log(err);
            user.resetPasswordExpire = undefined;
            user.resetPasswordToken = undefined;
    
            await user.save({ validateBeforeSave: false });
    
            return next(new ErrorResponse('Email could not be sent', 500));
        }
    });

    Explanation of the Fix

    In this version of the function, the second response res.status(200).json({ success: true, data: user }) has been removed. This ensures that the response is only sent once, either after the email is sent successfully or after an error is caught.

    Conclusion

    The ERR_HTTP_HEADERS_SENT error is a common mistake in Node.js when handling responses. It occurs when the server tries to send multiple responses for the same request. To avoid this, you should ensure that each request is only responded to once. By understanding this error and making sure that responses are correctly handled, you can avoid unnecessary bugs and improve the stability of your Express application.

    Key Takeaways:

    • Always send one response per request.
    • In try/catch blocks, ensure that a response is sent in both success and error cases.
    • Remove redundant response logic to prevent multiple responses being sent.