Skip to content

Does this code use gcloud.datastore the way it should? #365

Description

@jgeewax

Comments appreciated.

If it doesn't we might want to consider updating documentation to avoid whatever problems it might have.

/cc @stephenplusplus @ryanseys

var express = require('express');
var bodyParser = require('body-parser');
var gcloud = require('gcloud');
var util = require('util');
var app = express();
// Book data
var books = [
    { name: "War and Peace", author: "Leo Tolstoy" },
    { name: "The Count of Monte Cristo", author: "Alexandre Dumas" }
  ];
if (!process.env.PROJECT_ID) {
  console.log("The environment variable PROJECT_ID must be set to the project's id");
  process.exit(1);
}
// Initialize the store
var dataset = gcloud.datastore.dataset({ 
  projectId: process.env.PROJECT_ID
});
// Add a book
function addBook(transaction, book) {
  transaction.save({ key: dataset.key('Book'), data: book });
}
// Find a book
function findBooks(transaction, book, cb) {
  var query = dataset.createQuery(['Book']);
  book = book || {};
  if (book.author)
    query = query.filter('author = ', book.author);
  if (book.name)
    query = query.filter('name = ', book.name);
  transaction.runQuery(query, function (err, entities, endCursor) {
    if (err) return cb(err);
    cb(null, entities);
  });
}
// Ensure initial books are at least there.
function ensureExists(book, cb) {
  dataset.runInTransaction(function (transaction, done) {
    findBooks(transaction, book, function (err, entities) {
      if (!err && entities.length == 0) {
        addBook(transaction, book);
      }
      done();
    });
  }, cb);
}
books.forEach(function (book) {
  ensureExists(book, function (err) {
    console.log('Initial book error', err);
  });
});
// Add middle-ware
app.use(bodyParser.json());
// Add routes
app.get("/", function(req, res) {
  res.send('Hello World!');
});
function booksQuery(req, res, author) {
  dataset.runInTransaction(function (transaction, done) {
    findBooks(transaction, { author: author }, function (err, books) {
      if (err) return res.sendStatus(500);
      res.json(books.map(function (entity) {
        return entity.data;
      }));
      done();
    });
  }, function (err) {
    if (err) return res.sendStatus(500);
  });
}
app.get("/books", function (req, res) {
  booksQuery(req, res);
});
app.get("/books/:author", function (req, res) {
  booksQuery(req, res, req.param.author);
});
app.put("/books", function (req, res) {
  if (!req.body || !req.body.name || !req.body.author) return res.sendStatus(400);
  ensureExists({ author : req.body.author, name: req.body.name }, function (err) {
    if (err) return res.sendStatus(500);
    res.sendStatus(200);
  });
});
// Start server
var server = app.listen(8080, function () {
  console.log('Server running at http://localhost:%s', server.address().port);
});

