Google Firestore REST API integration

If you're new to Composer or REST APIs, check out the AppGyver Academy Primer: Integrations and REST

This article was written by a member of our community, Eduardo Jaramillo. In this article you will learn how to integrate and use Firestore REST API in your AppGyver app. Firestore is a great solution to store data that is non-relational, i.e. documents that do not reference and depend on other data in the database.

What is Firestore?

As stated by Google:

“Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud Platform. It keeps your data in sync across client apps through realtime listeners and offers offline support for mobile and web so you can build responsive apps that work regardless of network latency or Internet connectivity. Cloud Firestore also offers seamless integration with other Firebase and Google Cloud Platform products, including Cloud Functions”

Besides the aforementioned and it being easy to setup, it has a free usage policy for low traffic use (as of July 1, 2020, it’s free up to 50000 reads/day, 20000 writes/day and 20000 deletes/day) that allows its free use for many applications and, if needed, growth is not a problem.

The following web resources are of interest to users of Firestore with AppGyver:

Since this tutorial is about using Firestore as a data resource for AppGyver REST API direct integration, not Firestore itself, it will assume a basic knowledge of the Firestore setup.

Basics

Firestore REST API methods

Firestore uses the following methods, not all of which are implemented in AppGyver at the present time:

Firestore Method

HTTP Method

Firestore Use

AppGyver REST API direct integration implemented

batchGet

POST

Gets multiple documents

?(1)

beginTransaction

POST

Starts a new transaction

No

commit

POST

Commits a transaction, while optionally updating documents

No

createDocument

POST

Creates a new document

Yes

delete

DELETE

Deletes a document

Yes

get

GET

Gets a single document

Yes

list

GET

Lists documents

Yes

listCollectionIds

POST

Lists all the collection IDs underneath a document

?(2)

patch

PATCH

Updates or inserts a document

No

rollback

POST

Rolls back a transaction

?(2)

runQuery

POST

Runs a query

Yes(3)

(1) This was skipped since the GET COLLECTION method of AppGyver does it nicely.

(2) It might be possible, but not tried until now.

(3) It is not a direct implementation, but a use of some characteristics of AppGyver data resources.

While all Firestore methods are usable through an HTTP direct call, only those using the REST API direct integration will be presented. Additionally a workaround for querying collections based on field contents in the client side will also be shown.

AppGyver REST API direct integration methods

The following actions can be executed using the available methods in the AppGyver REST API direct integration:

AppGyver Method

Use with Firestore

GET COLLECTION (GET)

Get all documents in a collection (ordered by field if necessary)

GET RECORD (GET)

Get one document in a collection if the ID is known

CREATE RECORD (POST)

Create a new document (with a custom ID if necessary)

DELETE RECORD (DELETE)

Delete one document in a collection if the ID is known

CREATE RECORD (POST)(4)

Query database

(4) The use of CREATE RECORD (POST) to query the database is not a direct implementation of the method but a work-around that works nicely.

FIRESTORE data types

The following are the data types allowed by Firestore as they should be referenced in the schemas (taken from Firestore REST API)

Type

Notes

nullValue

nullA null value.

booleanValue

booleanA boolean (true/false) value.

integerValue

string (int64 format)An integer value.

doubleValue

numberA double value.

timestampValue

string (Timestamp format)A timestamp value. Precise only to microseconds. When stored, any additional precision is rounded down. A timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds. Example: "2014-10-02T15:01:23.045123456Z".

stringValue

stringA string value. The string, represented as UTF-8, must not exceed 1 MiB - 89 bytes. Only the first 1,500 bytes of the UTF-8 representation are considered by queries.

bytesValue

string (bytes format)A bytes value. Must not exceed 1 MiB - 89 bytes. Only the first 1,500 bytes are considered by queries.A base64-encoded string.

referenceValue

stringA reference to a document. For example: projects/{project_id}/databases/{database_id}/documents/{document_path}.

geoPointValue

object (LatLng)A geo point value representing a point on the surface of Earth.

arrayValue

object (ArrayValue)An array value. Cannot directly contain another array value, though can contain a map which contains another array.

mapValue

object (MapValue)A map value. Needed to nest fields.

Tips

  • Remember that Firestore, AppGyver and JavaScript are case sensitive (orderby is not the same as orderBy)

  • Since Firestore has a rather unique structure it is very important to pay attention to have an exact match on the schemas of the document and those in the AppGyver REST API:

    • Every document in Firestore has always the following keys: name, fields, createTime, updateTime. Never omit them in your schemas, except as noted in the CREATE RECORD (POST) method.

    • The fields key is an object with properties that have your actual data in objects with the name of your fields with a pair key:value, where key will denote the data type.

    • If your fields are composite, you will have also mapValues.

    • Example of a simple document schema in AppGyver REST API:

Example schema
  • Although you can have schemas to match any data structure you create on your Firestore database, it will be a lot easier if you keep the structure as simple as possible. This means to try to avoid composite fields as much as possible.

  • The document id is part of the name of each document. It is the string after the last slash as shown:

To get the id assigned by Firestore:

where pageVars.id is the name of the document, and the value "20" is the length of the string that Firestore assigns to document id's.

  • Since free use of Firestore caps at 50000 reads/day, it is advisable to delete the recursive read in the data variable set up, or the quota will be reached in a few hours:

Caveats and work-arounds

As shown in the Firestore REST API Methods section:

  • AppGyver doesn't have a PATCH method so the only way to change the content of a field is to GET the full document, DELETE the original document, change the data of the specific field and CREATE a new document. Care should be taken if the documentId can not be changed, in which case the new document should be created with the old id (Creating document with custom id).

  • Since the POST method is set in AppGyver to create a new record but, as shown in the Query document by field section, can be set for other uses with some tinkering, maybe all Firestore uses for the method can be set. Be aware of the limitation for the structured query response as noted in the server side query.

Data Resource Setup

Base

All AppGyver methods use a common base for each resource. In order to have flexibility and following the rules of Firestore, the following applies:

The remainder of the URL will be set up for each method and will have the following mask:

​ /projects/{your database}/databases/(default)/documents/./{additional_info}

The text in italics in the above mask can be explicitly typed or be a variable (in which case you type the variable name inside the curly brackets and pass its value as a URL placeholder).

Get Collection (GET)

The following image shows the config setup:

Once you setup the Relative path and the Response key path, run a test to check if you get the data, and if so, set schema from response.

The response will be an array with all documents in your database, ordered by default (document id). If you want the response ordered by the contents of a specific field, add:

?orderBy=your_field_name

to the Relative path as shown in the following image for the collection "Permisos" to be ordered by the values of the field "nombPermiso".

By default, the order will be lexical, ascending, but can be set to descending order by adding "desc" to the Relative path after the field name, separated by a space:

?orderBy=your_field_namedesc

Get all documents in collection "Permisos" ordered by the values of the field "nombPermiso" (ascending)

Get Record (GET) and Delete Record(DELETE)

Both methods use the same configuration strings. The following image shows the config setup in tab GET RECORD (GET):

Use the same strings and URL placeholder setup in the DELETE RECORD (DELETE) tab.

Create Record (POST)

The following image shows the config setup:

The setup shown in the above image will create a document with a documentId set by Firestore, 20 characters long.

The Create record (POST) request schema has to be manually created by adding properties starting with fields as the example shows:

This has to match the data schema in Firestore. Note that the schema starts with the fields object and does not include the name, createTime and updateTime fields.

Creating document with custom id

If you want to set your own id, you should add the following to the Relative path after the collection name:

?documentId={id}

and set a URL placeholder as show in the following image:

Also, you have to pay attention to the URL placeholder properties as needed for your application.

Query document collection by field

Firestore uses the HTTP method POST with a very powerful structured query payload in the body of the call. However, since the POST method implemented in AppGyver is, up to now, designed to create a new document (record), if you want to process the query server side, the CREATE RECORD (POST) AppGyver method can be used, but will need some twisting.

