diff --git a/src/node/examples/route_guide.proto b/src/node/examples/route_guide.proto
new file mode 100644
index 0000000000000000000000000000000000000000..4c7be175e4c600437ecfd60fb9b322fb3afaf601
--- /dev/null
+++ b/src/node/examples/route_guide.proto
@@ -0,0 +1,120 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+option java_package = "ex.grpc";
+
+package examples;
+
+// Interface exported by the server.
+service RouteGuide {
+  // A simple RPC.
+  //
+  // Obtains the feature at a given position.
+  rpc GetFeature(Point) returns (Feature) {}
+
+  // A server-to-client streaming RPC.
+  //
+  // Obtains the Features available within the given Rectangle.  Results are
+  // streamed rather than returned at once (e.g. in a response message with a
+  // repeated field), as the rectangle may cover a large area and contain a
+  // huge number of features.
+  rpc ListFeatures(Rectangle) returns (stream Feature) {}
+
+  // A client-to-server streaming RPC.
+  //
+  // Accepts a stream of Points on a route being traversed, returning a
+  // RouteSummary when traversal is completed.
+  rpc RecordRoute(stream Point) returns (RouteSummary) {}
+
+  // A Bidirectional streaming RPC.
+  //
+  // Accepts a stream of RouteNotes sent while a route is being traversed,
+  // while receiving other RouteNotes (e.g. from other users).
+  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
+}
+
+// Points are represented as latitude-longitude pairs in the E7 representation
+// (degrees multiplied by 10**7 and rounded to the nearest integer).
+// Latitudes should be in the range +/- 90 degrees and longitude should be in
+// the range +/- 180 degrees (inclusive).
+message Point {
+  optional int32 latitude = 1;
+  optional int32 longitude = 2;
+}
+
+// A latitude-longitude rectangle, represented as two diagonally opposite
+// points "lo" and "hi".
+message Rectangle {
+  // One corner of the rectangle.
+  optional Point lo = 1;
+
+  // The other corner of the rectangle.
+  optional Point hi = 2;
+}
+
+// A feature names something at a given point.
+//
+// If a feature could not be named, the name is empty.
+message Feature {
+  // The name of the feature.
+  optional string name = 1;
+
+  // The point where the feature is detected.
+  optional Point location = 2;
+}
+
+// A RouteNote is a message sent while at a given point.
+message RouteNote {
+  // The location from which the message is sent.
+  optional Point location = 1;
+
+  // The message to be sent.
+  optional string message = 2;
+}
+
+// A RouteSummary is received in response to a RecordRoute rpc.
+//
+// It contains the number of individual points received, the number of
+// detected features, and the total distance covered as the cumulative sum of
+// the distance between each point.
+message RouteSummary {
+  // The number of points received.
+  optional int32 point_count = 1;
+
+  // The number of known features passed while traversing the route.
+  optional int32 feature_count = 2;
+
+  // The distance covered in metres.
+  optional int32 distance = 3;
+
+  // The duration of the traversal in seconds.
+  optional int32 elapsed_time = 4;
+}
diff --git a/src/node/examples/route_guide_client.js b/src/node/examples/route_guide_client.js
new file mode 100644
index 0000000000000000000000000000000000000000..d4c083a6c5c5e3e6bd8e62853960a4ef15e1b9c4
--- /dev/null
+++ b/src/node/examples/route_guide_client.js
@@ -0,0 +1,231 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+var async = require('async');
+var fs = require('fs');
+var parseArgs = require('minimist');
+var path = require('path');
+var _ = require('underscore');
+var grpc = require('..');
+var examples = grpc.load(__dirname + '/route_guide.proto').examples;
+var client = new examples.RouteGuide('localhost:50051');
+
+var COORD_FACTOR = 1e7;
+
+/**
+ * Run the getFeature demo. Calls getFeature with a point known to have a
+ * feature and a point known not to have a feature.
+ * @param {function} callback Called when this demo is complete
+ */
+function runGetFeature(callback) {
+  var next = _.after(2, callback);
+  function featureCallback(error, feature) {
+    if (error) {
+      callback(error);
+    }
+    if (feature.name === '') {
+      console.log('Found no feature at ' +
+          feature.location.latitude/COORD_FACTOR + ', ' +
+          feature.location.longitude/COORD_FACTOR);
+    } else {
+      console.log('Found feature called "' + feature.name + '" at ' +
+          feature.location.latitude/COORD_FACTOR + ', ' +
+          feature.location.longitude/COORD_FACTOR);
+    }
+    next();
+  }
+  var point1 = {
+    latitude: 409146138,
+    longitude: -746188906
+  };
+  var point2 = {
+    latitude: 0,
+    longitude: 0
+  };
+  client.getFeature(point1, featureCallback);
+  client.getFeature(point2, featureCallback);
+}
+
+/**
+ * Run the listFeatures demo. Calls listFeatures with a rectangle containing all
+ * of the features in the pre-generated database. Prints each response as it
+ * comes in.
+ * @param {function} callback Called when this demo is complete
+ */
+function runListFeatures(callback) {
+  var rectangle = {
+    lo: {
+      latitude: 400000000,
+      longitude: -750000000
+    },
+    hi: {
+      latitude: 420000000,
+      longitude: -730000000
+    }
+  };
+  console.log('Looking for features between 40, -75 and 42, -73');
+  var call = client.listFeatures(rectangle);
+  call.on('data', function(feature) {
+      console.log('Found feature called "' + feature.name + '" at ' +
+          feature.location.latitude/COORD_FACTOR + ', ' +
+          feature.location.longitude/COORD_FACTOR);
+  });
+  call.on('end', callback);
+}
+
+/**
+ * Run the recordRoute demo. Sends several randomly chosen points from the
+ * pre-generated feature database with a variable delay in between. Prints the
+ * statistics when they are sent from the server.
+ * @param {function} callback Called when this demo is complete
+ */
+function runRecordRoute(callback) {
+  var argv = parseArgs(process.argv, {
+    string: 'db_path'
+  });
+  fs.readFile(path.resolve(argv.db_path), function(err, data) {
+    if (err) callback(err);
+    var feature_list = JSON.parse(data);
+
+    var num_points = 10;
+    var call = client.recordRoute(function(error, stats) {
+      if (error) {
+        callback(error);
+      }
+      console.log('Finished trip with', stats.point_count, 'points');
+      console.log('Passed', stats.feature_count, 'features');
+      console.log('Travelled', stats.distance, 'meters');
+      console.log('It took', stats.elapsed_time, 'seconds');
+      callback();
+    });
+    /**
+     * Constructs a function that asynchronously sends the given point and then
+     * delays sending its callback
+     * @param {number} lat The latitude to send
+     * @param {number} lng The longitude to send
+     * @return {function(function)} The function that sends the point
+     */
+    function pointSender(lat, lng) {
+      /**
+       * Sends the point, then calls the callback after a delay
+       * @param {function} callback Called when complete
+       */
+      return function(callback) {
+        console.log('Visiting point ' + lat/COORD_FACTOR + ', ' +
+            lng/COORD_FACTOR);
+        call.write({
+          latitude: lat,
+          longitude: lng
+        });
+        _.delay(callback, _.random(500, 1500));
+      };
+    }
+    var point_senders = [];
+    for (var i = 0; i < num_points; i++) {
+      var rand_point = feature_list[_.random(0, feature_list.length - 1)];
+      point_senders[i] = pointSender(rand_point.location.latitude,
+                                     rand_point.location.longitude);
+    }
+    async.series(point_senders, function() {
+      call.end();
+    });
+  });
+}
+
+/**
+ * Run the routeChat demo. Send some chat messages, and print any chat messages
+ * that are sent from the server.
+ * @param {function} callback Called when the demo is complete
+ */
+function runRouteChat(callback) {
+  var call = client.routeChat();
+  call.on('data', function(note) {
+    console.log('Got message "' + note.message + '" at ' +
+        note.location.latitude + ', ' + note.location.longitude);
+  });
+
+  call.on('end', callback);
+
+  var notes = [{
+    location: {
+      latitude: 0,
+      longitude: 0
+    },
+    message: 'First message'
+  }, {
+    location: {
+      latitude: 0,
+      longitude: 1
+    },
+    message: 'Second message'
+  }, {
+    location: {
+      latitude: 1,
+      longitude: 0
+    },
+    message: 'Third message'
+  }, {
+    location: {
+      latitude: 0,
+      longitude: 0
+    },
+    message: 'Fourth message'
+  }];
+  for (var i = 0; i < notes.length; i++) {
+    var note = notes[i];
+    console.log('Sending message "' + note.message + '" at ' +
+        note.location.latitude + ', ' + note.location.longitude);
+    call.write(note);
+  }
+  call.end();
+}
+
+/**
+ * Run all of the demos in order
+ */
+function main() {
+  async.series([
+    runGetFeature,
+    runListFeatures,
+    runRecordRoute,
+    runRouteChat
+  ]);
+}
+
+if (require.main === module) {
+  main();
+}
+
+exports.runGetFeature = runGetFeature;
+
+exports.runListFeatures = runListFeatures;
+
+exports.runRecordRoute = runRecordRoute;
+
+exports.runRouteChat = runRouteChat;
diff --git a/src/node/examples/route_guide_db.json b/src/node/examples/route_guide_db.json
new file mode 100644
index 0000000000000000000000000000000000000000..9d6a980ab7d76163b103a5eccd523a3ffac0ec98
--- /dev/null
+++ b/src/node/examples/route_guide_db.json
@@ -0,0 +1,601 @@
+[{
+    "location": {
+        "latitude": 407838351,
+        "longitude": -746143763
+    },
+    "name": "Patriots Path, Mendham, NJ 07945, USA"
+}, {
+    "location": {
+        "latitude": 408122808,
+        "longitude": -743999179
+    },
+    "name": "101 New Jersey 10, Whippany, NJ 07981, USA"
+}, {
+    "location": {
+        "latitude": 413628156,
+        "longitude": -749015468
+    },
+    "name": "U.S. 6, Shohola, PA 18458, USA"
+}, {
+    "location": {
+        "latitude": 419999544,
+        "longitude": -740371136
+    },
+    "name": "5 Conners Road, Kingston, NY 12401, USA"
+}, {
+    "location": {
+        "latitude": 414008389,
+        "longitude": -743951297
+    },
+    "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA"
+}, {
+    "location": {
+        "latitude": 419611318,
+        "longitude": -746524769
+    },
+    "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA"
+}, {
+    "location": {
+        "latitude": 406109563,
+        "longitude": -742186778
+    },
+    "name": "4001 Tremley Point Road, Linden, NJ 07036, USA"
+}, {
+    "location": {
+        "latitude": 416802456,
+        "longitude": -742370183
+    },
+    "name": "352 South Mountain Road, Wallkill, NY 12589, USA"
+}, {
+    "location": {
+        "latitude": 412950425,
+        "longitude": -741077389
+    },
+    "name": "Bailey Turn Road, Harriman, NY 10926, USA"
+}, {
+    "location": {
+        "latitude": 412144655,
+        "longitude": -743949739
+    },
+    "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA"
+}, {
+    "location": {
+        "latitude": 415736605,
+        "longitude": -742847522
+    },
+    "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA"
+}, {
+    "location": {
+        "latitude": 413843930,
+        "longitude": -740501726
+    },
+    "name": "162 Merrill Road, Highland Mills, NY 10930, USA"
+}, {
+    "location": {
+        "latitude": 410873075,
+        "longitude": -744459023
+    },
+    "name": "Clinton Road, West Milford, NJ 07480, USA"
+}, {
+    "location": {
+        "latitude": 412346009,
+        "longitude": -744026814
+    },
+    "name": "16 Old Brook Lane, Warwick, NY 10990, USA"
+}, {
+    "location": {
+        "latitude": 402948455,
+        "longitude": -747903913
+    },
+    "name": "3 Drake Lane, Pennington, NJ 08534, USA"
+}, {
+    "location": {
+        "latitude": 406337092,
+        "longitude": -740122226
+    },
+    "name": "6324 8th Avenue, Brooklyn, NY 11220, USA"
+}, {
+    "location": {
+        "latitude": 406421967,
+        "longitude": -747727624
+    },
+    "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA"
+}, {
+    "location": {
+        "latitude": 416318082,
+        "longitude": -749677716
+    },
+    "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA"
+}, {
+    "location": {
+        "latitude": 415301720,
+        "longitude": -748416257
+    },
+    "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA"
+}, {
+    "location": {
+        "latitude": 402647019,
+        "longitude": -747071791
+    },
+    "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA"
+}, {
+    "location": {
+        "latitude": 412567807,
+        "longitude": -741058078
+    },
+    "name": "New York State Reference Route 987E, Southfields, NY 10975, USA"
+}, {
+    "location": {
+        "latitude": 416855156,
+        "longitude": -744420597
+    },
+    "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA"
+}, {
+    "location": {
+        "latitude": 404663628,
+        "longitude": -744820157
+    },
+    "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA"
+}, {
+    "location": {
+        "latitude": 407113723,
+        "longitude": -749746483
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 402133926,
+        "longitude": -743613249
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 400273442,
+        "longitude": -741220915
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 411236786,
+        "longitude": -744070769
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 411633782,
+        "longitude": -746784970
+    },
+    "name": "211-225 Plains Road, Augusta, NJ 07822, USA"
+}, {
+    "location": {
+        "latitude": 415830701,
+        "longitude": -742952812
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 413447164,
+        "longitude": -748712898
+    },
+    "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA"
+}, {
+    "location": {
+        "latitude": 405047245,
+        "longitude": -749800722
+    },
+    "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA"
+}, {
+    "location": {
+        "latitude": 418858923,
+        "longitude": -746156790
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 417951888,
+        "longitude": -748484944
+    },
+    "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA"
+}, {
+    "location": {
+        "latitude": 407033786,
+        "longitude": -743977337
+    },
+    "name": "26 East 3rd Street, New Providence, NJ 07974, USA"
+}, {
+    "location": {
+        "latitude": 417548014,
+        "longitude": -740075041
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 410395868,
+        "longitude": -744972325
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 404615353,
+        "longitude": -745129803
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 406589790,
+        "longitude": -743560121
+    },
+    "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA"
+}, {
+    "location": {
+        "latitude": 414653148,
+        "longitude": -740477477
+    },
+    "name": "18 Lannis Avenue, New Windsor, NY 12553, USA"
+}, {
+    "location": {
+        "latitude": 405957808,
+        "longitude": -743255336
+    },
+    "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA"
+}, {
+    "location": {
+        "latitude": 411733589,
+        "longitude": -741648093
+    },
+    "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA"
+}, {
+    "location": {
+        "latitude": 412676291,
+        "longitude": -742606606
+    },
+    "name": "1270 Lakes Road, Monroe, NY 10950, USA"
+}, {
+    "location": {
+        "latitude": 409224445,
+        "longitude": -748286738
+    },
+    "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA"
+}, {
+    "location": {
+        "latitude": 406523420,
+        "longitude": -742135517
+    },
+    "name": "652 Garden Street, Elizabeth, NJ 07202, USA"
+}, {
+    "location": {
+        "latitude": 401827388,
+        "longitude": -740294537
+    },
+    "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA"
+}, {
+    "location": {
+        "latitude": 410564152,
+        "longitude": -743685054
+    },
+    "name": "13-17 Stanley Street, West Milford, NJ 07480, USA"
+}, {
+    "location": {
+        "latitude": 408472324,
+        "longitude": -740726046
+    },
+    "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA"
+}, {
+    "location": {
+        "latitude": 412452168,
+        "longitude": -740214052
+    },
+    "name": "5 White Oak Lane, Stony Point, NY 10980, USA"
+}, {
+    "location": {
+        "latitude": 409146138,
+        "longitude": -746188906
+    },
+    "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA"
+}, {
+    "location": {
+        "latitude": 404701380,
+        "longitude": -744781745
+    },
+    "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA"
+}, {
+    "location": {
+        "latitude": 409642566,
+        "longitude": -746017679
+    },
+    "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA"
+}, {
+    "location": {
+        "latitude": 408031728,
+        "longitude": -748645385
+    },
+    "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA"
+}, {
+    "location": {
+        "latitude": 413700272,
+        "longitude": -742135189
+    },
+    "name": "367 Prospect Road, Chester, NY 10918, USA"
+}, {
+    "location": {
+        "latitude": 404310607,
+        "longitude": -740282632
+    },
+    "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA"
+}, {
+    "location": {
+        "latitude": 409319800,
+        "longitude": -746201391
+    },
+    "name": "11 Ward Street, Mount Arlington, NJ 07856, USA"
+}, {
+    "location": {
+        "latitude": 406685311,
+        "longitude": -742108603
+    },
+    "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA"
+}, {
+    "location": {
+        "latitude": 419018117,
+        "longitude": -749142781
+    },
+    "name": "43 Dreher Road, Roscoe, NY 12776, USA"
+}, {
+    "location": {
+        "latitude": 412856162,
+        "longitude": -745148837
+    },
+    "name": "Swan Street, Pine Island, NY 10969, USA"
+}, {
+    "location": {
+        "latitude": 416560744,
+        "longitude": -746721964
+    },
+    "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA"
+}, {
+    "location": {
+        "latitude": 405314270,
+        "longitude": -749836354
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 414219548,
+        "longitude": -743327440
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 415534177,
+        "longitude": -742900616
+    },
+    "name": "565 Winding Hills Road, Montgomery, NY 12549, USA"
+}, {
+    "location": {
+        "latitude": 406898530,
+        "longitude": -749127080
+    },
+    "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA"
+}, {
+    "location": {
+        "latitude": 407586880,
+        "longitude": -741670168
+    },
+    "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA"
+}, {
+    "location": {
+        "latitude": 400106455,
+        "longitude": -742870190
+    },
+    "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA"
+}, {
+    "location": {
+        "latitude": 400066188,
+        "longitude": -746793294
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 418803880,
+        "longitude": -744102673
+    },
+    "name": "40 Mountain Road, Napanoch, NY 12458, USA"
+}, {
+    "location": {
+        "latitude": 414204288,
+        "longitude": -747895140
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 414777405,
+        "longitude": -740615601
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 415464475,
+        "longitude": -747175374
+    },
+    "name": "48 North Road, Forestburgh, NY 12777, USA"
+}, {
+    "location": {
+        "latitude": 404062378,
+        "longitude": -746376177
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 405688272,
+        "longitude": -749285130
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 400342070,
+        "longitude": -748788996
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 401809022,
+        "longitude": -744157964
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 404226644,
+        "longitude": -740517141
+    },
+    "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA"
+}, {
+    "location": {
+        "latitude": 410322033,
+        "longitude": -747871659
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 407100674,
+        "longitude": -747742727
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 418811433,
+        "longitude": -741718005
+    },
+    "name": "213 Bush Road, Stone Ridge, NY 12484, USA"
+}, {
+    "location": {
+        "latitude": 415034302,
+        "longitude": -743850945
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 411349992,
+        "longitude": -743694161
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 404839914,
+        "longitude": -744759616
+    },
+    "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA"
+}, {
+    "location": {
+        "latitude": 414638017,
+        "longitude": -745957854
+    },
+    "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA"
+}, {
+    "location": {
+        "latitude": 412127800,
+        "longitude": -740173578
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 401263460,
+        "longitude": -747964303
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 412843391,
+        "longitude": -749086026
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 418512773,
+        "longitude": -743067823
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 404318328,
+        "longitude": -740835638
+    },
+    "name": "42-102 Main Street, Belford, NJ 07718, USA"
+}, {
+    "location": {
+        "latitude": 419020746,
+        "longitude": -741172328
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 404080723,
+        "longitude": -746119569
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 401012643,
+        "longitude": -744035134
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 404306372,
+        "longitude": -741079661
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 403966326,
+        "longitude": -748519297
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 405002031,
+        "longitude": -748407866
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 409532885,
+        "longitude": -742200683
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 416851321,
+        "longitude": -742674555
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 406411633,
+        "longitude": -741722051
+    },
+    "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA"
+}, {
+    "location": {
+        "latitude": 413069058,
+        "longitude": -744597778
+    },
+    "name": "261 Van Sickle Road, Goshen, NY 10924, USA"
+}, {
+    "location": {
+        "latitude": 418465462,
+        "longitude": -746859398
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 411733222,
+        "longitude": -744228360
+    },
+    "name": ""
+}, {
+    "location": {
+        "latitude": 410248224,
+        "longitude": -747127767
+    },
+    "name": "3 Hasta Way, Newton, NJ 07860, USA"
+}]
diff --git a/src/node/examples/route_guide_server.js b/src/node/examples/route_guide_server.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc9ed251019f94cd94e49cc3964554f89cc95e38
--- /dev/null
+++ b/src/node/examples/route_guide_server.js
@@ -0,0 +1,249 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+var fs = require('fs');
+var parseArgs = require('minimist');
+var path = require('path');
+var _ = require('underscore');
+var grpc = require('..');
+var examples = grpc.load(__dirname + '/route_guide.proto').examples;
+
+var Server = grpc.buildServer([examples.RouteGuide.service]);
+
+var COORD_FACTOR = 1e7;
+
+/**
+ * For simplicity, a point is a record type that looks like
+ * {latitude: number, longitude: number}, and a feature is a record type that
+ * looks like {name: string, location: point}. feature objects with name===''
+ * are points with no feature.
+ */
+
+/**
+ * List of feature objects at points that have been requested so far.
+ */
+var feature_list = [];
+
+/**
+ * Get a feature object at the given point, or creates one if it does not exist.
+ * @param {point} point The point to check
+ * @return {feature} The feature object at the point. Note that an empty name
+ *     indicates no feature
+ */
+function checkFeature(point) {
+  var feature;
+  // Check if there is already a feature object for the given point
+  for (var i = 0; i < feature_list.length; i++) {
+    feature = feature_list[i];
+    if (feature.location.latitude === point.latitude &&
+        feature.location.longitude === point.longitude) {
+      return feature;
+    }
+  }
+  var name = '';
+  feature = {
+    name: name,
+    location: point
+  };
+  return feature;
+}
+
+/**
+ * getFeature request handler. Gets a request with a point, and responds with a
+ * feature object indicating whether there is a feature at that point.
+ * @param {EventEmitter} call Call object for the handler to process
+ * @param {function(Error, feature)} callback Response callback
+ */
+function getFeature(call, callback) {
+  callback(null, checkFeature(call.request));
+}
+
+/**
+ * listFeatures request handler. Gets a request with two points, and responds
+ * with a stream of all features in the bounding box defined by those points.
+ * @param {Writable} call Writable stream for responses with an additional
+ *     request property for the request value.
+ */
+function listFeatures(call) {
+  var lo = call.request.lo;
+  var hi = call.request.hi;
+  var left = _.min([lo.longitude, hi.longitude]);
+  var right = _.max([lo.longitude, hi.longitude]);
+  var top = _.max([lo.latitude, hi.latitude]);
+  var bottom = _.min([lo.latitude, hi.latitude]);
+  // For each feature, check if it is in the given bounding box
+  _.each(feature_list, function(feature) {
+    if (feature.name === '') {
+      return;
+    }
+    if (feature.location.longitude >= left &&
+        feature.location.longitude <= right &&
+        feature.location.latitude >= bottom &&
+        feature.location.latitude <= top) {
+      call.write(feature);
+    }
+  });
+  call.end();
+}
+
+/**
+ * Calculate the distance between two points using the "haversine" formula.
+ * This code was taken from http://www.movable-type.co.uk/scripts/latlong.html.
+ * @param start The starting point
+ * @param end The end point
+ * @return The distance between the points in meters
+ */
+function getDistance(start, end) {
+  function toRadians(num) {
+    return num * Math.PI / 180;
+  }
+  var lat1 = start.latitude / COORD_FACTOR;
+  var lat2 = end.latitude / COORD_FACTOR;
+  var lon1 = start.longitude / COORD_FACTOR;
+  var lon2 = end.longitude / COORD_FACTOR;
+  var R = 6371000; // metres
+  var φ1 = toRadians(lat1);
+  var φ2 = toRadians(lat2);
+  var Δφ = toRadians(lat2-lat1);
+  var Δλ = toRadians(lon2-lon1);
+
+  var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
+      Math.cos(φ1) * Math.cos(φ2) *
+      Math.sin(Δλ/2) * Math.sin(Δλ/2);
+  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+
+  return R * c;
+}
+
+/**
+ * recordRoute handler. Gets a stream of points, and responds with statistics
+ * about the "trip": number of points, number of known features visited, total
+ * distance traveled, and total time spent.
+ * @param {Readable} call The request point stream.
+ * @param {function(Error, routeSummary)} callback The callback to pass the
+ *     response to
+ */
+function recordRoute(call, callback) {
+  var point_count = 0;
+  var feature_count = 0;
+  var distance = 0;
+  var previous = null;
+  // Start a timer
+  var start_time = process.hrtime();
+  call.on('data', function(point) {
+    point_count += 1;
+    if (checkFeature(point).name !== '') {
+      feature_count += 1;
+    }
+    /* For each point after the first, add the incremental distance from the
+     * previous point to the total distance value */
+    if (previous != null) {
+      distance += getDistance(previous, point);
+    }
+    previous = point;
+  });
+  call.on('end', function() {
+    callback(null, {
+      point_count: point_count,
+      feature_count: feature_count,
+      // Cast the distance to an integer
+      distance: distance|0,
+      // End the timer
+      elapsed_time: process.hrtime(start_time)[0]
+    });
+  });
+}
+
+var route_notes = {};
+
+/**
+ * Turn the point into a dictionary key.
+ * @param {point} point The point to use
+ * @return {string} The key for an object
+ */
+function pointKey(point) {
+  return point.latitude + ' ' + point.longitude;
+}
+
+/**
+ * routeChat handler. Receives a stream of message/location pairs, and responds
+ * with a stream of all previous messages at each of those locations.
+ * @param {Duplex} call The stream for incoming and outgoing messages
+ */
+function routeChat(call) {
+  call.on('data', function(note) {
+    var key = pointKey(note.location);
+    /* For each note sent, respond with all previous notes that correspond to
+     * the same point */
+    if (route_notes.hasOwnProperty(key)) {
+      _.each(route_notes[key], function(note) {
+        call.write(note);
+      });
+    } else {
+      route_notes[key] = [];
+    }
+    // Then add the new note to the list
+    route_notes[key].push(JSON.parse(JSON.stringify(note)));
+  });
+  call.on('end', function() {
+    call.end();
+  });
+}
+
+/**
+ * Get a new server with the handler functions in this file bound to the methods
+ * it serves.
+ * @return {Server} The new server object
+ */
+function getServer() {
+  return new Server({
+    'examples.RouteGuide' : {
+      getFeature: getFeature,
+      listFeatures: listFeatures,
+      recordRoute: recordRoute,
+      routeChat: routeChat
+    }
+  });
+}
+
+if (require.main === module) {
+  // If this is run as a script, start a server on an unused port
+  var routeServer = getServer();
+  routeServer.bind('0.0.0.0:50051');
+  var argv = parseArgs(process.argv, {
+    string: 'db_path'
+  });
+  fs.readFile(path.resolve(argv.db_path), function(err, data) {
+    if (err) throw err;
+    feature_list = JSON.parse(data);
+    routeServer.listen();
+  });
+}
+
+exports.getServer = getServer;
diff --git a/src/node/package.json b/src/node/package.json
index a71577fb96620ebdba3d1fa9eb3d021561f5055d..70327941eeb61c62726460f441df485305382d2b 100644
--- a/src/node/package.json
+++ b/src/node/package.json
@@ -15,9 +15,10 @@
     "underscore.string": "^3.0.0"
   },
   "devDependencies": {
-    "mocha": "~1.21.0",
+    "async": "^0.9.0",
+    "googleauth": "google/google-auth-library-nodejs",
     "minimist": "^1.1.0",
-    "googleauth": "google/google-auth-library-nodejs"
+    "mocha": "~1.21.0"
   },
   "files": [
     "README.md",