If you are just on the way to learning the HTTP module or Express.js you might have come across this error more often. This error is generally caused when we send multiple HTTP responses for a single request.
In this article, we’ll deep dive into the cause of the error, “Cannot set headers after they are sent to client” by understanding how the HTTP protocol, the HTTP module, and the express.js module work along with a practical demonstration of how to solve this error easily.
Understanding the HTTP and Express Module
We know that the HTTP protocol establishes a client-server protocol where the client which is generally a Web Browser communicates with the server by exchanging individual messages. Node.js has a built-in HTTP module that helps in establishing such communication using JavaScript over the HTTP protocol.
The messages sent by the client, are called requests and the messages sent by the server as an answer are called responses. The HTTP module provides low-level API for stream handling and message parsing. A field within an HTTP request or response, known as an HTTP header, transmits extra information and contextual data regarding the request or response.
The client makes a request to the server. The request gets wrapped in a request object and sent to the server. This is where the Node.js HTTP module comes in. The HTTP module helps create a web server that listens to events. On receiving the request object from the client, the message is parsed, and then an appropriate response object is created setting the response headers also. The HTTP module sends in the response object to the client and the HTTP request-response cycle ends.
The following code will help you better understand the HTTP cycle.
const http = require('http')
const app = http.createServer()
app.on('request', (req, res) => {
if (req.url === '/') {
res.writeHead(200, {'content-type': 'text/html'})
res.write('<h1>Hello from Server</h1>')
res.end()
}
})
app.listen(3000)
In the above code, the HTTP module creates a server and listens to request events on port 3000. On receiving a request on the homepage i.e http://localhost:3000 a response object is created with headers set using the writeHead method and the body is an H1 tag set using the write method. The end method ends the HTTP cycle.
Using the HTTP module can get tedious so the Express.js package was introduced. Express is a wrapper around the HTTP module that helps in creating routes and middleware easily. A middleware function serves as an intermediary between the request and response objects, governing the exchange of data.
Express determines the type of middleware based on the number of parameters provided. Route middleware, for instance, utilizes a callback that includes the request and response parameters. Custom middleware usually receives the request, response, and next parameters. Finally, an error handler typically accepts the error, request, response, and next parameters.
Express works similarly to the HTTP module however it ends the HTTP cycle when the following method is used with the response:
- json()
- send()
- sendStatus()
- redirect()
- render()
Why Express produces the Error
If we ever send more than one such response method for a single request or HTTP cycle using express, the error, “Cannot set headers after they are sent to client” is produced which means we are trying to set the header for a response after it has already been sent to the client or after the HTTP cycle has already ended.
This error is also produced if an express middleware uses the next() method more than once or uses the above-mentioned response methods with the next() method in the same scope. I will explain this error and its solution better with some examples.
Setting up an Express.js project to demonstrate the Error
Open your terminal, create a project folder, initialize a node project inside it, and install the dependencies using the following commands.
mkdir httpServer && cd httpServer
npm init -y
npm install express ejs nodemon
Create a views folder and an index.ejs file inside it
views/index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home Page</title>
</head>
<body>
<h1>This is homepage</h1>
</body>
</html>
index.js
const express = require('express')
const app = express()
app.set('view engine', 'ejs')
app.get('/', (request, response) => {
response.render('index')
})
app.get('/user', (request, response) => {
response.redirect('/')
})
app.listen(3000, () => {
console.log("Server is listening on port 3000");
})
Here we have created a basic express server that listens on port 3000 and defined two routes, one for the homepage and another user. We are also rendering HTML pages using EJS.
Start the server using the command:
nodemon index.js
Now we can move on with the examples.
Example 1:
// index.js
const express = require('express')
const app = express()
app.set('view engine', 'ejs')
app.get('/', (request, response) => {
response.render('index')
})
app.get('/user', (request, response) => {
response.redirect('/')
response.status(200).json({"error": "Some error"})
})
app.listen(3000, () => {
console.log("Server is listening on port 3000");
})
Output:
On line 13 we are already redirecting the request to the home route and ending the HTTP cycle but on line 14 we are again trying to send a response object with status 200 and a JSON body. This violates the HTTP principle and we meet with an error.
Solution: Remove either line 13 or line 14 to remove the error. Alternatively, you can also use a return statement to shadow any code written below it in its scope.
return response.redirect('/')
Example 2
// index.js
const express = require('express')
const app = express()
app.set('view engine', 'ejs')
app.get('/', (request, response) => {
response.render('index')
})
const authUser = (request, response, next) => {
console.log('User authenticated');
next()
response.status(400).json({"key":"value"})
}
app.get('/user', authUser, (request, response) => {
response.status(200).json({"error": "Some error"})
})
app.listen(3000, () => {
console.log("Server is listening on port 3000");
})
In this example, we introduced a custom middleware for the route /user and defined it.
Output:
If we visit http://localhost:3000/user using our browser we are again given the error because in the middleware we use the next() method to go to the next route middleware and then also try to send a response body with status 400 and a JSON.
Solution: The solution to it will be to either completely remove line number 15 or use a condition check to decide which response to send. Alternatively, you can also return from the middleware using return next() to shadow any code written below it in its scope.
Conclusion
In this article, we discussed one of the most common errors when working with Express.js, that is “Cannot set headers after they are sent to client” and also saw some practical demonstrations on how to correct and avoid this error. Hope you find this article helpful.
If you are wondering what to read next, then try reading our introductory guide on Express.js.
References
https://stackoverflow.com/questions/7042340/error-cant-set-headers-after-they-are-sent-to-the-client
https://developer.mozilla.org/en-US/docs/Web/HTTP