Birdie Sanders

NodeJS tutorial – MongoDB driven Employee Database

This is a simplistic database-driven employee website. It does not, at all, have all of the functions a real employee database would. Nor is the database well designed for actual use. This is just designed to show the CRUD functions that you can use with MongoDB and NodeJS.

(CRUD: Create, Read, Update, Delete)

Disclaimer: I included jQuery links, and Bootstrap in this particular tutorial because it involves several pages and makes things look nicer. However, I did not write any jQuery code, and only included the link because some of Bootstrap’s JavaScript library requires it. Basically, I wanted the web pages to look decent, and Bootstrap is the easiest way to make that happen.

Directory setup and files:

  • /app.js
  • /server.js
  • /routes
    • /routes.js
  • /views
    • /form.html
    • /main.html
    • /people.html
    • /removepeople.html
    • /search.html
    • /subresult.html

On to the code.

var express = require('express');
var app = express();
var engine = require('consolidate');
var MongoClient = require('mongodb').MongoClient;
var ObjectID = require('mongodb').ObjectID;
var bodyParser = require('body-parser');
var url = "mongodb://localhost:27017/People";

You’ll see that in this tutorial, unlike the last one, we’re using a new database called people. (The MongoDB url allows you to specify the database you want to connect to.) We’re also using body-parser, which will allow us to get POST data from the forms that we are going to be building.

We also call “ObjectID”. This is going to allow us to query the database for “_id” fields. I’ll show you how to do that in the routes section.

app.engine('html', engine.nunjucks);
app.set('view engine', 'html');
app.set('views', __dirname + '/views');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended:true
}));

This sets up the application’s usages. We’re setting our HTML template engine to nunjucks, because I like it. We’re also setting the default extension of our view engine to HTML files, so we don’t have to write “.html” after every view call. Then we set our view folder to ‘{current directory}/views’ – And that’s where we’re going to shove all of our HTML files later.

The new pieces hear are in app.use(). We’re telling our application to use pieces of Body-Parser – Specifically JSON and urlencoded. Now, Body-Parser is middleware for express that allows us to get to the meat of the “body”. In NodeJS, the body normally comes through as a ReadableStream and is not easy to deal with. Body-Parser makes it easy for us to grab our elements out of the body, which is how we’re going to play with our Form Data.

You’ll notice in bodyParser.urlencoded we’ve passed a parameter of “extended: true”. You can read more about that particular attribute here, but the long-and-short of it is that we can play with urlencoded data like JSON data. (Not that we do it in this tutorial.) You can also set it to false if you’d rather. This is a required parameter; if you don’t include it, you’ll get a lovely warning in your console window that says “body-parser deprecated undefined extended: provide extended option.” – it will then tell you the file and line where the error took place. We like writing error free code, so include the command, even if your code works without it.

var queryMongoDB = function(type, doc, filter, collection, callback) {
  MongoClient.connect(url, function(err, db){
    if(type == "insert")
    {
      db.collection(collection).insert(doc, function(err, results){
        callback(results);
      });
    }
    else if(type == "upsert")
    {
      db.collection(collection).update(filter, doc, {'upsert': true }, function(err, results){
        callback(results);
      });
    }
    else if(type == "remove")
    {
      db.collection(collection, function(err, collection){
        collection.remove(filter, function(err, results){
          callback(results);
        });
      });
    }
    else
    {
      db.collection(collection).find(filter).toArray(function(err, docs) {
        callback(docs);
      });
    }
  });
}

This MongoQuery is a bit more complicated than in past tutorials. I’m gonna break it down for you. First, lets look at the function parameters: Type, query, filter, collection and callback.

Type: The type of query we’re going to do: A pure “insert”, for brand new records, an “upsert”, for an update/upsert, “remove”, for removing a record, and then a general “find”, to get data out of the database.
Doc: The JSON document that we want to insert/update in the database.
Filter: Filters are used to limit the data we’re looking for.
Collection: Whichever collection in the database we want to look at. This allows us to use multiple collections with one piece of code.
Callback: This allows us to set up a function that will happen as soon as this code is completed.

We then call our connect method, pass it a URl, and a function that gets an error handler and the database. Once we’re in there, we decide which db.collection call we want to use based on the type.

Our insert statement just inserts the query, which is a JSON document that is going to be stored directly in the collection. One of the nice things about node is that the following two documents can exist within the same collection without conflict:

[
  {
    "_id": "5707ef7f0afc5347981894a1",
    "index": 0,
    "guid": "a3adaf48-5a39-4aaa-bce5-2dabd54f324a",
    "isActive": false
  }
]

 

{
    "_id": "5707efb6a3e8458ef329cd54",
    "picture": "http://placehold.it/32x32",
    "age": 29,
    "eyeColor": "brown",
    "name": {
      "first": "Paul",
      "last": "Gibson"
    },
    "company": "OTHERWAY",
    "email": "[email protected]"
  }

That’s a pretty neat feature, and means you can spend less time “planning” your database, and more time working on your code.

Next is the upsert, which uses a filter. The filter that we use in this tutorial is the ‘_id’ field. If we’re submitting data with an ID, then we want to update that field. If it doesn’t exist, we want to insert it. You could also modify the filter to update all of a specific field in a database. So, if we filters on “first_name” : “James”, for example, and passed a query of “nickname” : “Jim”, all documents where first_name == “James” would get a field named nickname with a value of “Jim”. (Or, if they already had a nickname field, it would get updated to “Jim”. If there wasn’t a match on that criteria, it would insert it into the database. Of course, it would just say “nickname” : “Jim” if we were to query it, so you need to make sure you’re passing everything the document needs in order to work. (In this example, you’d want your document to look like this:

{
  "first_name" : "James",
  "nickname" : "Jim"
}

But BE CAREFUL. If you update a record with a new document, it will not update a specific field, it will update the whole document. So you need to pass the entire document back in. In another tutorial, we’ll show you how to update a single field. For now, just remember: The new document replaces the old document.

Remove, like insert, is straight forward: Pass it a document that you want removed. Because this is going to “filter” the data in our database, we’re using the “filter” variable to pass this through. In our application, we’re going to remove based on ID only, but like insert and upsert, you can filter based on any field in your database.

Find is simple – we query our database for documents based on specific criteria. That’s why I use “filter” again. (You can use “query” if you prefer; it’s more commonly used.)

var routes = require("./routes/routes.js")(app, queryMongoDB, ObjectID);

Of course, we need to call our routes, and pass our app, queryMongoDB, and ObjectID variables to our routes function. That gives us access to use them in the routes section.

var appRouter = function(app, queryMongo, ObjectID)
{
  app.get('/', function(req, res) {
    res.render('main');
  });
  app.post('/', function(req, res) {
    res.render('main');
  });
  app.get('/people/', function(req, res) {
    queryMongo('retrieve', {}, {}, 'PersonalInformation', function(results){
      res.render('people', { 'people' : results});
    });
  });
  app.get('/search', function(req, res) {
    res.render('search');
  });
  app.post('/search', function(req, res) {
    var filter = '';
    var doc = {[req.body.searchtype] : req.body.searchtext };
    if(req.body.searchtype == '_id')
    {
      doc = {[req.body.searchtype] : new ObjectID(req.body.searchtext)}
    }
    var type = 'retrieve';
    var collection = 'PersonalInformation';
    queryMongo(type, doc, filter, collection, function(results){
      res.render('people', {'people' : results});
    });
  });
  app.get('/submit/', function(req, res) {
    res.render('form');
  });
  app.post('/submit/', function(req, res) {
    var filter = {};
    var type = 'insert';
    if(req.body.id != '')
    {
      filter = { '_id' : new ObjectID(req.body.id) };
      type='upsert';
    }
    var doc = { 'first_name' : req.body.firstname, 'last_name' : req.body.lastname, 'email' : req.body.email, 'hire_date' : req.body.hiredate, 'notes' : req.body.notes };
    var collection = "PersonalInformation";
    queryMongo(type, doc, filter, collection, function(results) {
      var item = 'Submission failed. Reason unknown.';
      if(results.result.ok == 1 && results.result.n == 1)
      {
        item = 'Your submission has been added to the database.'
        if(results.result.nModified == 1)
        {
          item = 'Record has been updated.';
        }
      }
      res.render('subresult', { 'success' : item });
    });
  });
  app.post('/retrieve/', function(req, res) {
    var filter = {'_id' : new ObjectID(req.body.objid) };
    var doc = '';
    var type = 'retrieve';
    var collection = "PersonalInformation";
    queryMongo(type, doc, filter, collection, function(people) {
      var person = people[0];
      res.render('form', person);
    });
  });
  app.get('/remove', function(req, res) {
    queryMongo('retrieve', {}, {}, 'PersonalInformation', function(results){
      res.render('removepeople', { 'people' : results});
    });
  });
  app.post('/remove/', function(req, res) {
    var filter = {'_id' : new ObjectID(req.body.id) };
    var doc = '';
    var type = 'remove';
    var collection = "PersonalInformation";
    queryMongo(type, doc, filter, collection, function(results) {
      var item = 'Employee removal failed. Reason unknown.';
      if(results.result.ok == 1 && results.result.n == 1)
      {
        item = 'Employee has been removed.'
      }
      res.render('subresult', { 'success' : item });
    });
  });
  app.use(function(req, res) {
    res.sendStatus(404);
  });
}


module.exports = appRouter;

And that is the whole Routes page. I’m going to do a couple of specific callouts, but if you need more information, please refer to previous tutorials. They’re smaller and go into greater route details.

var doc = {[req.body.searchtype] : req.body.searchtext };
if(req.body.searchtype == '_id')
{
  doc = {[req.body.searchtype] : new ObjectID(req.body.searchtext)}
}

This piece of code has most of the new stuff in it. req.body.variableName can be used to get form data out of the body, which gets sent with the request. Form data is transmitted by default as enctype “x-form-www-urlencoded”. Body-Parser allows us to call the form data by the “name” field from each Form element and get the respective data.

You will also notice that we are using ObjectID. When you query MongoDB by the _id field, you’re not actually using the ID. You’re using an ObjectID that has a parameter passed to it. We are shown that parameter when we query a document. So, something like this will not work:

{
  "_id" : "5703fda1727aabd91d1ff17e"
}

Instead, we have to pass an Object through that will query the database correctly. That’s why we have the ObjectID parameter. When passed through to the database like this, it returns the data we’re expecting. Do be careful with this: MongoDB allows you to overwrite this field with pretty much anything.

Each of the forms we use don’t include anything new that you really need to know about – some of them use JavaScript to pass data, but they don’t need to be explained. I’m including them below with their names. I hope you enjoyed this tutorial!

subresult.html

{% extends "views/main.html" %}

{% block details %}
  <div>
    <h1>{{success}}</h1>
  </div>
{% endblock %}

search.html

{% extends "views/main.html" %}

{% block header %}
  <div class="jumbotron">
    <h1>Search</h1>
    <p>Here you can search for information based on Employee ID, First Name, Last Name, E-Mail Address, or Hire Date.</p>
  </div>
  <form action="../search" method="POST">
    <div class="form-group">
      <div class="col-md-6 col-md-offset-3">
        <div class="input-group">
          <div class="radio">
            <label class="radio-inline">
              <input type="radio" name="searchtype" id="fname" value="first_name" checked>
              First Name
            </label>
            <label class="radio-inline">
              <input type="radio" name="searchtype" id="lname" value="last_name">
              Last Name
            </label>
            <label type="radio-inline">
              <input type="radio" name="searchtype" id="email" value="email">
              E-Mail
            </label>
            <label type="radio-inline">
              <input type="radio" name="searchtype" id="hiredate" value="hire_date">
              Hire Date
            </label>
            <label type="radio-inline">
              <input type="radio" name="searchtype" id="_id" value="_id">
              ID
            </label>
          </div>
        </div>
        <div class="input-group">
          <span class="input-group-addon" id="search">
            <span class="glyphicon glyphicon-search" aria-hidden="true">
            </span>
          </span>
          <input type="text" class="form-control" name="searchtext" placeholder="Search..." aria-describedby="search" />
          <span class="input-group-btn">
            <button type="submit" class="btn btn-default">
              Search
            </button>
          </span>
        </div>
      </div>
    </div>
  </form>
{% endblock %}

people.html

{%extends "views/main.html" %}

{% block details %}
  {% for person in people %}
    <div class="col-md-2 pull-left">
      <div class="panel panel-default">
        <div class="panel-heading">
          {{person.first_name}} {{person.last_name}}
        </div>
        <div class="panel-body text-center">
          <button class="btn btn-default" onclick="PostData('{{person._id}}');">View Employee</button><br/>
        </div>
      </div>
    </div>
  {% endfor %}

<script>
  function PostData(id)
  {
    var newForm = document.createElement("form");
    newForm.method = "POST";
    newForm.action = "../retrieve/";
    var formInput = document.createElement("input");
    formInput.setAttribute('type', 'hidden');
    formInput.setAttribute("name", "objid");
    formInput.setAttribute("value", id);
    newForm.appendChild(formInput);
    document.body.appendChild(newForm);
    newForm.submit();
  }
</script>
{% endblock %}

removepeople.html

{%extends "views/main.html" %}

{% block details %}
  {% for person in people %}
    <div class="col-md-2 pull-left">
      <div class="panel panel-default">
        <div class="panel-heading">
          {{person.first_name}} {{person.last_name}}
        </div>
        <div class="panel-body text-center">
          <button class="btn btn-default" onclick="PostData('{{person._id}}');">Remove</button><br/>
        </div>
      </div>
    </div>
  {% endfor %}

<script>
  function PostData(id)
  {
    var newForm = document.createElement("form");
    newForm.method = "POST";
    newForm.action = "../remove/";
    var formInput = document.createElement("input");
    formInput.setAttribute('type', 'hidden');
    formInput.setAttribute("name", "id");
    formInput.setAttribute("value", id);
    newForm.appendChild(formInput);
    document.body.appendChild(newForm);
    newForm.submit();
  }
</script>
{% endblock %}

form.html

{% extends "views/main.html" %}

{% block details %}
  <form class="form-group" action="../submit" method="POST">
    <input type="text" style="display:none;" name="id" id="id" value="{{_id}}" />
    <input type="text" style="display:none;" name="collection" id="collection" value="PersonalInformation" />
    <div class="col-md-4 col-md-offset-2">
      <input class="form-control" type='text' name='firstname' id='firstname' value="{{first_name}}" placeholder="First Name..." />
    </div>
    <div class="col-md-4">
      <input class="form-control" type='text' name='lastname' id='lastname' value="{{last_name}}" placeholder="First Name..." />
    </div>
    <br/>
    <br/>
    <div class="col-md-8 col-md-offset-2">
      <input class="form-control" type='text' name='email' id='email' value="{{email}}" placeholder="E-Mail Address..." />
    </div>
    <br/>
    <br/>
    <div class="col-md-4 col-md-offset-2">
      <input class="form-control" type='text' name='hiredate' id='hiredate' value="{{hire_date}}" placeholder="Hire date..." />
    </div>
    <br/>
    <br/>
    <div class="col-md-8 col-md-offset-2">
      <textarea  class="form-control" id="notes" name='notes' rows="3" placeholder="Notes...">{{notes}}</textarea>
    </div>
    <div class="col-md-1 col-md-offset-9">
      <input class="btn btn-default" type='submit' value="submit" />
    </div>
  </form>
{% endblock %}

main.html

<head>
  <!-- jQuery, minified. Doesn't work with ie9 -->
  <script   src="https://code.jquery.com/jquery-2.2.3.min.js"   integrity="sha256-a23g1Nt4dtEYOj7bR+vTu7+T8VP13humZFBJNIYoEJo="   crossorigin="anonymous"></script>

  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">

  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">

  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
</head>
<body>
  <nav class="navbar navbar-default">
    <div class="container-fluid">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
          <span class="sr-only">Toggle Navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
      </div>
      <div class="collapse navbar-collapse" id="navbar-collapse">
        <ul class="nav navbar-nav">
          <li><a href="../">Home</a></li>
          <li><a href="../search">Search</a></li>
          <li><a href="../people">List All</a></li>
          <li><a href="../submit">Add New</a></li>
          <li><a href="../remove">Remove</a></li>
        </ul>
      </div>
    </div>
  </nav>
  <div class="container">
    {% block header %}
      <div class="jumbotron">
        <h1>
          Employee Website
        </h1>
        <p>
          This app enables you to add employees to your workforce.
        </p>
      </div>
    {% endblock %}

    {% block details %}
    <div class="col-md-5 col-md-offset-1">
      <div class="panel panel-default">
        <div class="panel-heading">
          Employee Records
        </div>
        <div class="panel-body">
          It is important for any business to keep good employee records. This system will provide you with all of the necessary tools for maintaining an orderly database of employe records.
          <br/>
          <br/>
          We hope you enjoy using this system.
        </div>
      </div>
    </div>
    <div class="col-md-5">
      <div class="panel panel-default">
        <div class="panel-heading">
          Features
        </div>
        <div class="panel-body">
          There are several different features that will give you the ability to maintain an employee database.
          <ul>
            <li>Add Employee</li>
            <li>Edit Employee</li>
            <li>List Employees</li>
            <li>Remove Employees</li>
          </ul>
          The employee fields consist of:
          <ul>
            <li>First Name</li>
            <li>Last Name</li>
            <li>E-Mail Address</li>
            <li>Hire Date</li>
            <li>Notes</li>
          </ul>
        </div>
      </div>
    </div>

    {% endblock %}

    {% block results %}
    {% endblock %}
  </div>
</body>

Thanks again for reading! I appreciate it. If you have any questions, feel free to hit me up on Twitter or in the Comments!

spacer

Leave a reply