Node app development

So, we have everything set out; let's build the app.

Let's create our package.json file.

Always use the npm init --y command to generate boilerplate package.json file.

Here are the packages we require:

  • body-parser
  • express
  • mongoose

Let's install them. Run this command under your project directory to install the dependencies:

npm install --save body-parser express mongoose 

This is what my package.json looks like:

{ 
  "name": "url-shortener", 
  "version": "1.0.0", 
  "description": "A NodeJS + Express + MongoDB based URL shortener", 
  "main": "app.js", 
  "scripts": { 
    "test": "echo "Error: no test specified" && exit 1" 
  }, 
  "author": "shahid", 
  "license": "MIT", 
  "dependencies": { 
    "body-parser": "^1.14.1", 
    "express": "^4.13.3", 
    "mongoose": "4.2.9" 
  } 
} 

Let's create the configuration file. Name it config.js and place the code, shown as follows:

var config = {}; 
config.db = {}; 
config.webhost = 'http://localhost:3000/'; 
config.db.uri = 'copy the uri from Azure portal'; 
config.db.host = 'packt-mongodb-2.documents.azure.com'; 
config.db.name = 'url_shortener'; 
module.exports = config; 

Copy the URI from the Azure portal screen. Refer to the following screenshot for reference:

We have done the configuration. Let's write the code to connect to our MongoDB database of Cosmos.

Create a file called connection.js and place the following code in it:

const mongoose = require('mongoose'); 
const config = require('../config.js'); 
mongoose.connect(config.db.uri); 
 
module.exports = mongoose.connection; 

This code will return the connection object to the caller.

Next, we will create database schema codebase which is to ensure the MongoDB schema and data indexing.

Here is the codebase for the same. Place this in the file named url.js:

var mongoose = require('mongoose'); 
var connection = require('./connection'); 
var Schema = mongoose.Schema; 
 
connection.once('open',function() { 
  console.log("Connected...."); 
}); 
 
var CounterSchema = Schema({ 
    _id: {type: String, required: true}, 
    seq: { type: Number, default: 0 } 
}); 
 
var counter = mongoose.model('counter', CounterSchema); 
 
// create a schema for our links 
var urlSchema = new Schema({ 
  _id: {type: Number, index: true}, 
  long_url: String, 
  clicked: {type: Number, default:0 ,index: true}, 
  created_at: Date 
}); 
 
urlSchema.pre('save', function(next){ 
  var doc = this; 
  counter.findByIdAndUpdate({_id: 'url_count'}, {$inc: {seq: 1}},    function(error, counter) { 
      if (error) 
          return next(error); 
          console.log(counter); 
      doc.created_at = new Date(); 
      doc._id = counter.seq; 
      next(); 
  }); 
}); 
 
var Url = mongoose.model('Url', urlSchema); 
 
module.exports = Url; 

As you can see in the preceding code, we are using our connection object and printing the status of the connection on the console.

As we discussed in the database section, we have created two schemas. One is to maintain the global counter and the other is to maintain the actual link.

We are using the pre-function event of Mongoose to basically get the latest count of the URL value from the counter collection. This is to make sure that we never generate duplicate short URL.

At the end, we are exporting the database object.

Next, we will develop our application logic, that is, server code to handle the request and response from the user and call functions which we developed.

Here is the code base for the server. We are using the express node module to create server routes:

var express = require('express'); 
var app = express(); 
var path = require('path'); 
var bodyParser = require('body-parser'); 
var mongoose = require('mongoose'); 
var config = require('./config'); 
var base58 = require('./base58.js'); 
 
// grab the url model 
var Url = require('./models/url'); 
 
app.use(bodyParser.json()); 
app.use(bodyParser.urlencoded({ extended: true })); 
 
app.use(express.static(path.join(__dirname, 'public'))); 
 
app.get('/', function(req, res){ 
  res.sendFile(path.join(__dirname, 'views/index.html')); 
}); 
 
app.post('/api/shorten', function(req, res){ 
  var longUrl = req.body.url; 
  var shortUrl = ''; 
 
  // check if url already exists in database 
  Url.findOne({long_url: longUrl}, function (err, doc){ 
    if (doc){ 
      shortUrl = config.webhost + base58.encode(doc._id); 
 
      // the document exists, so we return it without creating a new entry 
      res.send({'shortUrl': shortUrl}); 
    } else { 
      // since it doesn't exist, let's go ahead and create it: 
      var newUrl = Url({ 
        long_url: longUrl 
      }); 
 
      // save the new link 
      newUrl.save(function(err) { 
        if (err){ 
          console.log(err); 
        } 
 
        shortUrl = config.webhost + base58.encode(newUrl._id); 
 
        res.send({'shortUrl': shortUrl}); 
      }); 
    } 
 
  }); 
 
}); 
 
