Building a Flask(Python) CRUD API with Cloud Firestore(Firebase) and Deploying on Cloud Run.

Timothy
Google Cloud - Community
5 min readAug 29, 2019

In this tutorial, you will build a CRUD (Create, Read, Update, Delete) API to manage Todo Lists using Flask (a microframework for Python), Cloud Firestore (a flexible, scalable database for mobile, web, and server development from Firebase) and deploy the API to Cloud Run (a serverless environment to run containers on Google Cloud Platform).

Cloud Firestore stores data as collections of documents, it also features richer, faster queries and scales further than the Realtime Database.
You would be able to manage Todo List documents of varying fields via the API.

Requirements

Before you begin

Create a new Firebase project, or use an existing one.

  • Click on Database and Create database in the Cloud Firestore section.
  • Set your Security Rules and Location
  • You should have an initial screen similar to:

Download your Firebase Service Account Key

  • Click on the Settings Icon at the top of your dashboard
  • Click on the Service Account Tab
  • Select Python option for Admin SDK configuration snippet and Click on Generate new private key and save it as key.json

Create a new Google Cloud Platform (GCP) project, or use an existing one (you would need this to deploy to Cloud Run)

  • Install Cloud SDK or use Cloud Shell available on the Googl
  • (Optional) To set up Continuous Deployment check out this
  • Ensure you can run gcloud -h on your shell.

Source Code

Time to write some codes.
# app.py# Required Imports
import os
from flask import Flask, request, jsonify
from firebase_admin import credentials, firestore, initialize_app
# Initialize Flask App
app = Flask(__name__)
# Initialize Firestore DB
cred = credentials.Certificate('key.json')
default_app = initialize_app(cred)
db = firestore.client()
todo_ref = db.collection('todos')
@app.route('/add', methods=['POST'])
def create():
"""
create() : Add document to Firestore collection with request body
Ensure you pass a custom ID as part of json body in post request
e.g. json={'id': '1', 'title': 'Write a blog post'}
"""
try:
id = request.json['id']
todo_ref.document(id).set(request.json)
return jsonify({"success": True}), 200
except Exception as e:
return f"An Error Occured: {e}"
@app.route('/list', methods=['GET'])
def read():
"""
read() : Fetches documents from Firestore collection as JSON
todo : Return document that matches query ID
all_todos : Return all documents
"""
try:
# Check if ID was passed to URL query
todo_id = request.args.get('id')
if todo_id:
todo = todo_ref.document(todo_id).get()
return jsonify(todo.to_dict()), 200
else:
all_todos = [doc.to_dict() for doc in todo_ref.stream()]
return jsonify(all_todos), 200
except Exception as e:
return f"An Error Occured: {e}"
@app.route('/update', methods=['POST', 'PUT'])
def update():
"""
update() : Update document in Firestore collection with request body
Ensure you pass a custom ID as part of json body in post request
e.g. json={'id': '1', 'title': 'Write a blog post today'}
"""
try:
id = request.json['id']
todo_ref.document(id).update(request.json)
return jsonify({"success": True}), 200
except Exception as e:
return f"An Error Occured: {e}"
@app.route('/delete', methods=['GET', 'DELETE'])
def delete():
"""
delete() : Delete a document from Firestore collection
"""
try:
# Check for ID in URL query
todo_id = request.args.get('id')
todo_ref.document(todo_id).delete()
return jsonify({"success": True}), 200
except Exception as e:
return f"An Error Occured: {e}"
port = int(os.environ.get('PORT', 8080))
if __name__ == '__main__':
app.run(threaded=True, host='0.0.0.0', port=port)

There are individual methods and routes for each action the API would perform, you can improve upon the code snippet and add more functions to meet your needs. For each CRUD (Create, Read, Update, Delete) action, we define the route and its corresponding HTTP Method. We also try to perform that action and return a response with the 200 status code, if there’s a problem somewhere, we return the exception’s error.

Deploy to Cloud Run

We would have to write a Dockerfile for our API so as to be able to build it as a container and have it run on Cloud Run.

# Dockerfile
FROM python:3.7-stretch
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
ENTRYPOINT ["python"]
CMD ["app.py"]

Ensure that you have a requirements.txt file with the following contents.

# requirements.txt
flask
firebase_admin

Finally, our cloudbuild.yaml file which you will use to trigger builds needs to be created as follows.

# cloudbuild.yaml
steps:
# build & push the container image
- name: "gcr.io/kaniko-project/executor:latest"
args: ["--cache=true", "--cache-ttl=48h", "--destination=gcr.io/$PROJECT_ID/todo:latest"]
# Deploy container image to Cloud Run
- name: "gcr.io/cloud-builders/gcloud"
args: ['beta', 'run', 'deploy', 'todo', '--image', 'gcr.io/$PROJECT_ID/todo:latest', '--region', 'us-central1', '--allow-unauthenticated', '--platform', 'managed']

In addition, you can also import your key.json Firebase service account file into the same directory as it is not advisable to have it pushed to your source repository. A fast approach to this is to add an additional Cloud Build step that downloads the service account from a private location such as a Google Cloud Storage Bucket.

Execute Build & Deploy Steps on Cloud Shell

Run the following command to build your Docker container and push to Container Registry as specified in the cloudbuild.yaml file. This also performs an extra step of deploying to Cloud Run.

gcloud builds submit --config cloudbuild.yaml .

Deploy using Cloud Run Button

Recently, Google Cloud announced Cloud Run Button, an image and link you can add to the README of your source code repositories to allow others to deploy your application to Google Cloud Platform using Cloud Run. The steps to add the Cloud Run Button to your repository are:

  • Copy & paste this markdown into your README.md file
[![Run on Google Cloud](https://storage.googleapis.com/cloudrun/button.svg)](https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=YOUR_HTTP_GIT_URL)

Cleaning up

To prevent unnecessary charges, clean up the resources created for this tutorial, delete the resources or projects used (if you created a new project).

Useful Links

Thanks for reading through! Let me know if I missed any step, if something didn’t work out quite right for you or if this guide was helpful.

Originally posted on FullStackGCP

--

--