Reputation: 17
I am attempting to render a view using JS, Node, and Express but am getting this error 'Unhandled rejection Error: Can't set headers after they are sent'. I know it's something to do with asynchronous callbacks, but I can't work out exactly how to fix it.
The problem doesn't seem to be with the res.redirect (removing it doesn't display the data) but rather with the res.render
and var spaces
not loading in properly to the EJS, we think. When we put spaces
, we get the message "object Promise".
const userController = require('../controllers').user;
const spaceController = require('../controllers').space;
module.exports = (app) => {
app.get('/', function(req, res) {
res.render('index.ejs');
});
app.post('/users/new', function(req,res){
userController.create(req,res);
res.redirect('/');
});
app.get('/spaces', function(req, res) {
var spaces = spaceController.retrieve(req,res);
res.render('spaces/index.ejs', {spaces});
});
app.get('/spaces/new', function(req, res) {
res.render('spaces/new.ejs');
});
app.post('/spaces/new', function(req,res){
spaceController.create(req,res);
res.redirect('/spaces');
});
};
And my controller:
const Space = require('../models').Space;
module.exports = {
create(req, res) {
return Space
.create({
name: req.body.name,
description: req.body.description,
price: req.body.price,
})
.then(space => res.status(201).send(space))
.catch(error => res.status(400).send(error));
},
retrieve(req, res) {
return Space
.findAll()
.then(space => {
if (!space) {
return res.status(404).send({
message: 'Space Not Found',
});
}
return res.status(200).send(space);
})
.catch(error => res.status(400).send(error));
},
};
And my view:
<!doctype html>
<html>
<head>
<title>MakersBnB</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
<style>
body { padding-top:80px; }
</style>
</head>
<body>
<div class="container">
<div class="col-sm-6 col-sm-offset-3">
<div class="jumbotron text-center">
<h1>Book a Space</h1>
<form action="/spaces/new" method="get">
<button type="submit" class="btn btn-warning btn-lg">List a space</button>
</form>
<div class="spaces">
<% spaces.each(function(space) { %>
<ul>
<li><%= space.name %></a></li>
<li><%= space.description %></li>
<li>£ <%= space.price %></li>
</ul>
<% });%>
</div>
</div>
</div>
</body>
</html>
Upvotes: 0
Views: 1053
Reputation: 490
You are getting this error because you are sending multiple responses for some of your requests. You can only send one response for a request.
For example in your /users/new
route, you first make a call to userController.create
. userController.create
sends a response as part of the body of the function.
Your code then goes back to the callback for your /users/new
route and calls the res.redirect('/')
which invokes your /
route which also returns a response.
To fix, let your controller handle sending the response.
For example:
app.post('/users/new', userController.create);
In the body of the controller functions, you can also choose which view to render
app.get('/spaces', spaceController.retrieve);
and in the controller:
retrieve(req, res) {
return Space
.findAll()
.then(spaces => {
if (!spaces) {
return res.status(404).send({
message: 'Space Not Found',
});
}
return res.status(200).render('spaces/index.ejs', {spaces});
})
.catch(error => res.status(400).send(error));
},
Upvotes: 1
Reputation: 302
app.post('/users/new', function(req,res){
userController.create(req,res);
res.redirect('/');
});
You are basically starting Space.create() as async and before it finishes you are responding with a redirect.
you could try
app.post('/users/new',
userController.create,
(res,req) => res.redirect('/')
);
The redirect will never be reached with that but you could have userController.create return next() like this
create(req, res, next) {
return Space
.create({
name: req.body.name,
description: req.body.description,
price: req.body.price,
})
.then((space) => {
res.locals.space = space || {};
return next();
})
.catch(error => next(error));
}
with this middleware
app.post('/users/new',
userController.create,
(res,req) => res.status(200).send(res.locals.space)
);
Examples from recent project: https://github.com/JellyKid/ballsavr/tree/master/Backend/lib
Upvotes: 0
Reputation: 8632
these routes
app.post('/users/new', function(req,res){
userController.create(req,res);
res.redirect('/');
});
app.get('/spaces', function(req, res) {
var spaces = spaceController.retrieve(req,res);
res.render('spaces/index.ejs', {spaces});
})
app.post('/spaces/new', function(req,res){
spaceController.create(req,res);
res.redirect('/spaces');
});
the logic inside spaceController
already handle the response to the client asynchronously (both for success and fail)
but you send a synchronous response immediatly (res.redirect
) that will prevent you to send the async response later. this triggers the exception.
removing the res.redirect
instruction from the three routes should fix it.
Upvotes: 0