Use the MongoDB driver

Một phần của tài liệu single page web applications (Trang 296 - 306)

An application in a given language requires a database driver to efficiently interface with MongoDB. Without a driver, the only way to interact with MongoDB would be through the shell. A number of MongoDB drivers have been written in various lan- guages, including one for JavaScript in Node.js. A good driver handles many low-level tasks around interacting with a database without troubling the developer. Some exam- ples include reconnecting to the database in case of a lost connection, managing the connections to replica sets, buffer pooling, and cursor support.

8.3.1 Prepare the project files

In this chapter, we build on the work we completed in chapter 7. We’ll copy our entire file structure from chapter 7 into a new “chapter_8” directory where we’ll continue our work. Listing 8.1 shows our file structure after we have completed the copy. Files and directories we will be removing are shown in bold:

db.collection_name.find({ 'name' : 'Josh Powell' })

Return all documents in the collection_name collec- tion that have the field name with a value of “Josh Powell.”

db.collection_name.update({

'name': 'Josh Powell' }, {'name':

'Mr. Joshua C. Powell'})

Find all documents with a name of “Josh Powell”

and replace them with {'name': 'Mr.

Joshua C. Powell'}. db.collection_name.update({

'name': 'Mr. Joshua C. Powell' }, {$set: {'job': 'Author'} })

Find all documents with a name of “Mr. Joshua C.

Powell” and add or modify the attributes provided by the $set attribute.

db.collection_name.remove({

'name': 'Mr. Joshua C. Powell' })

Remove all documents with the field name with a value of “Mr. Joshua C. Powell” from the collection_name collection.

exit Exit the MongoDB shell.

Table 8.1 Basic MongoDB shell commands (continued)

Command Description

chapter_8

`-- webapp

|-- app.js

|-- js

| `-- data.js

|-- node_modules

|-- package.json

|-- public

| |-- css/

| |-- js/

| `-- spa.html

|-- routes.js

|-- socket.html

`-- socket.js

Let’s remove the js directory, the socket.html file, and the socket.js file. We should also remove the node_modules directory, as that will be regenerated during module instal- lation. Our updated structure should then look like listing 8.2:

chapter_8

`-- webapp

|-- app.js

|-- package.json

|-- public

| |-- css/

| |-- js/

| `-- spa.html

