1

This User's route with Puppeteer code:

Router.get('/generate_invoice', (req, res) => {


    const userData = req.session.user;
    res.render("./patientpanel/invoice", { user: userData });

    (async () => {
        // launch a new chrome instance
        const browser = await puppeteer.launch({
            headless: true
        });

        const page = await browser.newPage();
        const filePathName = path.resolve(__dirname, '../views/patientpanel/invoice.ejs');

        const html = fs.readFileSync(filePathName, 'utf8')
        await page.goto("http://localhost:5000/generate_invoice" + html);
        await page.setContent(html, {
            waitUntil: 'domcontentloaded'
        });
        const pdfBuffer = await page.pdf({
            format: 'A4'
        });

        // or a .pdf file
        await page.pdf({ path: "./user.pdf", format: pdfBuffer });
        await browser.close()
    })();
});

The PDF file generated successfully but it shows the EJS template as it is without any proper format and data which I rendered through the above router.

The EJS template code:

<tr class="information">
    <td colspan="2">
        <table>
            <tr>
                <td>
                    Name: <%- user.firstname %>
                    <%- user.lastname %><br />
                    Email: <%- user.email %><br />
                    Mobile No. : <%- user.mob %>
                </td>
            </tr>
        </table>
    </td>
</tr>

Getting Output Like This

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Digen
  • 67
  • 9
  • 1
    Why did you expect anything different? You read the file contents and plopped them in with `setContent`, literally. If you want to process that text as EJS, run EJS on it and pass in the user data. Hint: `const rendered = require("ejs").render(html)`, then `setContent(rendered)`. BTW, `readFileSync` is not a good thing to put into a request handler--it'll slow your app down, preventing the main thread from handling other requests as it waits for the kernel. It's already an `async` func so use `await fs.promises.readFile`. – ggorlen Oct 30 '22 at 18:52
  • Thank you. Actually I have no Idea about it. Cause I am using for the first time.Thanks for the hint.. – Digen Oct 31 '22 at 05:21
  • But how to pass that ({user : user data}) while rendering. I tried but I'm confused about it. If you have any source please let me know – Digen Nov 04 '22 at 19:30
  • 2
    Ah, I missed that you already have a `render` call in your code. `const rendered = require("ejs").render(html, {user: userData})` should work. You're confusing `res.render` which renders EJS as the body of a HTTP response with running EJS to process HTML for PDF purposes without sending a response. Probably remove your `res.render("./patientpanel/invoice", { user: userData });` line, because that sends a response right away rather than waiting for the PDF to render. You probably want to run Puppeteer to make the PDF, screenshot it, then send _that_ as the response with `res.sendFile()`. – ggorlen Nov 04 '22 at 19:33
  • Thank you . Finally I got the data – Digen Nov 04 '22 at 20:03

1 Answers1

3

The basic misunderstanding seems to be familiarity with EJS in conjunction with Express (e.g. res.render()), but not standalone. EJS offers renderFile and render methods that work with files and plain strings respectively. You want to use these to prepare the HTML, which is then put into Puppeteer, PDF'd and then sent as the response.

Here's the basic workflow to handle a request:

.-------------------.
| EJS template file |
`-------------------`
       |
[ejs.renderFile]
       |
       v
.-------------.
| HTML string |
`-------------`
       |
[page.setContent]
       |
       v
.-------------.
| browser tab |
`-------------`
       |
   [page.pdf]
       |
       v
.------------.
| PDF buffer |
`------------`
       |
   [res.send]
       |
       v
.----------.
| response |
`----------`

Here's a complete example you can work from (I simplified the path to make it easier to reproduce without your folder structure; template is in the same directory as the server code):

const ejs = require("ejs"); // 3.1.8
const express = require("express"); // ^4.18.1
const puppeteer = require("puppeteer"); // ^19.1.0

express()
.get("/generate-invoice", (req, res) => {
  const userData = { // for example
    firstname: "linus",
    lastname: "torvalds",
    email: "foo@bar.com",
    mob: "900-900-9000"
  };

  let browser;
  (async () => {
    browser = await puppeteer.launch();
    const [page] = await browser.pages();
    const html = await ejs.renderFile("invoice.ejs", {user: userData});
    await page.setContent(html);
    const pdf = await page.pdf({format: "A4"});
    res.contentType("application/pdf");

    // optionally:
    res.setHeader(
      "Content-Disposition",
      "attachment; filename=invoice.pdf"
    );

    res.send(pdf);
  })()
    .catch(err => {
      console.error(err);
      res.sendStatus(500);
    }) 
    .finally(() => browser?.close());
})
.listen(3000);

Navigate to http://localhost:3000/generate-invoice to test.

You can use ejs.render() along with fs.readFile to pull the file in once at the start of the app, saving an extra file read on each request with ejs.renderFile.

As an aside, it's a bit heavy to launch a browser on every request, so you might consider working with a single browser and multiple pages. See this answer for a potential approach.

ggorlen
  • 44,755
  • 7
  • 76
  • 106