Make a Picture Voting Game with Node.js (Part 2)

Make a Picture Voting Game with Node.js (Part 2)

In the first part of this tutorial, we laid down the foundation of our node.js web app. You learned about running and installing node, about npm and the nedb library, and we even wrote our first module. This week we will continue with the routes and views of our picture voting app.

The Routes and Configuration Files

Last week we made a module that handles the initialization of two datasets – users and photos. These datasets were exported by the module, which allows us to require it and access them in our other js files. We will do this is in our routes.js file, which holds all the routes that the application will respond to.

routes.js

/**
 * This file defines the routes used in your application
 * It requires the database module that we wrote previously.
 */ 

var db = require('./database'),
	photos = db.photos,
	users = db.users;

module.exports = function(app){

	// Homepage
	app.get('/', function(req, res){

		// Find all photos
		photos.find({}, function(err, all_photos){

			// Find the current user
			users.find({ip: req.ip}, function(err, u){

				var voted_on = [];

				if(u.length == 1){
					voted_on = u[0].votes;
				}

				// Find which photos the user hasn't still voted on

				var not_voted_on = all_photos.filter(function(photo){
					return voted_on.indexOf(photo._id) == -1;
				});

				var image_to_show = null;

				if(not_voted_on.length > 0){
					// Choose a random image from the array
					image_to_show = not_voted_on[Math.floor(Math.random()*not_voted_on.length)];
				}

				res.render('home', { photo: image_to_show });

			});

		});

	});

	app.get('/standings', function(req, res){

		photos.find({}, function(err, all_photos){

			// Sort the photos 

			all_photos.sort(function(p1, p2){
				return (p2.likes - p2.dislikes) - (p1.likes - p1.dislikes);
			});

			// Render the standings template and pass the photos
			res.render('standings', { standings: all_photos });

		});

	});

	// This is executed before the next two post requests
	app.post('*', function(req, res, next){

		// Register the user in the database by ip address

		users.insert({
			ip: req.ip,
			votes: []
		}, function(){
			// Continue with the other routes
			next();
		});

	});

	app.post('/notcute', vote);
	app.post('/cute', vote);

	function vote(req, res){

		// Which field to increment, depending on the path

		var what = {
			'/notcute': {dislikes:1},
			'/cute': {likes:1}
		};

		// Find the photo, increment the vote counter and mark that the user has voted on it.

		photos.find({ name: req.body.photo }, function(err, found){

			if(found.length == 1){

				photos.update(found[0], {$inc : what[req.path]});

				users.update({ip: req.ip}, { $addToSet: { votes: found[0]._id}}, function(){
					res.redirect('../');
				});

			}
			else{
				res.redirect('../');
			}

		});
	}
};

Here app is an instance of an Express.js web application that we will create in our index.js file. We are exporting a function which takes the app as an argument, which allows us to inject it as a dependency later on.

Standings

Standings

The next file that we will write, is a configuration file that sets some settings for our application:

config.js

/**
 * This file runs some configuration settings on your express application.
 */ 

// Include the handlebars templating library
var handlebars = require('express3-handlebars'),
	express = require('express');

// Require()-ing this module will return a function
// that the index.js file will use to configure the
// express application

module.exports = function(app){

	// Register and configure the handlebars templating engine
	app.engine('html', handlebars({ 
		defaultLayout: 'main',
		extname: ".html",
		layoutsDir: __dirname + '/views/layouts'
	}));

	// Set .html as the default template extension 
	app.set('view engine', 'html');

	// Tell express where it can find the templates
	app.set('views', __dirname + '/views');

	// Make the files in the public folder available to the world
	app.use(express.static(__dirname + '/public'));

	// Parse POST request data. It will be available in the req.body object
	app.use(express.urlencoded());

};

We are using the handlebars templating engine for our views (with the help of this adapter library), because it is easy to write and supports layout views. A layout will allow us to share a common design for all our pages, which is a big time saver. The code above also uses the static connect middleware to serve the files in the /public folder. This is the best way to make all the site assets accessible from a web browser.

The next file is index.js, which ties all of these modules together, and initializes a new Express.js application for us:

index.js

/**
 * This is the main file of the application. Run it with the
 * `node index.js` command from your terminal
 *
 * Remember to run `npm install` in the project folder, so 
 * all the required libraries are downloaded and installed.
 */ 

var express = require('express');

// Create a new express.js web app:

var app = express();

// Configure express with the settings found in
// our config.js file

require('./config')(app);

// Add the routes that the app will react to,
// as defined in our routes.js file

require('./routes')(app);

// This file has been called directly with 
// `node index.js`. Start the server!

app.listen(8080);
console.log('Your application is running on http://localhost:8080');

