Paula Muldoon
Paula Muldoon

Reputation: 17

Unhandled rejection Error: Can't set headers after they are sent using Node

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

Answers (3)

Ian
Ian

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

JellyKid
JellyKid

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

Karim
Karim

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

Related Questions