So there are two possible alternatives to query the database: Server side and Client side.

Server side

For the use of the structured query, you have to set a new resource with the base set up with a :runQuery required by Firestore. The CREATE RECORD (POST) base configuration should be set up as shown:

Note that there is no collection id but instead ":runQuery", which needs a structured query in the body payload of the POST call. For help setting the query, it is strongly suggested that you use the Firestore API explorer (https://cloud.google.com/firestore/docs/reference/rest/v1/projects.databases.documents).

In order to set the structured query in the body payload of the method call, a Create record (POST) request schema has to be set. In the simplest form, it will be as follows:

Note that "from" and "fields" are lists of objects

which is equivalent to a body:

{ "structuredQuery": { "from": [{ "collectionId": "", }], "select": { "fields": [{ "fieldPath": "", }], }, "where": { "fieldFilter": { "field": { "fieldPath": "", }, "op": "", "value": { "stringValue": "", }, }, }, };

Since the POST method is being used for an AppGyver non-intended use you can not include more fields in the "fields" array of the "select" clause of the query, so only the contents of one field will be returned. If the "select" clause is omitted, the query will return all fields, and as noted below, their key has to be included in the response schema.

Every empty values ("") have to be set with a variable or a static string in the method call as will be shown ahead.

Also a Create record (POST) response schema that matches the Firebase response has to be set. For the example above, it will be:

In the image, the object "nombInmueble" is the name of the field that is to be retrieved with the query.

The property "fields" has to have as objects the keys of the fields you will get in the response dependent on the way the structured query was set up in the request schema (either one field or all of them).

The properties "document", "createTime", "fields", "name" and "readTime" are mandatory.

Also, you have to set a data variable to get the response of the query. For the example, it will be:

And a page variable to use the data, as show for the example:

Note that "datos" is a list of objects

As can be seen above, the variable named "datos" its a list that has an schema that exactly matches the Create record (POST) response schema that was set above.

Then, the flow logic will be as shown, with a Create Record function followed by a Set page variable to assign the response to the variable (in this example "datos") using a formula.

The flow logic will look like this:

The binding (assignment) has to be done with a formula

The Create Record function bindings will look like this:

Values have to be set with variables or static values as required. Note that the arrays (Custom list) have to be set too. See the following example which sets the collection id (first Custom list on the image above):

The operators ("op") can be any of:

Operator

Use

LESS_THAN

field value < specified value

LESS_THAN_OR_EQUAL

field value <= specified value

GREATER_THAN

field value > specified value

GREATER_THAN_OR_EQUAL

field value >= specified value

EQUAL

field value == specified value

ARRAY_CONTAINS

field array contains specified value

IN

field == at least one of the specified values in a given array.

The given array values can't be empty values.

The given array can't have more than 10 values.

ARRAY_CONTAINS_ANY

field array contains any of the specified values in a given array

The given array values can't be empty values

The given array can't have more than 10 values

The variable ("datos" in the example) is assigned the values of the POST response with a formula as shown:

Although the validation of the formula shows an error, the value returned by POST is actually an array and will be bound to the variable and usable.

The data will then be available as usual.

Client side

To have the query processed on the client side, first get the document collection, and then process the query with javascript and assign the response to an appropriately configured page variable. The logic flow is as shown in the following image:

As an example, in order to get all documents in which the contents of a field (idProy) is a specific value passed with a page variable (idProyecto), the javascript needed is simple:

The data variable of input1 (listaProyectoTramite in the image) is the one set by the GET COLLECTION method.

The page variable (PTF in the image) and the Output schemas should be configured as the Firestore document schema (note that the variable type is array of objects). For the example above:

Then, the data will be available using the Page variable.

Conclusion

That's it! You've learned to integrate a Firestore database with your AppGyver Composer app, and should be ready to start utilizing Firestore in your own project. If you enjoyed the tutorial, be sure to give Eduardo a shout out on the forums!