An introduction to Node.js, Express, MongoDB, and more

Node and express can be incredibly confusing to a newcomer, even one with programming experience, I struggled a lot to understand the basics at first. Now that I have a firmer understanding of the framework, I thought I’d write an introduction to node, express, and some of my most used modules, the kind of thing I wish I could have read when I started learning node.js. I won’t be going over basic javascript syntax, and I’ll assume you have an intermediate understanding of programming in general.

What we’ll make

We’ll be creating an extremely simple express app. A user will log in and be directed to a home page which shows a list of all other users currently connected.

More specifically : There will be a log in screen, which will use passport to check the user’s credentials against a MongoDB database. Our app will direct the user to a home page if the log in succeeds. On the home page, we’ll be using socket.io to connect to our web server and maintain a list of current users. We’ll use Hogan, a JavaScript templating engine, to display our list of users on the site. We’ll also touch on sessions so that passport and socket.io work correctly.

What’s covered

  • Dependency installation and usage
  • Basic database setup in MongoDB
  • Local Passport authentication
  • Express-generator
  • Express middleware
  • Hogan/Mustache
  • Node package manager
  • Socket.io

Setting up the file structure

This guide assumes you have node and npm installed. If you don’t, the installation is very simple, just follow the instructions here.

We’ll be using the express framework. Express has a great tool called express-generator which will give us a template of a very basic web app to start with. First, install the express-generator with npm :

Keep in mind you may have to run the above command with sudo to make it work. Next, we’re going to create a template which uses Hogan for the templating engine. Hogan is a templating engine developed by Twitter. It’s my personal favorite, but you can leave this out if you prefer Jade, or add -e for ejs.

Now, you have a basic web app and all the neccessary files for this tutorial. After this, you’ll want to cd into the directory you just created and run sudo npm install to install dependencies. If you want to see the result so far, run npm start to start the app, and go to http://localhost:3000, you’ll see a page which says ‘welcome to express’.

Installing dependencies

Now you have a template for a web app, but you’ll need a few additional dependencies to track users currently online. Run the following commands to install the required dependencies into the node_modules directory.

What each of these do will become apparent later on, but I’ll give a quick rundown of each for now. Cookie and cookie-signature allow you to store and read cookies, which is required for sessions. Express-sessions is middleware for setting up sessions, and uses the cookie and cookie-signature dependencies that were installed first. Passport is middleware which makes authentication simple. Socket.io allows you to detect when a user has connected, and when they have disconnected. Mongoose is middleware for connecting to a MongoDB database.

All of these dependencies are now installed. However, they are not yet imported into your app.js file. Do that by adding the following lines to the top of your app.js file, below the line var app = express() :

 

Setting up authentication

For the database, we’ll be using MongoDB. If you do not have MongoDB installed, follow the instructions here. We’ll need to set up a database and collection for our users. So start mongo with mongo, and execute the following commands :

Now we have a database that contains a collection, which contains three users. We’ll be using this with passport to check if the user exists. Next, we have to create the login view, which we’ll name login.hjs, and place in our views directory. It won’t be a pretty login screen, but here’s the HTML for that file :

In our index.js file in the routes directory, we’ll add a route which will use this file. Here’s the code for that :

If we start our app with npm start now, then go to http://localhost:3000/login, we’ll see our log in screen. However, nothing is connected to the database yet. We’ll take care of that next, using mongoose. Add the following lines to your app.js file, below all the require statements :

After that, we need a way for passport to use that database to determine if the log in credentials are okay, as well as what it should do if they are valid/invalid. Add this below the code for mongoose that you just wrote :

Passport now knows what to do when authentication succeeds or fails. When the post request is sent from the form, passport.authenticate() will be called, and that will use the LocalStrategy we defined to check if the username and password entered is valid. It will redirect to /loginSuccess if it is valid, and /loginFailure if it isn’t. The lines for the bodyParser module make it possible for passport to access the username and password fields of the form we created.

Since passport now knows where to redirect if a login fails or succeeds, we have to define what to return for each of those paths in our index.js file. We’ll direct the user back to our login page if it fails. If it succeeds, we’ll direct the user to a page which will show all the users currently online. Here’s the code for that :

You’ll notice that we don’t display a page for /loginSuccess. Instead, we redirect the request. We don’t want the URL to be www.whatever.com/loginSuccess when the user logs in, more likely we’ll be displaying some sort of home page, and we’d like the URL to reflect that. We could just have passport use /home instead, but often web apps will have some sort of logic when a user logs in, so that can be placed before res.redirect('home');. We don’t have a home page yet, so let’s add that route too :

Here, instead of just displaying /home immediately, we first check if req.user exists. If it does, then the user is logged in and we display the home page. If it does not, we redirect to the log in page.

