Generating PDF documents with Node and Ionic

December 22, 2017, 8:50 am Categories:

Categories

In Part 3 of this tutorial series we developed the Ionic aspect of the project; adding the necessary logic and templating for the mobile facing portion of the application.

In this concluding tutorial we'll complete our development of the Node/ExpressJS/MongoDB and Ionic application by adding the ability to generate PDF documents on the server side with node.

Adding the PDF route

Open the ionic-crud/server/server.js file and after the last defined route - but before the app.use and app.listen declarations at the end of the file - enter the following route definition.

apiRouter.post('/generate-pdf', (req,res) =>
{

});

Here we're simply creating a route named generate-pdf which, as you'll probably remember from the previous tutorial, is called from within the generatePDF() method of the app/ionic-gallery/src/pages/view-gallery/view-gallery.ts file like so:

generatePDF()
{
   let headers 		: any		 = new HttpHeaders({ 'Content-Type': 'application/json' }),
       options 		: any 		 = { name : this.name, description : this.description, thumbnail : this.thumbnail },
       url       	: any      	 = this._HOST + "api/generate-pdf";

   this._HTTP
   .post(url, options, headers)
   .subscribe((data : any) =>
   {
      // If the request was successful notify the user
      this.displayNotification(name + ' was successfully created');      
   },
   (error : any) =>
   {
      console.dir(error);
   });
}

As the generatePDF() method supplies a JSON map of data (originally pulled from the project's MongoDB document) we now need to retrieve this data within our node application and parse that.

The PDF generating logic

Within our /generate-pdf route we're going to make use of the PDFKit API to generate a PDF document from the supplied data.

As we installed the PDFKit package in Part 1 of this tutorial and imported that within Part 2 of this tutorial we can simply jump straight into coding the functionality for this route.

The PDFKit documentation is fairly straightforward so I'll simply add the logic for generating PDF documents and explain what is happening at each stage of the code:

apiRouter.post('/generate-pdf', (req,res) =>
{
   /* Create a pdfKit object that defines the following properties:
    *
    * 1. Document orientation
    * 2. Document margins
    * 3. Document metadata (Title, Author, Subject and Date last modified)
    */
   var doc 				= 	new pdfKit({ layout : 'portrait', margins: { top: 50, bottom: 50, left: 72, right: 72 }, info: {
   	  Title 	: 'My fabulous document',
   	  Author 	: 'Saints at Play Limited',
   	  Subject 	: 'Demonstrating PDF Kit abilities',
   	  ModDate   : new Date(Date.now()).toLocaleString()
   }}),

       /* Retrieve the values for the MongoDB document - from the 
          posted data within the Ionic mobile application - that we 
          wish to embed within the generated PDF document */
   	   name 			=	req.body.name,
       description 		=	req.body.description,
       thumbnail 		=	req.body.thumbnail,
       displayed 		=	req.body.displayed,
       date 			=	Date.now();


   /* Replace any spaces within the name value with dashes instead */
   name 				= name.replace(' ', '-');
   
  
   /* Define the directory location and name of the generated PDF document */
   doc.pipe(fs.createWriteStream('pdfs/' + name + '.pdf'));


   /* Embed image within the document as follows:
      image(image to be embedded, x axis position, y axis position, fit image to specified dimensions) */
   doc.image(thumbnail, 25, 25, {
      fit 		: [320, 240]
   });


   /* Embed specific font, define font size and add text as follows:
      text(value to be displayed, x axis position, y axis position) */
   doc.font('fonts/diavlo-bold-webfont.ttf')
   .fontSize(18)
   .text(name, 25, 285);


   /* Embed specific font, define font size and add text as follows:
      text(value to be displayed, x axis position, y axis position) */
   doc.font('fonts/delicious-roman-webfont.ttf')
   .fontSize(16)
   .text(description, 25, 310);

   /* Finalise the PDF */
   doc.end();
});

As you've seen the syntax is fairly intuitive and quick to work with (although we're only scratching the surface of what PDFKit is capable of such as multi-page document generation, embedding vector graphics and annotations).

You'll notice in the above script that I supplied a couple of different fonts for embedding within the generated PDF document.

The Diavlo font can be found here and the Delicious font here.

You will also need to create a directory named fonts and a directory named pdfs within the root of the ionic-crud/server/ directory (ensure that the pdfs directory has read/write access).

With these in place your ionic-crud/server directory should now look like the following:

