Using Cloud Datastore with Node.js

This page of the Node.js Bookshelf tutorial shows how the sample app stores its persistent data in 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 read the setup instructions, go to Node.js Bookshelf app.

Configuring settings

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 by using npm:

npm install

Running the app on your local machine

  1. Start a local web server using npm:

    npm start
    
  2. In your web browser, enter the following address:

    http://localhost:8080

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

Deploying the app to the App Engine standard environment

  1. Deploy the sample app from the nodejs-getting-started/2-structured-data directory:

    gcloud app deploy
    
  2. In your web browser, enter this address:

    https://[YOUR_PROJECT_ID].appspot.com
    

    Replace [YOUR_PROJECT_ID] with your GCP project ID.

If you update your app, you deploy the updated version by entering the same command that you used to deploy the app. The new deployment creates a new version of your app and promotes it to the default version. The older versions of your app remain. By default the App Engine standard environment scales to 0 instances when there is no incoming traffic to a version. Thus, unused versions shouldn't cost anything. However, all of these app versions are still billable resources.

See the Cleaning up section in the final step of this tutorial for more information on cleaning up billable resources, including non-default app versions.

Understanding the code

This section walks you through the app's code and explains how it works.

Handling user submissions with forms

The add/edit form lets users add or edit book submissions within the app.

Image of add/edit form

The HTML form is created by 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.
  model.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 uses the user's submitted data to create a new book submission.

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

Here is the update function that saves 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 the extended entity property format for Cloud Datastore.

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 add books, they can click the Books link to go to the /books page, which lists all the books that are currently stored in Cloud Datastore. The page uses the books/model-datastore.js file, which initializes a Cloud Datastore client named ds and sets the kind variable to Book, which is the kind of entity the model manages.

const ds = new Datastore();
const kind = 'Book';

The list function lists all of the books that are using data retrieved from Cloud Datastore. The const q = ds.createQuery([kind]) statement creates a query named q that selects all books in Cloud Datastore.

The following additional properties are added to the query:

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

You run the query by calling ds.runQuery, which takes the query object q as its first parameter. The second parameter is a callback that takes the arguments (err, entities, nextQuery). The list function's callback cb takes the arguments (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);
  });
}
Was this page helpful? Let us know how we did:

Send feedback about...