Node-Express-MongoDB Project Setup with Heroku Deployment

Reference

Here is the step-by-step for creating a new Node.js app using Express and MongoDB. Because I did this a long time ago, and had to remember.

Quick Reference:

Prerequisites

  • Update Node to Long Term Stable version (nvm installation is simplest method)
  • Check npm version with npm -v and update if necessary
  • Check for nodemon with nodemon -v. If it’s not there, install it globally with npm install -g nodemon
  • Install MongoDB Community edition. You need to be able to successfully run a mongod server and access a mongo shell using the mongo command. Cloud Atlas is not required (ignore that in the MongoDB documentation). I wrote about this previously.

Set Up Express

  • Create a project folder and cd into it:
    mkdir new-project
    cd new-project
  • Initiate a git repository: git init
    • Another option for the previous 2 steps is to create an empty GitHub repo and clone it to your machine.
  • Add .gitignore file to ignore system files like .DS_Store, etc.
  • Add .README file, can be empty to start
  • Commit project folder as Initial commit and push to remote repo as necessary. From here no more commit/push instructions will be included, but commit changes often!
  • Run npm init and follow instructions (answer questions) to create package.json file. Or npm init -y to skip the questionnaire.
    • NOTE: The steps below assume the entry point is set to app.js
  • Add node_modules to .gitignore file
  • Install Express by running npm install express --save
  • Create app.js file in main project folder and add server boilerplate:
    var express = require("express");
    var app = express();

    app.get("/", function (req, res) {
    res.send("hello world");
    });

    var port = process.env.PORT || 3000;
    app.listen(port, function() {
    console.log("App running on port " + port);
    });
  • Save file then run nodemon app.js
  • Open localhost:3000 in browser; if you see hello world, the basic setup was successful.

Scaffold the Routing

Rather than putting all of the routes in the main app.js file, this can be modularized.

  • Make a routing index file:
    mkdir routes
    touch routes/index.js
  • Add node modules and an exporter to routes/index.js, and move the routing code from app.js to this file:
    var express = require("express");
    var router = express.Router();

    router.get("/", function (req, res) {
    res.send("hello world");
    });

    module.exports = router;
  • Access and use the exporter in the root folder’s app.js file:
    // Require Routes
    var indexRoutes = require("./routes/index");

    // Run app
    app.use(indexRoutes);
  • Run nodemon app.js and refresh the page at localhost:3000; you should see the same hello world

Set Up Views, Template Scaffold, & Static Files

Express looks for everything it should display on the front end to be stored in a views directory. We can also use the ejs package for templating. We also need to tell Express where to find static files like CSS, images, and local JavaScript.

  • In root project folder run npm install ejs --save

  • Tell Express to use ejs in the /app.js file: app.set("view engine", "ejs");

  • Set up the basic scaffolding for the page templates:

    mkdir views
    touch views/index.ejs
    mkdir views/partials
    touch views/partials/head.ejs
    touch views/partials/header.ejs
    touch views/partials/footer.ejs
  • Create the basic views/index.ejs file accessing the partials:

    <% include partials/head %>
    <% include partials/header %>

    <h4>This is where the main content will go</h4>

    <% include partials/footer %>
  • Set up the other partials with content in each, to ensure everything is linked:
    views/partials/head.ejs:

    <!DOCTYPE html>
    <html>
    <head>
    <title>News App</title>
    </head>

    views/partials/header.ejs

    <body>
    <header>
    <nav>
    <h1>This is where the navbar will go</h1>
    </nav>
    </header>
    <main>

    views/partials/footer.ejs

      </main>
    <footer>
    <h6>This is where the footer will go</h6>
    </footer>
    <!-- BEGIN SCRIPTS -->
    </body>
    </html>
  • Tell Express where to access the static files and set up a home for each type:
    /app.js:

    app.use(express.static(__dirname + "/public"));

    From root directory in command line:

    mkdir public
    mkdir public/css
    mkdir public/img
    mkdir public/js
    touch public/js/scripts.js
    touch public/css/styles.css
  • Add test CSS and JS code and then link their files to views/partials/head.ejs and views/partials/footer.ejs respectively:
    public/js/scripts.js:

    alert("HELLO!");

    public/css/styles.css:

    header {
    background-color: blue;
    }
    main {
    background-color: orange;
    }
    footer {
    background-color: teal;
    }

    Add to views/partials/head.ejs:

    <link rel="stylesheet" href="/css/styles.css">

    Add to views/partials/footer.ejs:

    <link rel="stylesheet" href="/js/scripts.css">
  • From command line run nodemon app.js and open localhost:3000 in a browser. After getting and closing the HELLO! alert, should see:
    boilerplate screenshot

Add 404