Activity

  1. added
    type: questionRequest for information or clarification. Not an issue.
    api: datastoreIssues related to the Datastore API.
    on Jan 29, 2015
  2. ryanseys commented on Jan 31, 2015

    @ryanseys
    Contributor

    I found the following hard issues:

    • req.param.author should be req.params.author.
    • console.log('Initial book error', err); should be if(err) console.log('Initial book error', err);
    • Add credentials to datastore initialization.

    Here's an updated version that fixes the issues above and features a bit more documentation. In addition to this, there are certain issues such as lack of braces on if statements, lack of new lines in if statements which aren't killers here and allow the application to still be functional, but differ from the more strict style guides we have adopted in the gcloud-node library. I haven't fixed those in this code and tried to assume the style that was already given to me.

    If you would like me to update the following snippet with those style guidelines, please let me know.

    var express = require('express');
    var bodyParser = require('body-parser');
    var gcloud = require('gcloud');
    var util = require('util');
    var app = express();
    
    var projectId = process.env.PROJECT_ID; // E.g. 'grape-smasher-902'
    var keyFile = process.env.GCLOUD_KEYFILE; // E.g. '~/.secrets/key.json'
    
    if (!projectId) {
      throw new Error("The project ID must be set in the environment variable PROJECT_ID");
    }
    
    // Initial book data
    var books = [
      { name: "War and Peace", author: "Leo Tolstoy" },
      { name: "The Count of Monte Cristo", author: "Alexandre Dumas" }
    ];
    
    // Initialize the store
    var dataset = gcloud.datastore.dataset({
      projectId: projectId,
      credentials: require(keyFile)
    });
    
    // Add a book
    function addBook(transaction, book) {
      transaction.save({ key: dataset.key('Book'), data: book });
    }
    
    // Find books that match a given book's values (or get all books)
    function findBooks(transaction, book, cb) {
      var query = dataset.createQuery(['Book']);
      book = book || {};
      if (book.author)
        query = query.filter('author = ', book.author);
      if (book.name)
        query = query.filter('name = ', book.name);
      transaction.runQuery(query, function (err, entities, endCursor) {
        if (err) return cb(err);
        cb(null, entities);
      });
    }
    
    // Ensure a book exists in the datastore, otherwise create it.
    function ensureExists(book, cb) {
      dataset.runInTransaction(function (transaction, done) {
        findBooks(transaction, book, function (err, entities) {
          if (!err && entities.length === 0) {
            addBook(transaction, book);
          }
          done();
        });
      }, cb);
    }
    
    // Query books by a certain author
    function booksQuery(req, res, author) {
      dataset.runInTransaction(function (transaction, done) {
        findBooks(transaction, { author: author }, function (err, books) {
          if (err) return res.sendStatus(500);
          res.json(books.map(function (entity) {
            return entity.data;
          }));
          done();
        });
      }, function (err) {
        if (err) return res.sendStatus(500);
      });
    }
    
    // Add Express middle-ware
    app.use(bodyParser.json());
    
    // Add routes
    app.get("/", function(req, res) {
      res.send('Hello World!');
    });
    
    // GET /books returns all books
    app.get("/books", function (req, res) {
      booksQuery(req, res);
    });
    
    // GET /books/Leo%20Tolstoy returns all books where author="Leo Tolstoy"
    app.get("/books/:author", function (req, res) {
      booksQuery(req, res, req.params.author);
    });
    
    // Create a book if it does not exist with a given name and author.
    app.put("/books", function (req, res) {
      if (!req.body || !req.body.name || !req.body.author) return res.sendStatus(400);
      ensureExists({ author : req.body.author, name: req.body.name }, function (err) {
        if (err) return res.sendStatus(500);
        res.sendStatus(200);
      });
    });
    
    // Ensure all initial books are created or already exist in the datastore.
    books.forEach(function (book) {
      ensureExists(book, function (err) {
        if(err) console.log('Initial book error', err);
      });
    });
    
    // Start the server
    var server = app.listen(8080, function () {
      console.log('Server running at http://localhost:%s', server.address().port);
    });
  3. added this to the Datastore Future milestone on Feb 2, 2015
  4. chuckjaz commented on Feb 2, 2015

    @chuckjaz

    This example originally came from me.

    I made some significant modifications to this file:

    The file is now in a repository here, repeated here:

    I have significantly simplified the example, removing the code to pre-initialize the datastore as it unnecessarily complicated the example. The population of the data will be handled elsewhere.

    I didn't include the credentials because this assumes you will be authenticated via the metadata store (running in GCE or Managed VMs).

    var express = require('express');
    var bodyParser = require('body-parser');
    var gcloud = require('gcloud');
    var app = express();
    
    // Initialize the store
    var dataset = gcloud.datastore.dataset({ 
      projectId: process.env.PROJECT_ID
    });
    
    // Add a book
    function addBook(book, cb) {
      dataset.save({ key: dataset.key('Book'), data: book }, cb);
    }
    
    // Find all books
    function findAllBooks(cb) {
      dataset.runQuery(dataset.createQuery(['Book']), cb);
    }
    
    // Find an author's books
    function findAuthorBooks(author, cb) {
      dataset.runQuery(dataset.createQuery(['Book']).filter("author = ", author), cb);
    }
    
    // Add middle-ware
    app.use(bodyParser.json());
    
    // Add routes
    app.get("/", function(req, res) {
      res.send('Hello World!\n');
    });
    
    // GET /books returns all books
    app.get("/books", function (req, res) {
      findAllBooks(function (err, books) {
        if (err) return res.sendStatus(500);
        res.json(books.map(function (entity) {
          return entity.data;
        }));
      });
      res.json(books);
    });
    
    // GET /books/Leo%20Tolstoy returns all books where author="Leo Tolstoy"
    app.get("/books/:author", function (req, res) {
      findAuthor(req.params.author, function (err, books) {
        if (err) return res.sendStatus(500);
        res.json(books.map(function (entity) {
          return entity.data;
        }));
      });
    });
    
    // Create a book with a given name and author
    app.put("/books", function (req, res) {
      if (!req.body || !req.body.name || !req.body.author) return res.sendStatus(400);
      addBook({ author: req.body.author, name: req.body.name }, function (err) {
        if (err) return res.sendStatus(500);
        res.sendStatus(200);
      });
    });
    
    // Start server
    var server = app.listen(8080, function () {
      console.log('Server running at http://localhost:%s', server.address().port);
    });
  5. ryanseys commented on Feb 2, 2015

    @ryanseys
    Contributor

    Looks awesome now! 👍

  6. chuckjaz commented on Feb 2, 2015

    @chuckjaz

    Thanks!

    One issue I brought up with JJ privately but wanted to bring up publicly is the projectId. The initialization of the datastore looks like:

    var dataset = gcloud.datastore.dataset({ 
      projectId: process.env.PROJECT_ID
    })

    This seems unnecessary to me as, if you are in GCE or Managed VMs, the project id can be retrieved from the metadata store [http://metadata.google.internal/computeMetadata/v1/project/project-id] as in,

    curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/project/project-id
    

    Could we make the default project id the value of requesting the project-id from the metadata server?

    Chuck.

  7. ryanseys commented on Feb 2, 2015

    @ryanseys
    Contributor

    I might be mistaken but in Google App Engine and/or Managed VMs you can use the process.env.GAE_LONG_APP_ID environment variable to get the projectId.

  8. stephenplusplus commented on Feb 2, 2015

    @stephenplusplus
    Contributor

    Good idea to automatically use the environment's project ID. I believe we did that way back, then I forgot to re-implement. Left a to do on the PR that is tidying up the GAE story: #328

  9. chuckjaz commented on Feb 2, 2015

    @chuckjaz

    Thanks. I will close this issue as the review is done.

  10. 39 remaining items

  11. added a commit that references this issue on Feb 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

api: datastoreIssues related to the Datastore API.type: questionRequest for information or clarification. Not an issue.

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions