Node development for an Ionic Application

December 22, 2017, 8:20 am Categories:

Categories

In Part 1 of this tutorial we set up the foundation of our project by installing & configuring the necessary software as well as defining the project directory structure; dividing that into 2 separate aspects: server and app.

ionic-crud
    |
    | - app
    |
    | - server

The server directory of the project will store the Node/ExpressJS development of the application while the app directory is where the Ionic mobile code will be stored and managed.

In this second part of the tutorial we'll focus solely on the Node/ExpressJS development of the project.

First things first

Within the root of the server directory create the following files:

  • config.js (will contain the necessary environmental configurations for the node application)
  • server.js(will contain the node application logic)

Open the ionic-crud/server/config.js file and enter the following code:

module.exports = {
   'port'		: process.env.PORT || 8080,
   'database'	: 'mongodb://localhost:27017/gallery'
};

This simply defines the port number that the node application will run on and the MongoDB database instance and collection name (gallery) that we wish to use for the project.

Note that if the gallery collection doesn't currently exist it will be automatically created (with the above declaration) the first time the node application is run which is pretty useful.

As we're developing the Node/MongoDB application locally for the purposes of this tutorial we don't need to supply log-in credentials for the above database configuration. We could, of course, set MongoDB up to require this but let's keep it simple for local development!

Building the node application

We'll use the ionic-crud/server/server.js file to define the application routes for the server-side of the project along with the required logic for the following functionality:

As with all Node development we start by declaring all of the packages that we require for the application within the ionic-crud/server/server.js file:

// 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');



/*

   Everything else that follows(all of the routes and application logic) 
   NEEDS to be placed BEFORE the code at the bottom of the file!     

*/



/* Mount the specified Middleware function based on matching path 
   ALL Http requests will be sent to /api followed by whatever the 
   requested endpoint is
*/   
app.use('/api', apiRouter);


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

It's important to note that with the above we've used the Express Router object to create a 'mini-application' located at /api.

Doing so allows us to group route endpoints based on specific criteria.

For example, if we were developing a CMS we could group endpoints based on the following criteria/requirements:

  • admin/ (supplies endpoints for administrator access only)
  • users/ (supplies endpoints for non-administrator use)
  • assets/ (supplies endpoints for managing assets such as images, documents etc)

As it is we're simply developing a very basic API for retrieving and managing documents within the project's MongoDB database hence the use of /api .

Size limits and CORS

We follow the defined module imports for the server.js file by declaring the size limits for posted data (as we'll be supplying images as base64 encoded strings the size of our Http POST requests needs to be reasonably catered for):

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

We'll subsequently configure CORS access for the node application with the following declarations:

/* Manage CORS Access for ALL requests/responses */
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();
});

Without implementing the CORS access definitions we wouldn't be able to access the node application from other computers/devices so this is a VERY important declaration to place into the server.js file.

It's also important that we grant access for the following Http request types:

  • GET
  • DELETE
  • POST
  • PUT

Without these we won't be able to manage the MongoDB database through the different Http request calls sent from our Ionic mobile application to node.

Before we start defining the different routes for our node application let's take a few minutes to develop the Mongoose data model that we'll use to manage interfacing with the project's MongoDB database.

Developing our model

Within the project's server directory create a sub-directory named models.

Within the ionic-crud/server/models directory now create a new file named gallery.js.

We'll be using this file to define a model that can be used with the Mongoose software API to handle document retrieval and management (I.e. adding, updating and deleting a specific document) for the project's MongoDB database.

Open the ionic-crud/server/models/gallery.js file and add this database model logic as follows:

/* Import the Mongoose software module */
var mongoose 			=	require('mongoose'),

    /* Create a Mongoose Schema object for generating
       document rules as to what structure MUST be 
       expected when requesting/sending data to and from 
       the MongoDB database collection */
	Schema 				=	mongoose.Schema,

    /* Define the schema rules (field names, types and rules) */
	GallerySchema 		=	new Schema({
	   name   		: { type : String, required : true, max : 50 },
	   description	: { type : String, required : true },
	   thumbnail 	: { type : String, required : true },
	   displayed    : Boolean,
	   date 		: { type: Date, default: Date.now }
	});

/* Export model for application usage */
module.exports = mongoose.model('Gallery', GallerySchema);

You can find further information about the Mongoose API and data models here.

Now that we've created the required data modelling logic for interfacing with the project's MongoDB database we can return to the server.js file and start defining the required routes that will be used for handling client requests.

Defining the application routes

With the foundation for the ionic-crud/server/server.js file in place we can now start adding the necessary routes.

A route in ExpressJS terminology is an endpoint for a client request.

What this means in plain terms is that we can define specific URL's to handle specific requests. For example, if I want to retrieve all of the documents from the gallery collection in the project's MongoDB database I might define the following route:

apiRouter.get('/gallery', function(req, res) 
{
  // We'll place some logic in here to do something magical!
});

Breaking this down what we have are the following constituent parts of an ExpressJS route:

  1. The type of Http request that we want to intercept (I.e. get, post, put, delete etc)
  2. The path definition for the route ('/gallery')
  3. A callback function which provides the HTTP request and response parameters (shortened to req and res)
  4. Within the callback function we would then place the logic for that specific route (I.e. retrieving data from a MongoDB database, exporting data to a CSV file etc)

Further information about routing in ExpressJS can be found in the online documentation.

Let's declare the wrappers for each route definition that we'll be using then add the logic to each one in full over the following sections:

apiRouter.get('/gallery', function(req, res) 
{

});




apiRouter.post('/gallery', function(req, res) 
{


});




apiRouter.put('/gallery/:recordID', function(req, res) 
{

});




apiRouter.delete('/gallery/:recordID', function(req, res) 
{
  
});

Handling GET requests

Retrieving data from the MongoDB database is handled with Http GET requests to the /api/gallery 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 });

   });
});

Handling POST requests

Creating a new document within the MongoDB database is handled by retrieving posted data from Http POST requests to the api/gallery 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' });

   });

});

Handling PUT requests

Updating an existing document within the MongoDB database is handled by retrieving supplied data with Http PUT requests and parameters to the api/gallery route:

/* 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 });
         });
      }     

   });
});

Handling DELETE requests

Removing an existing document from the MongoDB database is handled by retrieving supplied data with Http DELETE requests and parameters to the api/gallery route:

/* 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 });

   });
});

The logic in full

Our completed ionic-crud/server/server.js file should now resemble the following (comments added to assist with understanding purpose of each supplied block of code):

// 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 });

   });
});




/* 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);

The server directory

By the end of this tutorial you should have an ionic-crud/server directory that resembles the following:

ionic-crud
    |
    | - app
    |
    | - server
           |
           | - config.js
           |
           | - models/
                  |
                  | - gallery.js
           |           
           | - node_modules/
           |
           | - package.json
           |
           | - server.js

To run the node application simply open your system CLI, navigate to the root of the server directory and execute the following command:

nodemon server.js

This will run the nodemon software which will listen for any changes to files within the server directory and re-launch the node application accordingly.

With the node application running silently in the background we can now move onto developing the Ionic mobile application that our project uses to interact with Node/MongoDB.

In summary

As part of our series on developing an Ionic mobile application using Node, ExpressJS & MongoDB we've covered a lot of ground in this tutorial.

We've created a node application that uses ExpressJS routes and Http request types to successfully communicate with the project's MongoDB database courtesy of Mongoose Schemas.

In the next tutorial we'll develop the Ionic mobile application that will be used to communicate with node to retrieve/manage data from MongoDB.

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