JavaScript Toddler Game Part 5: Adding a database

31. July 2012

 

I had intended to end this series on part 4, all I had left to do was add a layer of persistence because Heroku didn’t keep files for more than a few hours, which was a pretty heavy limitation.  So I looked into various storage options, and I ended up going with CouchDB.  The process was a bit more involved ( and interesting ) than I suspected, so I decided to add another post in the series.  This part covers setting up cloud based database, and changing our code to persist to the database.

 

First off, I went to IrisCouch.com and signed up for a free database.  They provide (free!) cloud hosted CouchDB installs, including a full Futon management system, pictured below:

image

 

You can use this interface to create new databases, configure security etc.  It’s a bit tricky at times to navigate, but it gets the job done.

 

I created a new database called firstthis, then immediately secured it ( by default your website is publically accessible to everyone! ).  CouchDB works by storing information in documents instead of tables in a traditional database.  Instead of updating individual fields, you update the data in your document then replace the entire thing.  Tracking the newest revision becomes incredibly important when working with CouchDB.  Perhaps most key of all, CouchDB adds two fields to your data, _id and _rev.  _rev represents the newest revision, while _id represents the unique key.  In our case, for our user settings, we are going to use their email address as the key.  We simply store the files variable from our script to the server.  Here is a sample of files stored in CouchDB ( shown in the iriscouch admin page ):

 

image

 

As you can see, its simply our JavaScript variable, with an _id and _rev added. 

 

When you sign up for IrisCouch, you are given a URL where your database server is located, in the form of yoursite.iriscouch.com.

 

Now let’s take a look at the code differences.  It is pretty thoroughly documented ( combined with the above explaination ), so I won’t go into a lot of detail.  CouchDB is accessed using REST requests, but I instead used a node library Nano for access.  This was a bit of a double edged sword, as it took away a great deal of the complexity and grunt work, however it also removed me a step away from the well documented CouchDB. 

 

All of the code changes are in server.js

var express = require('express'), server = express.createServer(), im = require('imagemagick'), nano = require('nano')('http://Serapth:meat32ball@gfs.iriscouch.com'), db_name = "firstthis", db = nano.use(db_name), userEmail = 'mike@gamefromscratch.com', fs = require('fs'), files = { files:{}}; // Helper functions for getting and inserting docs in couchDB using nano function get_doc(docName,res){ db.get(docName,function(err,body){ if(!err) { res(body); } }); }; // There is no update in CouchDB. Just inserts, inserts and more inserts. // If you don't have the most current rev ( or it isn't a new insert ), an error (HTTP409) will occur. // TODO: Real error handling, attempt to get latest file, get it's rev, then try inserting again function insert_doc(doc,docname, tried) { db.insert(doc,docname, function (error,val,newval) { if(error) { return console.log(error); } // The insert will result in an updated rev, update our local files var to the most current rev. return files._rev = val.rev; }); } // Setup server static paths server.use('/cocos2d', express.static(__dirname + '/cocos2d') ); server.use('/cocosDenshion', express.static(__dirname + '/cocosDenshion') ); server.use('/classes', express.static(__dirname + '/classes') ); server.use('/resources', express.static(__dirname + '/resources') ); // Install the bodyParser middleware, which enables form data to be parsed when an upload occurs. server.use(express.bodyParser()); // Handle requests for / by returning index.html server.get('/', function(req,res){ res.sendfile('index.html'); console.log('Sent index.html'); }); // Handle requests for /settings by returning settings.html server.get('/settings',function(req,res){ res.sendfile('settings.html'); console.log('Send settings.html'); }); // Handle requests for images will be the form of site.com/image/imagename.png // Fetchs the image data from CouchDB and returns to user server.get('/image/:name', function(req,res){ if(files.files[req.params.name]) { res.contentType(files.files[req.params.name].contentType); db.attachment.get(userEmail + "/" + files.files[req.params.name].name,files.files[req.params.name].name).pipe(res); } }); // Uses ImageMagick to get image dimensions and return them as JSON data // This is to work around the requirement for Cocos2D sprites to know dimensions before loading image server.get('/imageSize/:name',function(req,res){ im.identify(files.files[req.params.name].path,function(err,features){ if(err) throw err; else res.json({ "width":features.width, "height":features.height }); }); }); // This gets the photo data, which is contained in our files variable. Simply return it JSON encoded server.get('/getPhotos', function(req,res){ res.json(files.files); }); // Erase all images : TODO: Remove images from database as well!!!! server.get('/clearAll', function(req,res){ files.files = {}; res.statusCode = 200; res.send(""); }) // Unfortunately there is no easy way to tell when a multi file upload is complete on the server, On('end') isnt called // Therefore we call /doneUpload from the client once we are done uploading. // Once we are done uploading files, we save our updated Files var up to couchDB, then get it again so it again immediately to have the most current rev server.get('/doneUpload', function(req,res){ insert_doc(files,userEmail,files._rev,0); get_doc(userEmail,function(res) { files = res; }); res.statusCode = 200; res.sendfile('settings.html'); }) server.post('/upload',function(req,res){ // This method is going to be called for each attached file. // Add the file details to files.files[] with the key set to the filename files["files"][req.files.Filedata.name] = { "name":req.files.Filedata.name, "path":req.files.Filedata.path, "size":req.files.Filedata.size, "contentType":req.files.Filedata.type, "description":req.body.description }; // Now read the file from disk and insert the file data in our CouchDB as an attachment named "emailAddress/filename.png" fs.readFile(req.files.Filedata.path,function(err,data){ if(!err){ db.attachment.insert(userEmail + "/" + req.files.Filedata.name,req.files.Filedata.name,data,req.files.Filedata.type , {}, function(err,body){ console.log(body); res.statusCode = 200; res.send(""); }); } }); }); server.listen(process.env.PORT || 3000); // Check the couchDB for an entry keyed to the users email address. If one exists, copy it into our files var get_doc(userEmail,function(results){ if(results){ files = results; } });

 

Now when you upload images, they will be stored to your CouchDB database on IrisCouch.  When you restart Node, it will automatically grab and populate files from the database. Note, the application from parts 1-4 aren’t updated to use this new code.

Keep in mind, this code isn’t production ready… error handling is sparse, there is no authentication, it would be easy to exploit with a DoS attack for example.  However, if you are interested in storing data from your Node App in a CouchDB database, I hope this sample was useful.

Programming , , ,







blog comments powered by Disqus