Use Firestore with Cloud Functions
Contributed by Google employees.
This tutorial demonstrates using Cloud Functions to store and retrieve data with Firestore. The Cloud Function is implemented in Node.js and tested with versions 10, 12, and 14.
The sample Cloud Function is triggered by a web request,
which you can simulate with curl
.
Prerequisites
- Create a project in the Cloud Console.
- Enable billing for your project.
- Enable the Cloud Functions API.
- Enable the Firestore API (with Firestore in Native mode).
- Open Cloud Shell.
Set the project ID for the Cloud SDK:
gcloud config set project [PROJECT_ID]
This guide uses cloud-functions-firestore
as the Firestore collection.
Preparing the Cloud Function
This Cloud Function will either store a basic POST payload as a document in Firestore or retrieve a document from Firestore by ID.
You can find the source code on GitHub.
Alternatively, you can download
package.json
and
index.js
Start a new npm app
If you do not already have an npm project, go ahead and create one in a new directory:
mkdir cloud-functions-firestore
cd cloud-functions-firestore
npm init
Install @google-cloud/firestore
Add the Firestore client to your Node.js app, saving the dependency:
npm install --save --save-exact @google-cloud/firestore
Writing the Cloud Function code
You can copy and paste the simplified version of the code,
into index.js
(or
download it).
const Firestore = require('@google-cloud/firestore');
// Use your project ID here
const PROJECTID = '[YOUR_PROJECT_ID]';
const COLLECTION_NAME = 'cloud-functions-firestore';
const firestore = new Firestore({
projectId: PROJECTID,
timestampsInSnapshots: true
// NOTE: Don't hardcode your project credentials here.
// If you have to, export the following to your shell:
// GOOGLE_APPLICATION_CREDENTIALS=<path>
// keyFilename: '/cred/cloud-functions-firestore-000000000000.json',
});
/**
* Retrieve or store a method in Firestore
*
* Responds to any HTTP request.
*
* GET = retrieve
* POST = store (no update)
*
* success: returns the document content in JSON format & status=200
* else: returns an error:<string> & status=404
*
* @param {!express:Request} req HTTP request context.
* @param {!express:Response} res HTTP response context.
*/
exports.main = (req, res) => {
if (req.method === 'POST') {
// store/insert a new document
const data = (req.body) || {};
const ttl = Number.parseInt(data.ttl);
const ciphertext = (data.ciphertext || '')
.replace(/[^a-zA-Z0-9\-_!.,; ']*/g, '')
.trim();
const created = new Date().getTime();
// .add() will automatically assign an ID
return firestore.collection(COLLECTION_NAME).add({
created,
ttl,
ciphertext
}).then(doc => {
console.info('stored new doc id#', doc.id);
return res.status(200).send(doc);
}).catch(err => {
console.error(err);
return res.status(404).send({
error: 'unable to store',
err
});
});
}
// everything below this requires an ID
if (!(req.query && req.query.id)) {
return res.status(404).send({
error: 'No II'
});
}
const id = req.query.id.replace(/[^a-zA-Z0-9]/g, '').trim();
if (!(id && id.length)) {
return res.status(404).send({
error: 'Empty ID'
});
}
if (req.method === 'DELETE') {
// delete an existing document by ID
return firestore.collection(COLLECTION_NAME)
.doc(id)
.delete()
.then(() => {
return res.status(200).send({ status: 'ok' });
}).catch(err => {
console.error(err);
return res.status(404).send({
error: 'unable to delete',
err
});
});
}
// read/retrieve an existing document by ID
return firestore.collection(COLLECTION_NAME)
.doc(id)
.get()
.then(doc => {
if (!(doc && doc.exists)) {
return res.status(404).send({
error: 'Unable to find the document'
});
}
const data = doc.data();
if (!data) {
return res.status(404).send({
error: 'Found document is empty'
});
}
return res.status(200).send(data);
}).catch(err => {
console.error(err);
return res.status(404).send({
error: 'Unable to retrieve the document',
err
});
});
};
Note: Replace [YOUR_PROJECT_ID]
in the code with your project ID.
In this code, the function listens for a POST request
with the data fields of ciphertext
and ttl
.
It will store the values into a new document in Firestore
using the add()
function (which auto-assigns an ID).
The function also listens for a GET request with an id
in the query string.
It will look up that document in Firestore and, if found, return the document.
Note: You probably want more input sanitation for a production application.
Deploying the Cloud Function
After the code is deployed, Cloud Functions will automatically run that code when triggered.
You now have a package.json
file listing your dependencies
and you have an index.js
file which will respond to an HTTP trigger.
You use the gcloud
command-line tool to deploy the function, and configure it to listen to HTTP requests.
(optional) Install functions-emulator for local testing
You can run and test your function locally.
To do that, you need to install the functions framework:
npm install --save-dev @google-cloud/functions-framework
Follow the instructions
to configure the package.json
file if you are not using the downloaded version.
Start the function:
npm start
By default, the function listens on port 8080. You can test it by sending curl
requests.
You can open a new terminal by clicking the +
sign in the menu bar and running the following command
to create a new document:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"ttl":1,"ciphertext":"daa5370871aa301e5e12d4274d80691f75e295d648aa84b73e291d8c82"}' \
http://localhost:8080
The output is the following:
{"_firestore":{"projectId":"xxxxx"},"_path":{"segments":["cloud-functions-firestore","bdgb7hQKbTL6PCROwLsO"]},"_converter":{}}
You can retrieve the document:
curl http://localhost:8080?id=bdgb7hQKbTL6PCROwLsO
The output is similar to the following:
{"ttl":1,"created":1625077880857,"ciphertext":"daa5370871aa301e5e12d4274d80691f75e295d648aa84b73e291d8c82"}
Note that the function code added the field created
.
Deploy the code to Google Cloud Functions
Deploying the code is easy with the gcloud command-line interface.
gcloud functions deploy cloud-functions-firestore \
--entry-point=main \
--trigger-http \
--runtime=nodejs12 \
--allow-unauthenticated
In this case, cloud-functions-firestore
is the name of the function that you want to trigger in your code, triggered by an HTTP request. main
is the entry point of the function.
The output is similar to the following:
Deploying function (may take a while - up to 2 minutes)...⠹
For Cloud Build Logs, visit: https://console.cloud.google.com/cloud-build/builds;region=us-central1/1538f6d2-e6b6-4deb-8583-b872ae5e2de1?project=xxxxx
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
buildId: 1538f6d2-e6b6-4deb-8583-b872ae5e2de1
entryPoint: main
httpsTrigger:
securityLevel: SECURE_OPTIONAL
url: us-central1-[YOUR_PROJECT_ID].cloudfunctions.net/cloud-functions-firestore
ingressSettings: ALLOW_ALL
labels:
deployment-tool: cli-gcloud
name: projects/xxxxx/locations/us-central1/functions/cloud-functions-firestore
runtime: nodejs12
serviceAccountEmail: xxxxx@appspot.gserviceaccount.com
sourceUploadUrl: https://storage.googleapis.com/......
status: ACTIVE
timeout: 60s
updateTime: '2021-06-30T18:54:10.142Z'
versionId: '1'
Testing the deployed Cloud Function in production
You should now be able to send HTTP requests to the endpoint and test out the function in production.
Now you can test your function by sending curl
requests to the HTTPS trigger URL, which you can find
from the deployment output or from the Cloud Functions console.
You can create a new document:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"ttl":1,"ciphertext":"daa5370871aa301e5e12d4274d80691f75e295d648aa84b73e291d8c82"}' \
https://us-central1-[YOUR_PROJECT_ID].cloudfunctions.net/cloud-functions-firestore
The output is similar to the following:
{"_firestore":{"projectId":"[YOUR_PROJECT_ID]"},"_path":{"segments":["cloud-functions-firestore","61Eej99ONcqY4jVvj1Ij"]},"_converter":{}}
Retrieve the document:
curl https://us-central1-[YOUR_PROJECT_ID].cloudfunctions.net/cloud-functions-firestore?id=61Eej99ONcqY4jVvj1Ij
The output is similar to the following:
{"created":1625079401209,"ttl":1,"ciphertext":"daa5370871aa301e5e12d4274d80691f75e295d648aa84b73e291d8c82"}
Note that our function code added field created
.
Iterate
This is a very short cycle of code, deploy, and test, so you should be able to iterate rapidly.
When you deploy, you overwrite the current version, at the function's URL (blue/green deployment in the background, can take a few seconds to switch over).
If you need to access a previously deployed version, you can append /revisions/${REVISION}
with the value of the versionId
that the deploy
command
returns.
There are a lot of other settings available. Review docs and help:
gcloud functions deploy --help
What's next
As you can see, it is very easy to create and deploy small functions.
You can deploy larger applications just as easily.
Your functions can be triggered by many other events, not just web requests.
And you only pay for your functions for the seconds they are being run, and they can scale out as needed even if you get super-popular.
Read more about Cloud Functions, and make something awesome!
Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see our Site Policies. Java is a registered trademark of Oracle and/or its affiliates.