diff --git a/firestore/README.md b/firestore/README.md new file mode 100644 index 0000000000..13076255d8 --- /dev/null +++ b/firestore/README.md @@ -0,0 +1,72 @@ +Google Cloud Platform logo + +# [Cloud Firestore: Node.js Samples](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/main/firestore) + +This collection of samples demonstrates how to perform various operations with Google Cloud Firestore using Node.js, including basic reads and writes, data pipelines, and solution counters. + +## Table of Contents + +* [Before you begin](#before-you-begin) +* [Samples](#samples) + * [Limit-to-last-query](#limit-to-last-query) + * [Pipelines-quickstart](#pipelines-quickstart) + * [Quickstart](#quickstart) + * [Solution-counters](#solution-counters) +* [Running the tests](#running-the-tests) + +## Before you begin + +Before running the samples, make sure you've followed the steps outlined in +[Using the client library](https://github.com/GoogleCloudPlatform/nodejs-docs-samples#using-the-client-library). + +`npm install` + +## Samples + +### Limit-to-last-query + +View the [source code](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/main/firestore/limit-to-last-query.js). + +__Usage:__ + +`node limit-to-last-query.js` + +----- + +### Pipelines-quickstart + +View the [source code](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/main/firestore/pipelines-quickstart.js). + +__Usage:__ + +`node pipelines-quickstart.js` + +----- + +### Quickstart + +View the [source code](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/main/firestore/quickstart.js). + +__Usage:__ + +`node quickstart.js` + +----- + +### Solution-counters + +View the [source code](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/main/firestore/solution-counters.js). + +__Usage:__ + +`node solution-counters.js` + +----- + +## Running the tests + +To run the automated tests for these samples, execute: + +`npm test` + +[product-docs]: https://cloud.google.com/firestore \ No newline at end of file diff --git a/firestore/limit-to-last-query.js b/firestore/limit-to-last-query.js new file mode 100644 index 0000000000..c901ff5c07 --- /dev/null +++ b/firestore/limit-to-last-query.js @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START firestore_limit_to_last_query] +const {Firestore} = require('@google-cloud/firestore'); + +// Create a new client +const firestore = new Firestore(); + +async function limitToLastQuery() { + try { + const collectionReference = firestore.collection('cities'); + const cityDocuments = await collectionReference + .orderBy('name') + .limitToLast(2) + .get(); + const cityDocumentData = cityDocuments.docs.map(d => d.data()); + cityDocumentData.forEach(doc => { + console.log(doc.name); + }); + } catch (error) { + console.error('Error executing limitToLastQuery:', error); + } +} +limitToLastQuery(); +// [END firestore_limit_to_last_query] diff --git a/firestore/package.json b/firestore/package.json new file mode 100644 index 0000000000..a050db5eb4 --- /dev/null +++ b/firestore/package.json @@ -0,0 +1,20 @@ +{ + "name": "@google-cloud/firestore-samples", + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "repository": "googleapis/nodejs-firestore", + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "mocha --timeout 600000" + }, + "dependencies": { + "@google-cloud/firestore": "^8.3.0" + }, + "devDependencies": { + "chai": "^4.2.0", + "mocha": "^7.0.0" + } +} \ No newline at end of file diff --git a/firestore/pipelines-quickstart.js b/firestore/pipelines-quickstart.js new file mode 100644 index 0000000000..aed1663ab0 --- /dev/null +++ b/firestore/pipelines-quickstart.js @@ -0,0 +1,63 @@ +// Copyright 2026 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START firestore_pipelines_quickstart] +const {Firestore} = require('@google-cloud/firestore'); +const {field} = require('@google-cloud/firestore/pipelines'); + +// Create a new client +const firestore = new Firestore(); + +async function quickstartPipelines() { + try { + // Obtain a collection reference. + const collection = firestore.collection('posts'); + + // Create a few new posts + for (let i = 0; i < 5; i++) { + await collection.add({ + title: `Post ${i}`, + rating: Math.random() * 10, // random rating on a 10 point scale + }); + } + console.log('Entered new data into the collection'); + + // Create a Pipeline that queries the 'posts' collection. + // Select the fields 'rating' and 'title', and convert the title to uppercase. + // Filter the results to only include posts with rating > 5. + const myPipeline = firestore + .pipeline() + .collection('posts') + .select('rating', field('title').toUpper().as('uppercaseTitle')) + .where(field('rating').greaterThan(5)); + + // Execute the Pipeline against the Firestore server. + const pipelineSnapshot = await myPipeline.execute(); + + // Iterate over each result in the PipelineSnapshot, printing the + // post to the console. + pipelineSnapshot.results.forEach(pipelineResult => { + console.log(pipelineResult.data()); + }); + } catch (error) { + console.error( + 'Error executing pipelines quickstart:', + error.message || error + ); + } +} +quickstartPipelines(); +// [END firestore_pipelines_quickstart] diff --git a/firestore/quickstart.js b/firestore/quickstart.js new file mode 100644 index 0000000000..ad3b682cec --- /dev/null +++ b/firestore/quickstart.js @@ -0,0 +1,54 @@ +// Copyright 2017 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START firestore_quickstart] +const {Firestore} = require('@google-cloud/firestore'); + +// Create a new client +const firestore = new Firestore(); + +async function quickstart() { + try { + // Obtain a document reference. + const document = firestore.doc('posts/intro-to-firestore'); + + // Enter new data into the document. + await document.set({ + title: 'Welcome to Firestore', + body: 'Hello World', + }); + console.log('Entered new data into the document'); + + // Update an existing document. + await document.update({ + body: 'My first Firestore app', + }); + console.log('Updated an existing document'); + + // Read the document. + const doc = await document.get(); + console.log(`Read the document with ID: ${doc.id}`); + console.log('Document data:', doc.data()); + + // Delete the document. + await document.delete(); + console.log('Deleted the document'); + } catch (error) { + console.error('Error executing quickstart:', error.message || error); + } +} +quickstart(); +// [END firestore_quickstart] diff --git a/firestore/solution-counters.js b/firestore/solution-counters.js new file mode 100644 index 0000000000..639efc6635 --- /dev/null +++ b/firestore/solution-counters.js @@ -0,0 +1,69 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; +const {Firestore, FieldValue} = require('@google-cloud/firestore'); + +async function main() { + // [START firestore_solution_sharded_counter_increment] + function incrementCounter(docRef, numShards) { + const shardId = Math.floor(Math.random() * numShards); + const shardRef = docRef.collection('shards').doc(shardId.toString()); + return shardRef.set({count: FieldValue.increment(1)}, {merge: true}); + } + // [END firestore_solution_sharded_counter_increment] + + // [START firestore_solution_sharded_counter_get] + async function getCount(docRef) { + const querySnapshot = await docRef.collection('shards').get(); + return querySnapshot.docs.reduce( + (total, doc) => total + (doc.get('count') || 0), + 0 + ); + } + // [END firestore_solution_sharded_counter_get] + + // [START firestore_data_delete_doc] + async function deleteDocs(docRef) { + const shardsCollectionRef = docRef.collection('shards'); + const shardDocs = await shardsCollectionRef.get(); + const promises = shardDocs.docs.map(doc => doc.ref.delete()); + return Promise.all(promises); + } + // [END firestore_data_delete_doc] + + try { + // Create a new client + const firestore = new Firestore(); + const docRef = firestore.doc( + 'distributed_counter_samples/distributed_counter' + ); + // Clean up documents from potential prior test runs + await deleteDocs(docRef); + const numberOfShards = 10; + // Increase the document count + await incrementCounter(docRef, numberOfShards); + console.log('counter increased'); + // Get document count + const count = await getCount(docRef); + console.log(`new count is : ${count}`); + // Delete the document + await deleteDocs(docRef); + console.log('Deleted the document'); + } catch (error) { + console.error('Error in sharded counter:', error.message || error); + } +} + +main(); diff --git a/firestore/test/limit-to-last-query.test.js b/firestore/test/limit-to-last-query.test.js new file mode 100644 index 0000000000..ed393bec1d --- /dev/null +++ b/firestore/test/limit-to-last-query.test.js @@ -0,0 +1,48 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const {execSync} = require('child_process'); +const {assert} = require('chai'); +const {after, before, describe, it} = require('mocha'); +const exec = cmd => execSync(cmd, {encoding: 'utf8'}); +const {Firestore, FieldPath} = require('@google-cloud/firestore'); + +describe('limit to last query', () => { + const firestore = new Firestore(); + const cities = ['San Francisco', 'Los Angeles', 'Tokyo', 'Beijing']; + + before(async () => { + await Promise.all( + cities.map(city => firestore.doc(`cities/${city}`).set({name: city})) + ); + }); + + after(async () => { + const cityCollectionRef = firestore.collection('cities'); + const cityDocs = ( + await cityCollectionRef.select(FieldPath.documentId()).get() + ).docs; + await Promise.all( + cityDocs.map(doc => cityCollectionRef.doc(doc.id).delete()) + ); + }); + + it('should run limitToLast query', () => { + const output = exec('node limit-to-last-query.js'); + assert.include(output, 'San Francisco'); + assert.include(output, 'Tokyo'); + }); +}); diff --git a/firestore/test/quickstart.test.js b/firestore/test/quickstart.test.js new file mode 100644 index 0000000000..add01a103e --- /dev/null +++ b/firestore/test/quickstart.test.js @@ -0,0 +1,30 @@ +// Copyright 2017 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const {execSync} = require('child_process'); +const {assert} = require('chai'); +const {describe, it} = require('mocha'); +const exec = cmd => execSync(cmd, {encoding: 'utf8'}); + +describe('should make some API calls', () => { + it('should run quickstart', () => { + const output = exec('node quickstart.js'); + assert.include(output, 'Entered new data into the document'); + assert.include(output, 'Updated an existing document'); + assert.include(output, 'Read the document'); + assert.include(output, 'Deleted the document'); + }); +}); diff --git a/firestore/test/solution-counters.test.js b/firestore/test/solution-counters.test.js new file mode 100644 index 0000000000..bf1f10f06a --- /dev/null +++ b/firestore/test/solution-counters.test.js @@ -0,0 +1,29 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const {execSync} = require('child_process'); +const {assert} = require('chai'); +const {describe, it} = require('mocha'); +const exec = cmd => execSync(cmd, {encoding: 'utf8'}); + +describe('distributed counter', () => { + it('should increase, get counter and delete the docs', () => { + const output = exec('node solution-counters.js'); + assert.include(output, 'counter increased'); + assert.include(output, 'new count is : 1'); + assert.include(output, 'Deleted the document'); + }); +});