Now, when you run the app with npm start, then try logging in with invalid credentials, you’ll see the URL change to localhost:3000/loginFailure. But when they’re valid, it seems to do nothing, the URL is still localhost:3000/login. If you look at the console, you’ll see it’s going to loginSuccess, redirecting to home, then being redirected to log in again. The reason for this is sessions aren’t yet set up, so <code>req.user</code> is undefined. Add this code to your app.js file, above the passport code :

Now the log in works fine, but you’ll get an error when you get to the home page, because we haven’t set up that page yet. We’ll get to that later, we’re done with all the passport code now.

Sharing the online users variable

We need a way to store every user that’s connected, and remove every user that disconnects. Our implementation will be a simple array of all usernames. You could use a database if you have a lot of users, or you could have an array of dictionaries which can hold more data about each user, but we’re keeping the implementation simple for now.

In your app.js file, create an empty array called onlineUsers, below all the require statements, above the mongoose code :

Socket.io server-side

Now that we have authentication all set up, we need a way to detect when a user has connected and disconnected. We’ll be using socket.io to detect connect and disconnect events. Including this will require rearranging our project somewhat. We’re going to be moving the code for starting the server from bin/www into app.js. For larger projects this is not wise; in this case, it’s a simple solution to a problem that would otherwise require a complex solution.

Insert this code into your app.js file, below the require statements :

This is just the code to start the server, which is usually placed in bin/www. You’ll notice it’s calling debug, which we haven’t required yet. Add this code to the top of your app.js file :

Now, we moved the server code to our app.js file. We no longer need bin/www, and starting our app now will result in an error, as we’re telling it to listen on the same port twice. Open the package.json file in the root directory of your project, and change this :

To this :

We’ve already installed socket.io as a dependency, so now we have to require it in our app and set it up so that it listens to requests on our server. We can do that with this line of code, inserted directly below the passport code in app.js :

Socket.io is now listening on the same port as our server. Now, we could export the socket.io functionality into another file, but since this is a simple web app, we’re going to keep it in app.js.

After the last line of code you wrote, enter this code :

You’ll notice that there’s a lot of work being done to get the session. Unfortunately, there’s no easy way to access the current session with socket.io, and the above code is the cleanest solution I’ve found, courtesy of hexacyanide on SO.

Socket.io client-side

Socket.io is now all set up to receive connection events. However, socket.io isn’t yet implemented on the client-side, so connect and disconnect events will never be called. We’ll create the home page now. Create a file called home.hjs in the views directory of the project. Edit this file to look like this :

Now, whenever a user connects, the function which was given to the io.on(‘connection’) event will be called. It will get the session, retrieve the user, then push the username into the list of all users if it is not there already. Also, it will log all online users. Now, we need a way to get that information to the user. This is where Hogan comes in. Hogan will allow us to display the contents of our onlineUsers array.

After the header in home.hjs, add the following line :

Now, go to the routes/index.js file, and change the line res.render('home', null); to res.render('home', {users : onlineUsers});. If this was in app.js, that would be all we would need. However, since it’s in a different file, we need to require app.js so we can access the onlineUsers variable. directly above the res.render('home'... line, enter this line of code :

To make the onlineUsers variable accessible to other modules, we need to export it from app.js, add this to the bottom of your app.js file :

Now, when we login and go to our home page we’ll see… the same thing as before. Our implementation has a drawback. The connection event will not be called until the page is loaded, and by that time onlineUsers will have already been passed into the res.render function. What we can do instead, is add the current user to the list ourselves as we pass the onlineUsers variable into the res.render function. However, we only do this the first time, because if the user refreshes the page, he/she will be in the array already. The new code looks like this, to be inserted where res.render('home', {users : onlineUsers}); used to be:

Now, let’s say the list of users is [‘bob’, ‘billy’]. When Billybob logs in and is directed to the home page, the onlineUsers list will be concatenated with a list with only his name in it. So [‘bob’, ‘billy’].concat([‘billybob’]) will be the variable that Hogan uses to parse the home.hjs file.

That’s it! Try opening a few tabs and testing it out, using the usernames and passwords that you created in the MongoDB database.

Conclusion

That’s all for this introduction. If there’s interest I’ll try to expand on it a bit and write another post or two about node and express. Comment or send me an email if you have any questions or suggestions.

One thought on “An introduction to Node.js, Express, MongoDB, and more

  1. I tried your tutorial, I did however only got it working without the VAR keyword before onlineUsers (making it a global variable). Nevertheless I loved the tutorial I hope you’ll make another one.

    Grz

    In your app.js file, create an empty array called onlineUsers, below all the require statements, above the mongoose code :

    var onlineUsers = [];

Leave a Reply

Your email address will not be published. Required fields are marked *