Software developer blog

Web app crash course (Part 4)

In the previous part we have built a very simple chat application, but it stored messages in memory. Every time it was restarted, all messages were gone forever. In this part of the series we will look at MongoDB, a document database. There are many other database technologies, some of them are more frequently used than MongoDB, but for our purposes it is the best choice.

If you haven't read the previous parts, click here.

Installing Mongo

The first step is to install the database if you don't have it already on your machine. Type sudo apt-get install mongodb into your terminal. You can check if the installation was successful by typing mongo into your terminal. This will open up a command line client for mongo. If you type show dbs it should show you a list of databases. There should be at least one: local.

After installing the database we need to install the ruby gems that will connect to it for us. We will add the mongo and json-ext gems to your Gemfile:

Gemfile
source 'https://rubygems.org'
 
gem 'sinatra'
gem 'mustache'
gem 'rspec'
gem 'mongo'
gem 'bson_ext'

Now run bundle to get those gems installed.

MongoDB basics

MongoDB is a document store. A document in this context is a hash or array of primitive types: numbers, strings and arrays and hashes of those. Mongo was originally designed for JavaScript, so it uses JSON objects to encode such documents. It's not really important to know how a JSON encoded document looks like, but I thought a simple example might be useful. Let's suppose we have an array of Books. Each of them has a title, year of release, and an array of authors. Each author has a first and last name.

[
    {
        "title": "Refactoring",
        "year_of_release": 1999,
        "authors": [
            { "first_name": "Martin", "last_name": "Fowler" }
        ]
    },
    {
        "title": "The Pragmatic Programmer",
        "year_of_release": 1999,
        "authors": [
            { "first_name": "Andrew", "last_name": "Hunt" },
            { "first_name": "Dave", "last_name": "Thomas" }
        ]
    }
]

MongoDB has databases, and each of these databases has collections. Databases are usually used to separate applications, so that each application can have it's own sandbox. Collections are used to group together documents of the same type. For example I could have a Books collection that contains the two books above. You can insert documents into a collection, and you can search for them.

Connecting to Mongo

We will create a simple class that connects to MongoDB, and provides a way to access collections within a specific database of MongoDB. This is the document_store.rb I have put in my project root:

document_store.rb
require 'mongo'
 
class DocumentStore
  include Mongo
 
  def initialize
    @mongo_client = MongoClient.new
    @db = @mongo_client.db("web-app-tutorial")
  end
 
  def [](collection)
    @db[collection]
  end
 
  def self.instance
    @instance ||= DocumentStore.new
  end
end

Notice that we are using the singleton pattern again. The single instance we have will connect to the web-app-tutorial database of your MongoDB. Note that the database doesn't need to exist, Mongo will create an empty one when it is first used. I use the [] (sub-script operator) function to access collections within the database.

Using Mongo

Now that we can connect to Mongo, lets modify our ChatMessageStore to use it:

lib/chat_message_store.rb
require_relative '../document_store'
 
class ChatMessageStore
  def initialize
    @collection = DocumentStore.instance['chat_messages']
  end
 
  def messages
    @collection.find.sort(:_id).to_a
  end
 
  def add_message(name, message)
    @collection.insert({'name' => name, 'message' => message})
  end
 
  def self.instance
    @instance ||= ChatMessageStore.new
  end
end

The initialize connects to the chat_messages collection. We had to turn messages into a proper function that simply loads all of the messages, sorts by _id, and converts the result set into an array. Finally add_message inserts a new document into the collection.

That's pretty much it. You can now start Sinatra, and try the chat page. No matter how many times you restart the app, or turn of your computer, the messages will remain where they are.

Let's commit to git:

git add document_store.rb
git commit -a -m 'Use MongoDB to store chat messages' 
git push

Download it here.

Where do we go from here...

This is a very simple web app, and I took a shortcut here and there. For example I really should have put the database name into a configuration file, so that it's easy to change. But my aim was to expose you to the tool set, and get you going. There is still a lot to learn: how to upload images (or have it uploaded by someone who is an expert at it), how to authenticate users, how to use bootstrap to quickly enhance the design of your page, how to deploy your app to a heroku, or to your own server, how to select specific documents from MongoDB instead of just all of them, not to mention that SQL is a lot more frequently used database technology than MongoDB and we haven't even touched that topic.

However my advice to you is to go build something with what you have already learned. I can only recall 2 times in my life that I had to implement an image upload, and only 3 times that I implemented user authentication. These things need to be done once for every application, so you can learn those when they come up as a problem. (You would forget it anyway by then, if you learn it now.) Once you have built something with Ruby, learn JavaScript and jQuery, so that you can build even more responsive applications. Finally if you have plans for interviewing with a company that uses SQL, then try to migrate your existing web app from Mongo to MySQL. It won't be easy, but you'll learn a lot about SQL.