Using Cloud Datastore with Node.js

This page of the Node.js Bookshelf tutorial shows how the sample app stores its persistent data in Google Cloud Datastore. The sample code for this step provides examples of how to create, read, update, and delete (CRUD) data stored in Cloud Datastore.

This page is part of a multi-page tutorial. To start from the beginning and see instructions for setting up, go to Node.js Bookshelf App.

Configuring settings

In the nodejs-getting-started/2-structured-data directory, create a config.json file with this content:

{
  "GCLOUD_PROJECT": "[YOUR_PROJECT_ID]",
  "DATA_BACKEND": "datastore"
}

Replace [YOUR_PROJECT_ID] with your project ID.

Cloud Datastore is a fully managed service that is automatically initialized and connected to your App Engine app. No further configuration is required.

Installing dependencies

Install dependencies in the nodejs-getting-started/2-structured-data directory:

  • Using npm:

    npm install
    
  • Using yarn:

    yarn install
    

Running the app on your local machine

  1. Start a local web server using npm or yarn:

    • Using npm:

      npm start
      
    • Using yarn:

      yarn start
      
  2. In your web browser, enter this address.

    http://localhost:8080

Now you can browse the app's web pages and add, edit, and delete books.

Deploying the app to the App Engine flexible environment

  1. Deploy the sample app:

    gcloud app deploy
    

  2. In your web browser, enter this address. Replace [YOUR_PROJECT_ID] with your project ID:

    https://[YOUR_PROJECT_ID].appspot.com
    

If you update your app, you can deploy the updated version by entering the same command you used to deploy the app the first time. The new deployment creates a new version of your app and promotes it to the default version. The older versions of your app remain, as do their associated VM instances. Be aware that all of these app versions and VM instances are billable resources.

You can reduce costs by deleting the non-default versions of your app.

To delete an app version:

  1. In the Cloud Platform Console, go to the App Engine Versions page.

    Go to the Versions page

  2. Click the checkbox next to the non-default app version you want to delete.
  3. Click the Delete button at the top of the page to delete the app version.

For complete information about cleaning up billable resources, see the Cleaning up section in the final step of this tutorial.

Understanding the code

This section walks you through the application code and explains how it works.

Handling user submissions with forms

The add/edit form allows users to add and edit book submissions within the app.

Image of add/edit Form

The HTML form is created using Pug, which is a Node.js template engine. The following Pug template specifies that the form include text input fields for "Title", "Author", "Date Published" and "Description":

block content
  h3 #{action} book
  form(method="POST")
    .form-group
      label(for="title") Title
      input.form-control(type="text", name="title", id="title", value=book.title)
    .form-group
      label(for="author") Author
      input.form-control(type="text", name="author", id="author", value=book.author)
    .form-group
      label(for="publishedDate") Date Published
      input.form-control(type="text", name="publishedDate", id="publishedDate", value=book.publishedDate)
    .form-group
      label(for="description") Description
      input.form-control(type="text", name="description", id="description", value=book.description)
    .form-group
    button.btn.btn-success(type="submit") Save

Handling form submissions

When a user clicks Add Book, the following code in books/crud.js sends the user to the app's /add page, which displays the add/edit form.

router.get('/add', (req, res) => {
  res.render('books/form.pug', {
    book: {},
    action: 'Add'
  });
});

When a user fills out the Add book form and then clicks Save, the following code in books/crud.js handles the form's HTTP POST action. This initiates the process of sending the submitted data to Cloud Datastore by passing the data to the model.create function.

router.post('/add', (req, res, next) => {
  const data = req.body;

  // Save the data to the database.
  getModel().create(data, (err, savedData) => {
    if (err) {
      next(err);
      return;
    }
    res.redirect(`${req.baseUrl}/${savedData.id}`);
  });
});

The books/model-datastore.js file contains the code that performs CRUD functions for data stored in Cloud Datastore. For example the model.create statement calls the create function in model- datastore.js, which sends the user's submitted data to the update function with a null value as the first parameter, which indicates that this is a new book submission.

module.exports = {
  create,
  read,
  update,
  delete: _delete,
  list
};

Here is the update function that does the actual work of saving the user's submitted data to Cloud Datastore. The toDatastore and fromDatastore helper functions are used to translate the user's submitted data from the app's format to Cloud Datastore's extended entity property format.

function update (id, data, cb) {
  let key;
  if (id) {
    key = ds.key([kind, parseInt(id, 10)]);
  } else {
    key = ds.key(kind);
  }

  const entity = {
    key: key,
    data: toDatastore(data, ['description'])
  };

  ds.save(
    entity,
    (err) => {
      data.id = entity.key.id;
      cb(err, err ? null : data);
    }
  );
}

After users have added books, clicking the Books link navigates to the /books page, which list all the books currently stored in Cloud Datastore. The page calls the books/model-datastore.js file which initializes a Datastore dataset named ds and then sets the kind variable to Book, which is the type of entity being stored by this app. A single app can store more than one kind in the datastore.

const ds = Datastore({
  projectId: config.get('GCLOUD_PROJECT')
});
const kind = 'Book';

Next, the list function does the work of listing all the books using data retrieved from Cloud Datastore. The statement var q = ds.createQuery([kind]) creates a query named q that will select all books in the datastore. The following additional properties are added to the query:

  • .limit(limit) sets the maximum amount of results to return per page.
  • .order('title') specifies that the results should be ordered alphabetically by book title.
  • .start(token) specifies the starting point for handling pagination and allows additional pages to be requested based on the value of token.

The query is then executed by calling ds.runQuery, which takes the query object q as the query to be run for its first parameter. The second parameter of ds.runQuery is a callback that is invoked with (err, books, nextPageToken).

function list (limit, token, cb) {
  const q = ds.createQuery([kind])
    .limit(limit)
    .order('title')
    .start(token);

  ds.runQuery(q, (err, entities, nextQuery) => {
    if (err) {
      cb(err);
      return;
    }
    const hasMore = nextQuery.moreResults !== Datastore.NO_MORE_RESULTS ? nextQuery.endCursor : false;
    cb(null, entities.map(fromDatastore), hasMore);
  });
}

Send feedback about...