inversify-express-utils icon indicating copy to clipboard operation
inversify-express-utils copied to clipboard

res.render(..) not working : error Can't set headers after they are sent

Open satya-jugran opened this issue 7 years ago • 6 comments

Express res.render is not working and throwing exception.

Expected Behavior

It should render render a view and let the view engine send a response.

Current Behavior

->res.send({'someKey': 'someValue'}) is working -> res.render('viewName', {title: 'Home'}) is throwing exception from handlebars view engine - error Can't set headers after they are sent

It is also working as soon as we remove inversifyjs.

Possible Solution

Let expressjs's view engine send the response. If InverseJs is sending the response after setting headers then there should be an option to define that inversejs is responsible for routing but not for sending responses. Although I'm not sure how it is working internally.

Steps to Reproduce (for bugs)

  1. Checkout any inversify express app seed
  2. In the controller, instead of res.send try rendering a view with res.render(..)

Context

Rendering a view is common is express app when our app is not single page app and we want to render the view from server side.

Your Environment

  • Version used: npm -> 5.6.0, express -> 4.13.4, express-handlebars: 3.0.0
  • Environment name and version (e.g. Chrome 39, node.js 5.4): Node 8.11.2
  • Operating System and version (desktop or mobile): Windows 10 desktop
  • Link to your project:

Code for simple HomeController:

import { Request, Response, NextFunction } from "express"; import { injectable, inject } from "inversify"; import { interfaces, controller, request, response, httpGet, next } from "inversify-express-utils";

@controller("/home") export class HomeController implements interfaces.Controller { @httpGet("/") private index(req: Request, res: Response) { // res.send({"message": "Success" }); res.render("home", { title: "Home" }); } }

Stack trace

_http_outgoing.js:491 throw new Error('Can't set headers after they are sent.'); ^

Error: Can't set headers after they are sent. at validateHeader (_http_outgoing.js:491:11) at ServerResponse.setHeader (_http_outgoing.js:498:3) at ServerResponse.header (F:\WitSpry\Source Code\Wisely\wisely\node_modules\express\lib\response.js:767:10) at ServerResponse.contentType (F:\WitSpry\Source Code\Wisely\wisely\node_modules\express\lib\response.js:595:15) at ServerResponse.send (F:\WitSpry\Source Code\Wisely\wisely\node_modules\express\lib\response.js:145:14) at F:\WitSpry\Source Code\Wisely\wisely\dist\controllers\home.js:31:17 at Immediate._onImmediate (F:\WitSpry\Source Code\Wisely\wisely\node_modules\express-handlebars\lib\utils.js:26:13) at runCallback (timers.js:810:20) at tryOnImmediate (timers.js:768:5) at processImmediate [as _immediateCallback] (timers.js:745:5)

satya-jugran avatar Jun 07 '18 11:06 satya-jugran

+1

whyvra avatar Sep 24 '18 12:09 whyvra

I had the same issue, wrapping the res.render() in a Promise worked, but not ideal.

aescarcha avatar Sep 25 '18 12:09 aescarcha

Do you have an example in a controller of your solution? Or can we get this fix? This is currently stopping me from using inversify-express-utils at all! I need view engine support :/

whyvra avatar Sep 27 '18 12:09 whyvra

Hi, in my solution all the controllers extend an abstract BaseController which has the custom render method. this is my code:

@injectable()
export abstract class BaseController {
    public render(res: express.Response, template: string, options = {}): Promise<string> {
        return new Promise<string>((resolve, reject) => {
            res.render(template, options, (err, compiled) => {
                if (err) {
                    console.log(err);
                    reject('500 when rendering the template');
                }
                resolve(compiled);
            });
        });
    }
}
@controller('/')
export class ListingController extends BaseController {

    constructor(@inject(TYPES.ApiService) private apiService: ApiService) {
        super();
    }

    @httpGet('/')
    public async get(
        @request() req: express.Request,
        @response() res: express.Response,
    ) {
        // Do things...
        const data: IListingData = await this.apiService.get(params);
        return this.render(res, 'listing/listing', data);
    }
}

I hope it helps!

aescarcha avatar Sep 28 '18 09:09 aescarcha

This took me a while to find out. I was getting 204 responses with empty bodies even though I called res.render(). Even res.status(200).render() didn't help. @aescarcha Your solution still works, thanks for that

dominicbartl avatar Aug 19 '20 11:08 dominicbartl

waveball

httpget avatar Feb 12 '25 23:02 httpget