app.get('/:encoded_id', function(req, res){ 
 
  var base58Id = req.params.encoded_id; 
 
  var id = base58.decode(base58Id); 
 
  // check if url already exists in database 
  Url.findOne({_id: id}, function (err, doc){ 
    if (doc) { 
      res.redirect(doc.long_url); 
    } else { 
      res.redirect(config.webhost); 
    } 
  }); 
 
}); 
 
var server = app.listen(3000, function(){ 
  console.log('Server listening on port 3000'); 
}); 

First, we need the module we require, such as express for web server or body parser to parse HTTP parameter values.

Next, we need three modules. Refer to the following code:

// our sweet config file. 
var config = require('./config'); 
// Base 58 decoding, will discuss it in next section 
var base58 = require('./base58.js'); 
// grab the url model 
var Url = require('./models/url'); 

The URL module actually stores the URL and their count as we discussed in the preceding section.

Next, we defined routes and hit upon those routes doing certain actions. For example, on a root page request, we send the HTML content back to the server.

Then, we defined two APIs, one to shorten the URL and another to decode it. Both of these routes call the model function present in the URI module.

Whether it's encoding or decoding, we are using the base58 module function. Let's see what that module does.

Here is the code of the base58.js module:

var alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; 
var base = alphabet.length; 
 
function encode(num){ 
  var encoded = ''; 
  while (num){ 
    var remainder = num % base; 
    num = Math.floor(num / base); 
    encoded = alphabet[remainder].toString() + encoded; 
  } 
  return encoded; 
} 
 
function decode(str){ 
  var decoded = 0; 
  while (str){ 
    var index = alphabet.indexOf(str[0]); 
    var power = str.length - 1; 
    decoded += index * (Math.pow(base, power)); 
    str = str.substring(1); 
  } 
  return decoded; 
} 
 
module.exports.encode = encode; 
module.exports.decode = decode; 

Here, we are doing actual encoding and decoding of the URL. This function encodes the string to the specified code and, once we hit the code, it returns the exact same URL back. This function and module is the heart of our code base.

Let's look at the user front facing application code, that is, our HTML code. Here is the index.html file and its code:

<!DOCTYPE html> 
<html> 
<head> 
  <meta charset="utf-8"> 
  <meta http-equiv="X-UA-Compatible" content="IE=edge"> 
  <meta name="viewport" content="width=device-width, initial-scale=1"> 
 
  <title>URL Shortener - coligo.io</title> 
  <link href='https://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'> 
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> 
  <link href="css/styles.css" rel="stylesheet"> 
</head> 
<body> 
 
  <div class="site-wrapper"> 
 
    <div class="site-wrapper-inner"> 
 
      <div class="main-container"> 
 
        <div class="inner cover"> 
          <span class="glyphicon glyphicon-link"></span> 
          <h1>URL Shortener</h1> 
           
          <div class="row"> 
 
            <div class="col-lg-12"> 
              <div class="input-group input-group-lg"> 
                <input id="url-field" type="text" class="form-control" placeholder="Paste a link..."> 
                <span class="input-group-btn"> 
                  <button class="btn btn-shorten" type="button">SHORTEN</button> 
                </span> 
              </div> 
            </div> 
 
            <div class="col-lg-12"> 
              <div id="link"></div> 
            </div> 
 
          </div> 
 
        </div> 
 
      </div> 
 
    </div> 
 
  </div> 
 
  <script src="https://code.jquery.com/jquery-2.2.0.min.js"></script> 
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script> 
  <script src="javascripts/shorten.js"></script> 
</body> 
</html> 

We have done our code; let's test run it.

Run the following command in the terminal to start the application:

node app.js 

It should print the following in the console:

Now, open up the browser and hit localhost:3000 (replace localhost with URL if deployed on server). You should see a similar screen to the one shown in the following screenshot:

Now, add an URL and hit the SHORTEN button. You should receive the encoded link. Refer to the following screenshot for reference:

Click on that URL and it will take you to the destination URL.

Let's check out the database entries. Open up Azure portal and go to the Cosmos DB MongoDB collection. You should see the entries over there similar to what is shown in the following screenshot:

Add more URLs to see more entries and diverse encoding in the IDs.

Let's look over security and permission.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.117.56