ionic-crud
    |
    | - app
    |
    | - server
           |
           | - config.js
           |
           | - fonts/
                 |
                 | - delicious-bold-webfont.ttf
                 |
                 | - diavlo-bold-webfont.ttf
           |
           | - models/
                  |
                  | - gallery.js
           |           
           | - node_modules/
           |
           | - package.json
           |
           | - pdfs/
           |
           | - server.js

The completed server.js file

With the addition of the /generate-pdf route the code for the project's completed ionic-crud/server/server.js file should now look like this:

// Import the server configuration file
var config 			= require('./config'),

    /* Import the ExpressJS framework for Middleware/routing */
    express 		= require('express'),

    /* Import the File System module for enabling File I/O operations */
	fs      		= require("fs"),

    /* Import Mongoose for enabling communication with MongoDB and 
       management of data handling tasks */
	mongoose 		= require('mongoose'),

    /* Import Body Parser module for enabling data from POST requests
       to be extracted and parsed */
	bodyParser      = require("body-parser"),

    /* Import PDFKit module to dynamically generate and publish PDF 
       documents for the application */
	pdfKit      	= require("pdfkit"),

    /* Handle for storing the ExpressJS object */
	app 			= express(),

    /* Use ExpressJS Router class to create modular route handlers */
	apiRouter     	= express.Router(),

    /* Import path module to provide utilities for working with file 
       and directory paths */
	path 			= require('path'),

    /* Define Mongoose connection to project's MongoDB database */
	connection 		= mongoose.connect(config.database, { useMongoClient: true }),

    /* Import Schema for managing MongoDB database communication 
       with Mongoose */
	gallery         = require('./models/gallery');




/* 
  Manage size limits for POST/PUT requests 
 */
app.use(bodyParser.json({limit: '10mb'}));
app.use(bodyParser.urlencoded({limit: '10mb', extended: true}));




/* 
   Manage CORS Access for ALL requests/reponses 
 */
app.use(function(req, res, next)
{
   /* Allow access from any requesting client */
   res.setHeader('Access-Control-Allow-Origin', '*');

   /* Allow access for any of the following Http request types */
   res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, PUT');

   /* Set the Http request header */
   res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type,Authorization');
    next();
});




/* Manage ALL Http GET requests to the specified route */
apiRouter.get('/gallery', function(req, res) 
{
   /* Use the gallery model and access Mongoose's API to 
      retrieve ALL MongoDB documents whose displayed field
      has a value of true */
   gallery.find({ displayed: true }, (err, recs) =>
   {

      /* If we encounter an error log this to the console */
      if (err) 
      {
         console.dir(err);
      }

      /* Send the retrieve documents based as JSON encoded 
         data with the Router Response object */
      res.json({ records: recs });

   });
});




/* Manage ALL Http POST requests to the specified route */
apiRouter.post('/gallery', function(req, res) 
{

   /* Retrieve the posted data from the Request object and assign
      this to variables */
   var name 			=	req.body.name,
       description 		=	req.body.description,
       thumbnail 		=	req.body.thumbnail,
       displayed 		=	req.body.displayed,
       date 			=	Date.now();


   /* Use the gallery model to access the Mongoose API method to
      add the supplied data as a new document to the MongoDB
      database */
   gallery.create({ name 			: name,  
   					description 	: description, 
   					thumbnail 		: thumbnail, 
   					displayed 		: displayed, 
   					date 			: date },
   				 function (err, small) 
   {

      /* If we encounter an error log this to the console*/
      if (err) 
      {
         console.dir(err);
      }

      /* Document was successfully created so send a JSON encoded 
         success message back with the Router Response object */
      res.json({ message: 'success' });

   });

});




/* Handle PUT requests with expected recordID parameter */
apiRouter.put('/gallery/:recordID', function(req, res) 
{

   /* Use the gallery model to access the Mongoose API method and
      find a specific document within the MongoDB database based 
      on the document ID value supplied as a route parameter */
   gallery.findById({ _id: req.params.recordID }, (err, recs) =>
   {

      /* If we encounter an error we log this to the console */
      if (err) 
      {
         console.dir(err);
      }
      else 
      {
         /* Assign the posted values to the respective fields for the retrieved
            document */
      	 recs.name 				= req.body.name 		|| recs.name;
         recs.description 		= req.body.description 	|| recs.description;
         recs.thumbnail  		= req.body.thumbnail	|| recs.thumbnail;
         recs.displayed 		= req.body.displayed 	|| recs.displayed;
 
         /* Save the updated document back to the database */
         recs.save((err, recs) => 
         {
            /* If we encounter an error send the details as a HTTP response */
            if (err) 
            {
               res.status(500).send(err)
            }

            /* If all is good then send a JSON encoded map of the retrieved data
               as a HTTP response */
            res.json({ records: recs });
         });
      }     

   });
});




