Table of Contents
The primary cause of the error, "Error: Cannot set headers after they are sent to client" is sending multiple HTTP responses per request. The error mainly occurs when you run any of the five response methods after another in a middleware or route function: json()
, send()
, sendStatus()
, redirect()
, and render()
.
This tutorial shows multiple ways to produce and solve the error "Error: Cannot set headers after they are sent to client."
Before that, it would be best to understand the workings of the http
and express
modules. What is more?
Find out below.
Understand the http module
Since the origin of the error "Error: Cannot set headers after they are sent to client" is disobeying a critical HTTP rule, it is important to understand how the http
module works in Node.js.
HTTP enables transferring information between the client (browser) and a web server. It comes with methods like GET and POST, which determine how information is transferred. Other metadata are content type and length. The metadata is often referred to as headers.
The client makes a request. The request gets wrapped in the request object and sent to the server. The server handles the request and sends feedback in the response object.
Node.js presents you with the http
module to handle the request and response objects. First, we import the built-in module, create a web server and listen for a request event.
import http from 'http'
const app = http.createServer()
app.on('request', (req, res) => {
if (req.url === '/') {
res.writeHead(200, {'content-type': 'text/html'})
res.write('<h2>Home Page</h2>')
res.end()
}
})
app.listen(3000)
The on()
method has a callback function that handles request and response objects. For example, we can check the route req.url === '/'
making the request before sending the response headers and body. The end()
method terminates the response. That concludes one HTTP cycle.
As you can see from the above example, writing useful code with the raw http
module could be hectic. That is where express comes to the rescue.
Why express produces the error, “Error: Cannot set headers after they are sent to client“
express
is a wrapper around the http
module. It simplifies creating routes and custom middleware. Middleware is a function that sits between the request and response objects and controls the communication.
express
infers middleware type depending on the number of supplied parameters. For example, a route middleware accepts a callback with the request and response parameters. A custom middleware often receives the request, response, and next parameters. Lastly, an error handler mostly accepts error, request, response, and next parameters.
The client sends a request. express
receives and handles the request through the request parameter. After handling the request, it sends feedback through the response object handler. That completes one HTTP cycle.
express
terminates an HTTP cycle by sending one of these methods: json(
), send()
, sendStatus()
, redirect()
, or render()
.
json()
sends JSON to the client. send()
mainly sends strings, buffers, or objects. sendStatus()
sets the response status code and sends its string representation. For instance, a 200 status code equates to OK.
redirect()
redirects the user to a URL. render()
sends a file, mostly referred to as a view, like index.html and index.ejs.
Sending two or more response methods in the same control flow and middleware invokes the error, "Error: Cannot set headers after they are sent to client."
Here is a practical example.
Lab environment setup
Launch your terminal. Create the project directory and cd
into it before initializing npm
and installing the application dependencies.
mkdir httpHeaders && cd httpHeaders
npm init -y
npm i express nodemon ejs express-ejs-layouts
code .
express
is the web server; nodemon
, restarts the server when you modify the script file; ejs
, templating engine; express-ejs-layouts
, creates ejs
layouts. We then open the project in Visual Studio Code.
Since we will be using ECMAScript modules, it would help to specify the module type "type": "module"
before modifying the test
script to use nodemon
.
package.json
{
"name": "httpheaders",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "nodemon index"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"ejs": "^3.1.8",
"express": "^4.18.2",
"express-ejs-layouts": "^2.5.1",
"nodemon": "^2.0.20"
}
}
Now let's invoke and solve the error, "Error: Cannot set headers after they are sent to client."
Practical examples
Assume we want to send the home page when a request is made through the slash /
route. And redirect the user to the home page on making a request at the /about
page.
views/layout.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>Document</title>
</head>
<body>
<%- body %>
</body>
</html>
views/index.ejs
<h2>Home Page</h2>
index.js
import express from 'express'
import layouts from 'express-ejs-layouts'
const app = express()
app.set('view engine', 'ejs')
app.use(layouts)
app.get('/', (req, res) => {
res.render('index')
})
app.get('/about', (req, res) => {
res.redirect('/')
})
app.listen(3000)
In the process, we (accidentally) send multiple responses per request.
Example~1: sendStatus() and json()
app.get('/about', (req, res) => {
// res.redirect('/')
res.sendStatus(200).json({ message: 'Redirecting to the home page.'})
})
res.sendStatus(200)
ends the HTTP cycle with the OK
message. The JSON from json({ message: 'Redirecting to the home page.'})
is treated as another send response.
The solution here is to use res.status(200)
instead of res.sendStatus(200)
.
app.get('/about', (req, res) => {
// res.redirect('/')
// res.sendStatus(200).json({ message: 'Redirecting to the home page.'})
res.status(200).json({ message: 'Redirecting to the home page.'})
})
Example~2: redirect() and json()
If we uncomment the redirect method, we get another error. Reason?
app.get('/about', (req, res) => {
res.redirect('/')
res.status(200).json({ message: 'Redirecting to the home page.'})
})
res.redirect('/')
ends the HTTP cycle. The next nested response methods are meaningless to an already closed header.
Conclusion
The key takeaway from this tutorial is that nesting or running multiple response functions per request in a scope produces the error: Cannot set headers after they are sent to client. You can solve the error as explained in the tutorial.