Great! Our app is taking shape! To start it, execute the command node index.js, and the server will start listening on port 8080. However, if you try opening http://localhost:8080, it in your browser at this point, you will only see error messages for missing template files. This is because we haven’t yet written our views.

The Views

The first view that we will create, is the layout. This file will define the common HTML that is shared by the other pages of our site. Your app may have more than one layout (for example if you wish to have separate designs for your home page and for your administration screens), but we will only have one here.

views/layouts/main.html

<!DOCTYPE html>
<html>

	<head>
		<meta charset="utf-8"/>
		<title>Node.js Picture Voting Game</title>

		<meta name="viewport" content="width=device-width, initial-scale=1" />

		<link href="http://fonts.googleapis.com/css?family=Open+Sans:300,700" rel="stylesheet" />
		<link href="css/styles.css" rel="stylesheet" />

	</head>

	<body>

		<header>
			<h1><span class="green">Cute</span> or <span class="red">NOT?</span></h1>
			<h2>A Node.js Voting Game</h2>
		</header>

		{{{body}}}

		<footer>
            <a class="tz" href="http://tutorialzine.com/2014/01/nodejs-picture-voting-game-part-1/">Tutorial: Node.js Picture Voting Game</a>

	</body>
</html>

The {{{body}}} tag is automatically replaced by the HTML of the other views that use this layout. Here is the HTML specific to the index page:

views/home.html

<nav>
	<a class="active" href="./">Game</a>
	<a href="./standings">Standings</a>
</nav>

{{#if photo}}

	<img src="photos/{{photo.name}}" width="530" height="420" alt="Cat Picture" />

	<div class="button-holder">
		<form action="./cute" method="post">
			<input type="hidden" name="photo" value="{{photo.name}}" />
			<input type="submit" value="Cute!" />
		</form>
		<form action="./notcute" method="post">
			<input type="hidden" name="photo" value="{{photo.name}}" />
			<input type="submit" value="Not Cute!" />
		</form>
	</div>

{{else}}

	<h3>No more photos to vote on! Check out the <a href="./standings">standings</a>.</h3>

{{/if}}

Handlebars templates can have if/else constructs, loops and lots of other features that allow you to write clean HTML. And here is the template for the standings page:

views/standings.html

<nav>
	<a href="./">Game</a>
	<a class="active" href="./standings">Standings</a>
</nav>

{{#if standings}}

	<ul>

		{{#each standings}}

		<li>
			<img src="photos/{{name}}" alt="Cat picture thumbnail" />
			<p class="up">{{this.likes}}</p>
			<p class="down">{{this.dislikes}}</p>
		</li>

		{{/each}}

	</ul>

{{/if}}

By using templates we are able to separate the code for presenting the data from the data itself. You can use many different template engines in your express web application.

We’re Done!

With this, our Node.js picture voting game is complete! You can enhance it with some of the countless node.js modules and libraries and modify it in any way that you wish. I hope that you found this tutorial useful! If you have any suggestions, bring them to the comment section below.

Presenting Bootstrap Studio

a revolutionary tool that developers and designers use to create
beautiful interfaces using the Bootstrap Framework.

Learn more
Web Browser Frame DevKit Box Mouse Cursor
by Martin Angelov

Martin is a web developer with an eye for design from Bulgaria. He founded Tutorialzine in 2009 and it still is his favorite side project.

1Share this post
2Read one more article
3Get your free book
Book Cover
jQuery Trickshots

Tutorialzine's advanced jQuery techniques book.

Download

5 Comments

  1. chris mccoy says:

    How are you passing the IP address onto the visitor? If I run the node app at the port like domain:8080 it works fine, but when i use an Apache using Proxypass it doesn't pass the IP, so if i vote on all the pictures from my IP, and I give the URL to someone else, it shows them that all images have been voted on, curious on your setup how you do this.

    1. Martin Angelov says:

      To everyone who is reading this, if you run into this problem, simply add this setting to your config.js file:

      app.enable('trust proxy');
      

      When you run your node.js app behind Apache or Nginx, your app will always get 127.0.0.1 as the IP address of the visitor. Adding the 'trust proxy' setting tells express.js to work around this problem.

      1. Chris McCoy says:

        thanks for the trust proxy tip, i hacked together a startup script so the nodeapp starts on boot

        https://gist.github.com/chrismccoy/8662211

  2. Cube3x says:

    Node.js is really powerful! Thanks for sharing.

  3. Vanessa says:

    Could you talk a little bit about deployment strategies? I'd be interested in a controlled host (e.g., Bluehost), or generally anywhere without sudo or apache, or a cloud based solution.

Add Comment

Add a Reply

HTML is escaped automatically. Surround code blocks with <pre></pre> for readability.
Perks:   **bold**   __italics__   [some text](http://example.com) for links