/* Handle DELETE requests with expected recordID parameter */
apiRouter.delete('/gallery/:recordID', function(req, res) 
{
   
   /* Use the gallery model to access the Mongoose API method and
      find & remove a specific document within the MongoDB database 
      based on the document ID value supplied as a route parameter */
   gallery.findByIdAndRemove({ _id: req.params.recordID }, (err, recs) =>
   {

      /* If we encounter an error we log this to the console */
      if (err) 
      {
         console.dir(err);
      }


      /* If all is good then send a JSON encoded map of the removed data
         as a HTTP response */
      res.json({ records: recs });

   });
});




apiRouter.post('/generate-pdf', (req,res) =>
{
   /* Create a pdfKit object that defines the following properties:
    *
    * 1. Document orientation
    * 2. Document margins
    * 3. Document metadata (Title, Author, Subject and Date last modified)
    */
   var doc 				= 	new pdfKit({ layout : 'portrait', margins: { top: 50, bottom: 50, left: 72, right: 72 }, info: {
   	  Title 	: 'My fabulous document',
   	  Author 	: 'Saints at Play Limited',
   	  Subject 	: 'Demonstrating PDF Kit abilities',
   	  ModDate   : new Date(Date.now()).toLocaleString()
   }}),

       /* Retrieve the values for the MongoDB document - from the 
          posted data within the Ionic mobile application - that we 
          wish to embed within the generated PDF document */
   	   name 			=	req.body.name,
       description 		=	req.body.description,
       thumbnail 		=	req.body.thumbnail,
       displayed 		=	req.body.displayed,
       date 			=	Date.now();


   /* Replace any spaces within the name value with dashes instead */
   name 				= name.replace(' ', '-');
   
  
   /* Define the directory location and name of the generated PDF document */
   doc.pipe(fs.createWriteStream('pdfs/' + name + '.pdf'));


   /* Embed image within the document as follows:
      image(image to be embedded, x axis position, y axis position, fit image to specified dimensions) */
   doc.image(thumbnail, 25, 25, {
      fit 		: [320, 240]
   });


   /* Embed specific font, define font size and add text as follows:
      text(value to be displayed, x axis position, y axis position) */
   doc.font('fonts/diavlo-bold-webfont.ttf')
   .fontSize(18)
   .text(name, 25, 285);


   /* Embed specific font, define font size and add text as follows:
      text(value to be displayed, x axis position, y axis position) */
   doc.font('fonts/delicious-roman-webfont.ttf')
   .fontSize(16)
   .text(description, 25, 310);

   /* Finalise the PDF */
   doc.end();
});




/* Mount the specified Middleware function based on matching path */   
app.use('/api', apiRouter);


/* Open a UNIX socket, listen for connections to the specified port */
app.listen(config.port);

Generating a PDF

Open your system CLI, navigate to the root of the ionic-crud/server directory and run the node server with the following command:

nodemon server.js

Then install and launch the ionic-gallery application, that we completed coding in the previous tutorial, on your handheld device, select the document you wish to publish from the list displayed on the application's home page and click the Publish to PDF button:

Ionic application displaying a selected database record in a modal window

All things being well you should then see your PDF documents being generated within the ionic-crud/server/pdfs directory like so:

Generated PDF documents displayed in the pdfs directory of the node application

In summary

Generating PDF documents with node is made almost effortless thanks to the PDFKit library.

The API and its syntax is quite intuitive and quick to understand which greatly reduces the time required to familiarise one's self with using the tool and, as an added bonus, the documentation is fairly solid too.

All in all if you're looking to generate PDF's from your Ionic applications then using a combination of node and PDFKit should be high on your list of approaches.

If you've enjoyed what you've read and/or found this helpful please feel free to share your comments, thoughts and suggestions in the comments area below.

I explore different aspects of using the Ionic framework within my e-book featured below and if you're interested in learning more about further articles and e-books that I'm working on please sign up to my FREE mailing list.

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top