`-- routes.js

Now with our directory copied and tidied up, we’re ready to attach MongoDB to our application. Our first step is to install the MongoDB driver.

8.3.2 Install and connect to MongoDB

We find that the MongoDB driver is a good solution for many applications. It’s simple, fast, and easy to understand. If we need more capability we might consider using an Object Document Mapper (ODM). An ODM is analogous to an Object Relational Mapper (ORM) frequently used for relational databases. There are a few options available: Mon- goskin, Mongoose, and Mongolia to name a few.

We’ll be using the basic MongoDB driver for our application because most of our associations and higher-level data modelling are handled on the client. We don’t want any ODM validation features, as we’ll be validating our document structure using a general purpose JSON schema validator. We’ve made that choice because the JSON schema validator is standards-compliant and works on both the client and the server, whereas the ODM validations only work on the server at this time.

Listing 8.1 Copy files from chapter 7

Listing 8.2 Remove some files and directories we no longer need

273 Use the MongoDB driver

We can use our package.json to install the MongoDB driver. As before, we’ll spec- ify the major and minor versions of the module, but request the latest patch version, as shown in listing 8.3. Changes are shown in bold:

{ "name" : "SPA",

"version" : "0.0.3",

"private" : true,

"dependencies" : {

"express" : "3.2.x",

"mongodb" : "1.3.x",

"socket.io" : "0.9.x"

} }

We can run npm install to install all the modules in the manifest, including the Mon- goDB driver. Let’s edit the routes.js file to include mongodb and start a connection, as shown in listing 8.4. Changes are shown in bold:

/*

* routes.js - module to provide routing

*/

...

// --- BEGIN MODULE SCOPE VARIABLES --- 'use strict';

var

configRoutes,

mongodb = require( 'mongodb' ), mongoServer = new mongodb.Server(

'localhost',

mongodb.Connection.DEFAULT_PORT ),

dbHandle = new mongodb.Db(

'spa', mongoServer, { safe : true } );

dbHandle.open( function () {

console.log( '** Connected to MongoDB **' );

});

// --- END MODULE SCOPE VARIABLES --- ...

We can also remove basic auth from our server application, as shown in listing 8.5.

/*

* app.js - Express server with routing

*/

...

// --- BEGIN SERVER CONFIGURATION ---

Listing 8.3 Update the manifest for npm install—webapp/package.json

Listing 8.4 Open a MongoDB connection—webapp/routes.js

Listing 8.5 Remove basic auth from our server application—webapp/app.js Include the MongoDB connector.

Configure the MongoDB server connection object, passing in the URL (localhost) and port.

Create the MongoDB database handle, passing in the server connection object and a set of options. As of the 1.3.6 driver, the safe setting has been deprecated.

Setting {w:1} should provide similar results for a single MongoDB server.

Open the database connection.

Add a callback function to be executed when the connection has completed.

app.configure( function () { app.use( express.bodyParser() );

app.use( express.methodOverride() );

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

app.use( app.router );

});

...

Now we can start our server application (node app.js at the command prompt) and see the following output:

Express server listening on port 3000 in development mode

** Connected to MongoDB **

Now that we’ve connected our server application to MongoDB, let’s explore basic Create-Read-Update-Delete (CRUD) operations.

8.3.3 Use MongoDB CRUD methods

Before we update our server application further, we’d like to get comfortable with MongoDBCRUD methods. Let’s open a terminal and start the MongoDB shell by typ- ing mongo. We can then create some documents in a collection (using the insert method) as shown in listing 8.6. Our input is shown in bold:

> use spa;

switched to db spa

> db.user.insert({

"name" : "Mike Mikowski", "is_online" : false,

"css_map":{"top":100,"left":120,

"background-color":"rgb(136, 255, 136)"

} });

> db.user.insert({

"name" : "Mr. Joshua C. Powell, humble humanitarian", "is_online": false,

"css_map":{"top":150,"left":120,

"background-color":"rgb(136, 255, 136)"

} });

> db.user.insert({

"name": "Your name here", "is_online": false,

"css_map":{"top":50,"left":120,

"background-color":"rgb(136, 255, 136)"

} });

> db.user.insert({

"name": "Hapless interloper",

Listing 8.6 Create some documents in MongoDB

The line app.use(express.basicAuth('user','spa')); has been deleted.

275 Use the MongoDB driver

"is_online": false,

"css_map":{"top":0,"left":120,

"background-color":"rgb(136, 255, 136)"

} });

We can read these document to ensure they have been added correctly (using the find method) as shown in listing 8.7. Our input is shown in bold:

> db.user.find()

{ "_id" : ObjectId("5186aae56f0001debc935c33"),

"name" : "Mike Mikowski",

"is_online" : false,

"css_map" : {

"top" : 100, "left" : 120,

"background-color" : "rgb(136, 255, 136)"

} },

{ "_id" : ObjectId("5186aaed6f0001debc935c34"),

"name" : "Mr. Josh C. Powell, humble humanitarian",

"is_online" : false,

"css_map" : {

"top" : 150, "left" : 120,

"background-color" : "rgb(136, 255, 136)"

} }

{ "_id" : ObjectId("5186aaf76f0001debc935c35"),

"name" : "Your name here",

"is_online" : false,

"css_map" : {

"top" : 50, "left" : 120,

"background-color" : "rgb(136, 255, 136)"

} }

{ "_id" : ObjectId("5186aaff6f0001debc935c36"),

"name" : "Hapless interloper",

"is_online" : false,

"css_map" : {

"top" : 0, "left" : 120,

"background-color" : "rgb(136, 255, 136)"

} }

Note that MongoDB automatically adds a unique ID field, named _id, to any docu- ment that’s inserted. Hmm, though the name field for one of our authors is obviously correct (although perhaps an understatement), it seems too formal. Let’s remove the stuffiness and update the document (using the update method) as shown in listing 8.8.

Our input is shown in bold:

Listing 8.7 Read documents from MongoDB

> db.user.update(

{ "_id" : ObjectId("5186aaed6f0001debc935c34") }, { $set : { "name" : "Josh Powell" } }

);

db.user.find({

"_id" : ObjectId("5186aaed6f0001debc935c34") });

{ "_id" : ObjectId("5186aaed6f0001debc935c34"),

"name" : "Josh Powell",

"is_online" : false,

"css_map" : {

"top" : 150, "left" : 120,

"background-color" : "rgb(136, 255, 136)"

} }

We couldn’t help but notice that a hapless interloper has entered our database. Like a red-shirted crew member in a Star Trek landing party, a hapless interloper shouldn’t make it past the end of a scene. We’d hate to break with tradition, so let’s dispatch this interloper forthwith and delete the document (using the remove method) as shown in listing 8.9. Our input is shown in bold:

> db.user.remove(

{ "_id" : ObjectId("5186aaff6f0001debc935c36") } );

> db.user.find()

{ "_id" : ObjectId("5186aae56f0001debc935c33"),

"name" : "Mike Mikowski",

"is_online" : false,

"css_map" : {

"top" : 100, "left" : 120,

"background-color" : "rgb(136, 255, 136)"

} }

{ "_id" : ObjectId("5186aaed6f0001debc935c34"),

"name" : "Josh Powell",

"is_online" : false,

"css_map" : {

"top" : 150, "left" : 120,

"background-color" : "rgb(136, 255, 136)"

} }

{ "_id" : ObjectId("5186aaf76f0001debc935c35"),

"name" : "Your name here",

"is_online" : false,

"css_map" : {

"top" : 50, "left" : 120,

Listing 8.8 Update a document in MongoDB

Listing 8.9 Delete a document from MongoDB

277 Use the MongoDB driver

"background-color" : "rgb(136, 255, 136)"

} }

We’ve now completed the Create-Read-Update-Delete operations using the MongoDB console. Now let’s update our server application to support these operations.

8.3.4 Add CRUD to the server application

Because we’re using Node.js, the interaction with MongoDB is going to be different than most other languages because JavaScript is event-based. Now that we have some documents in the database to play around with, let’s update our router to use MongoDB to fetch a list of user objects, as shown in listing 8.10. Changes are shown in bold:

/*

* routes.js - module to provide routing

*/

...

// --- BEGIN PUBLIC METHODS --- configRoutes = function ( app, server ) {

...

app.get( '/:obj_type/list', function ( request, response ) { dbHandle.collection(

request.params.obj_type,

function ( outer_error, collection ) { collection.find().toArray(

function ( inner_error, map_list ) { response.send( map_list );

} );

} );

});

...

};

module.exports = { configRoutes : configRoutes };

// --- END PUBLIC METHODS --- ...

Before looking at the results in your browser, you may want to get a browser extension or add-on that makes the JSON more human-readable. We use JSONView 0.0.32 on Chrome and JSONovich 1.9.5 on Firefox. Both of these are available through the respective vendor add-on sites.

We can start our application by typing nodeapp.js in the terminal. When we point our browser to http://localhost:3000/user/list, we should see a JSON document pre- sentation similar to figure 8.3:

Listing 8.10 Update our router to retrieve a user list—webapp/routes.js

Use the dbHandle object to retrieve the collection specified in :obj_type in the URL, and pass in a callback to be executed.

Find all documents in the (dbHandle.collection) collection and transform the results into an array.

Send the list of JSON objects back to the client.

We can now add the remaining CRUD operations as shown in listing 8.11. Changes are shown in bold:

/*

* routes.js - module to provide routing

*/

...

// --- BEGIN MODULE SCOPE VARIABLES --- 'use strict';

var

configRoutes,

mongodb = require( 'mongodb' ), mongoServer = new mongodb.Server(

'localhost',

mongodb.Connection.DEFAULT_PORT ),

dbHandle = new mongodb.Db(

'spa', mongoServer, { safe : true } ),

makeMongoId = mongodb.ObjectID;

// --- END MODULE SCOPE VARIABLES --- // --- BEGIN PUBLIC METHODS --- configRoutes = function ( app, server ) {

app.get( '/', function ( request, response ) { response.redirect( '/spa.html' );

});

Listing 8.11 Add the MongoDB driver and CRUD to our router—routes.js

Figure 8.3 Response from MongoDB through Node.js to client

Copy the ObjectID function into a makeMongoId module-scope variable.

This is a convenience. Note that we now open the database connection at the end of the module.

279 Use the MongoDB driver

app.all( '/:obj_type/*?', function ( request, response, next ) { response.contentType( 'json' );

next();

});

app.get( '/:obj_type/list', function ( request, response ) { dbHandle.collection(

request.params.obj_type,

function ( outer_error, collection ) { collection.find().toArray(

function ( inner_error, map_list ) { response.send( map_list );

} );

} );

});

app.post( '/:obj_type/create', function ( request, response ) { dbHandle.collection(

request.params.obj_type,

function ( outer_error, collection ) { var

options_map = { safe: true }, obj_map = request.body;

collection.insert(

obj_map, options_map,

function ( inner_error, result_map ) { response.send( result_map );

} );

} );

});

app.get( '/:obj_type/read/:id', function ( request, response ) { var find_map = { _id: makeMongoId( request.params.id ) };

dbHandle.collection(

request.params.obj_type,

function ( outer_error, collection ) { collection.findOne(

find_map,

function ( inner_error, result_map ) { response.send( result_map );

} );

} );

});

app.post( '/:obj_type/update/:id', function ( request, response ) { var

find_map = { _id: makeMongoId( request.params.id ) }, obj_map = request.body;

Add the capability to list every user. This was shown earlier in the section. Don’t add it twice.

Insert the document into MongoDB. The safe option specifies that the callback won’t be executed until after the document is successfully inserted into MongoDB; otherwise the callback will be executed immediately without waiting for a success response. It’s up to you if you want to be quicker or safer. This isn’t strictly required here, as we had set our default safe option when we configured the database handle. Also see our earlier note about the new w option which deprecates the safe option.

Use the findOne method provided by the Node.js MongoDB driver to find and return the first document matching the search parameters. Since there should be only one object of a particular ID, one is all we need returned.

dbHandle.collection(

request.params.obj_type,

function ( outer_error, collection ) { var

sort_order = [], options_map = {

'new' : true, upsert: false, safe: true };

collection.findAndModify(

find_map, sort_order, obj_map, options_map,

function ( inner_error, updated_map ) { response.send( updated_map );

} );

} );

});

app.get( '/:obj_type/delete/:id', function ( request, response ) { var find_map = { _id: makeMongoId( request.params.id ) };

dbHandle.collection(

request.params.obj_type,

function ( outer_error, collection ) {

var options_map = { safe: true, single: true };

collection.remove(

find_map, options_map,

function ( inner_error, delete_count ) {

response.send({ delete_count: delete_count });

} );

} );

});

};

module.exports = { configRoutes : configRoutes };

// --- END PUBLIC METHODS --- // --- BEGIN MODULE INITIALIZATION --- dbHandle.open( function () {

console.log( '** Connected to MongoDB **' );

});

// --- END MODULE INITIALIZATION ---

We now have user CRUD operations working from the client through the Node.js server and into MongoDB and back again. Now we would like the application to vali- date data received from the client.

Use the findAndModify method provided by the Node.js MongoDB driver. This method will find all documents matching the search criteria and replace them with the object found in obj_map. Yes, we know the name is misleading, but we didn’t write the MongoDB driver, now did we?

Use the remove method to remove all documents matching the attributes of the object map. We pass in single:true as an option so that it only deletes one document at most.

Add a module initialization section.

281 Validate client data

Một phần của tài liệu single page web applications (Trang 296 - 306)

Tải bản đầy đủ (PDF)

(433 trang)