Using Cloud Datastore with Go

This page of the Go 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 Go Bookshelf App.

Configuring settings

  1. Go to the directory that contains the sample code:

    Linux/Mac OS X

    cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/getting-started/bookshelf
    

    Windows

    cd %GOPATH%\src\github.com\GoogleCloudPlatform\golang-samples\getting-started\bookshelf
    

  2. Open config.go for editing.

  3. Uncomment this line:

    // DB, err = configureDatastoreDB("projectid")
    
  4. Replace "projectid" with your project ID.

  5. Save and close config.go.

Running the app on your local machine

  1. Run the app to start a local web server:

    cd app
    go run app.go auth.go template.go
    
  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.

Press Control+C to exit the local web server.

Deploying the app to the App Engine flexible environment

  1. In the app directory, enter this command to deploy the sample:

    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.

Application structure

This diagram shows how the application components work together.

Bookshelf app deployment process and structure

Understanding the code

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

Handling user submissions with forms

When a user clicks Add Book, the browser navigates to /books/add, and the addFormHandler function in app/app.go renders a form where the user can add a book:

r.Methods("GET").Path("/books/add").
	Handler(appHandler(addFormHandler))
// addFormHandler displays a form that captures details of a new book to add to
// the database.
func addFormHandler(w http.ResponseWriter, r *http.Request) *appError {
	return editTmpl.Execute(w, r, nil)
}

Image of add/edit Form

The form includes text input fields for Title, Author, Date Published and Description (it also includes some others, which are used in other sections of the tutorial):

<form method="post" enctype="multipart/form-data" action="/books{{if .}}/{{.ID}}{{end}}">
  <div class="form-group">
    <label for="title">Title</label>
    <input class="form-control" name="title" id="title" value="{{.Title}}">
  </div>
  <div class="form-group">
    <label for="author">Author</label>
    <input class="form-control" name="author" id="author" value="{{.Author}}">
  </div>
  <div class="form-group">
    <label for="publishedDate">Date Published</label>
    <input class="form-control" name="publishedDate" id="publishedDate" value="{{.PublishedDate}}">
  </div>
  <div class="form-group">
    <label for="description">Description</label>
    <input class="form-control" name="description" id="description" value="{{.Description}}">
  </div>
  <div class="form-group">
    <label for="image">Cover Image</label>
    <input class="form-control" name="image" id="image" type="file">
  </div>
  <button class="btn btn-success">Save</button>
  <input type="hidden" name="imageURL" value="{{.ImageURL}}">
  <input type="hidden" name="createdBy" value="{{.CreatedBy}}">
  <input type="hidden" name="createdByID" value="{{.CreatedByID}}">
</form>

Handling form submissions

When a user fills in the Add book form and then clicks Save, the createHandler function starts the process of sending the submitted data to Cloud Datastore by passing it to the bookshelf.DB.AddBook method:

r.Methods("POST").Path("/books").
	Handler(appHandler(createHandler))
// createHandler adds a book to the database.
func createHandler(w http.ResponseWriter, r *http.Request) *appError {
	book, err := bookFromForm(r)
	if err != nil {
		return appErrorf(err, "could not parse book from form: %v", err)
	}
	id, err := bookshelf.DB.AddBook(book)
	if err != nil {
		return appErrorf(err, "could not save book: %v", err)
	}
	go publishUpdate(id)
	http.Redirect(w, r, fmt.Sprintf("/books/%d", id), http.StatusFound)
	return nil
}

The AddBook function, defined in db_datastore.go, performs a Put() operation to add the user's submitted data to the datastore:

// AddBook saves a given book, assigning it a new ID.
func (db *datastoreDB) AddBook(b *Book) (id int64, err error) {
	ctx := context.Background()
	k := datastore.IncompleteKey("Book", nil)
	k, err = db.client.Put(ctx, k, b)
	if err != nil {
		return 0, fmt.Errorf("datastoredb: could not put Book: %v", err)
	}
	return k.ID, nil
}

When the user clicks Books, the browser navigates to the /books page, and the ListBooks method displays all the books in the datastore.

The statement datastore.NewQuery("Book") creates a query that selects all entities with kind equal to "Book". A single app can store more than one kind in the datastore. The query includes .Order("Title"), which specifies that the results should be ordered alphabetically by book title.

The query is executed by calling GetAll, which takes the query object and appends all the returned entities to the books slice. Finally, the ID field is set on the returned entities:

// ListBooks returns a list of books, ordered by title.
func (db *datastoreDB) ListBooks() ([]*Book, error) {
	ctx := context.Background()
	books := make([]*Book, 0)
	q := datastore.NewQuery("Book").
		Order("Title")

	keys, err := db.client.GetAll(ctx, q, &books)

	if err != nil {
		return nil, fmt.Errorf("datastoredb: could not list books: %v", err)
	}

	for i, k := range keys {
		books[i].ID = k.ID
	}

	return books, nil
}

Send feedback about...