Better add this in now so it’s not an afterthought.

  • Create views/404.ejs file:
    <% include partials/head %>
    <% include partials/header %>

    <h2>404 Error</h2>
    <h3>This page does not exist</h3>

    <% include partials/footer %>
  • Add 404 catch-all to /app.js file. Note this must remain at the bottom, even when more routing files are added later.
    app.use(function(req, res) {
    res.render("404");
    });

Add Environment Variables Manager

To keep some things secret from the public when code is published to GitHub, environment variables can be used, and dotenv is an npm package that helps manage these visibly.

  • Install the dotenv package as a dev dependency:

    npm install dotenv --save
  • Create a file called .env in the project’s root directory, and add all of the secret info as key-value pairs:

    PORT=5000
    NODE_ENV=development
    MONGODB_URI=mongodb://localhost/secret-app-address
    SESSION_SECRET=seCret-p@s5w0rd
  • If other people will be collaborating on the project, also create a file called .env.default in the project’s root directory, and copy/paste from the .env file, omitting the values:

    PORT=
    NODE_ENV=
    MONGODB_URI=
    SESSION_SECRET=

    Make sure your collaborators get the secret sauce via secure channels so that they can replicate your dev environment on their own local machines.

  • SUPER IMPORTANT! Add the new .env file to .gitignore

  • Make the app use the .env file by invoking it in app.js. I usually do this conditionally by creating a dev environment, and using the environment files here only; later when deploying the app to a remote server, new values can be assigned to each key specifically for the production environment:

    var nodeEnv = process.env.NODE_ENV || "development";
    if (nodeEnv === "development") {
    require("dotenv").config()}
  • Add Mongoose to the project, this will be the ORM to manage MongoDB from Node:
    npm install mongoose --save
  • In the /app.js file, require mongoose and connect to the database server. Also include the database model(s) which will be created in the next step:
    var mongoose = require("mongoose");
    var TestItem = require("./models/test");
    mongoose.connect(process.env.MONGODB_URI, {useMongoClient: true});
  • Create a directory for the Mongoose models and add the file referenced in the previous step:
    mkdir models
    touch models/test.js
  • Create a database model in the newly created file models/test.js:
    var mongoose = require("mongoose");

    // Test Schema & Model Setup
    var testItemSchema = new mongoose.Schema({
    name: String,
    age: Number
    });

    module.exports = mongoose.model("TestItem", testItemSchema);
  • To test everything is linked correctly, add a dummy db entry into the very bottom of the app.js file:
    // TEMPORARY DATABASE TEST -- DELETE WHEN REAL DATABASE MODELS ARE READY
    var george = new TestItem({
    name: "george",
    age: 47
    });
    george.save().then(() => console.log("DATABASE IS LINKED & SAVING DATA!"));
    Look for this console message on the Node server. If it’s there, that’s all finished!

Deploy To Heroku

Since I broke everything and had to reinstall the Heroku CLI, let’s start at the beginning.

To install Heroku I used Homebrew:

brew install heroku/brew/heroku

// And after installation...
heroku --version

// And finally to authenticate...
heroku login

Then I followed the prompts to log in with my existing Heroku account.

But actually…this isn’t completely necessary because it’s also possible to do the deployment by setting Heroku to watch a GitHub repo and automatically build and deploy with each push to the repo. Once completed, you can see build and server logs in the web interface; that said, installing the Heroku CLI is helpful, because you can see the logs a bit quicker by running heroku logs from the command line.

To enable automatic builds from GitHub, open the Deploy tab and follow the steps to authenticate GitHub access from this Heroku app. A bit farther down the page, click Enable Automatic Deploys to your chosen branch so that Heroku will run a new build with each code push. If you ever want to change which branch deploys it’s quite simple—just click Disable Automatic Deploys, change the branch, and enable them again.

SO…back on the Heroku web dashboard, we need to add a MongoDB database. Open the Resources tab and then Find More Add-Ons. mLab offers small free MongoDB servers perfect for a demo app, so find this on the list and add it to the project. When this is done in this method, Heroku automatically adds the new MONGODB_URI environment variable to this app, so as long as the process key has the same name for the local dev environment, the app will be able to get the correct production value and access the web-hosted MongoDB server.

Now’s a good time to add in any other environment variables that you may need (like API keys, etc.); from the Settings tab, click Reveal Config Vars to add or edit these.

Heroku is configured by default to run the npm start script to start your app. Make sure this script exists in the root folder’s package.json file. In this case it will point to app.js:

"scripts": {
"start": "node app.js"
}

Ans that’s it! Once you have a successful build, you can click the Open App button from the top of the Heroku dashboard and you should see the same demo app page that you loaded locally earlier.

The End

These steps set up the basic boilerplate for a MEN app. Obviously a lot more can be added. For now this is good though!