Skip to main content

FastAPI and Firebase

·3 mins

FastAPI: A Modern Python Web Framework #

FastAPI is a modern, high-performance Python web framework built on top of the Pydantic data validation library and Starlette. It offers several advantages:

  • Asynchronous programming: FastAPI leverages asynchronous programming, allowing it to handle multiple concurrent requests efficiently.
  • Data validation: Pydantic provides automatic data validation, ensuring that incoming data adheres to the expected schema.
  • OpenAPI/Swagger support: FastAPI automatically generates OpenAPI/Swagger documentation, making it easy for developers and consumers to understand the API.
  • Dependency injection: This feature simplifies code organization and testing by automatically managing dependencies.

Cloud Firestore: A NoSQL Database #

Cloud Firestore is a scalable, cloud-based NoSQL database that is ideal for real-time applications. It offers:

  • Real-time updates: Firestore automatically updates clients with changes to data, enabling real-time synchronization.
  • Scalability: It can handle millions of reads and writes per second, making it suitable for high-traffic applications.
  • Offline capabilities: Firestore provides offline support, allowing users to continue using the application even when they are not connected to the internet.
  • Query flexibility: Firestore supports flexible querying capabilities, allowing you to retrieve data based on various criteria.

Backend #

Bringing these two together in a backend leads to a pretty efficient server. This is the base for a few projects I’m working on.

Demo #

Let’s bring both of these together in a demo project using uv. It’s a great tool but I think a little unfortunate naming due to libuv which is something entirely different. I’ve been using their formatting tool called ruff for a while now, and it has been excellent.

$ uv init
Initialized project `fastapi-firestore`

# simply two depenencies
$ uv add "fastapi[standard]" google-cloud-firestore

Using Python 3.12.5 interpreter at: /opt/homebrew/opt/[email protected]/bin/python3.12
Creating virtualenv at: .venv
Resolved 51 packages in 1.07s
   Built fastapi-firestore @ file:///<snip>/experiments/fastapi-firestore
Prepared 50 packages in 891ms
Installed 50 packages in 45ms
 + annotated-types==0.7.0
 + anyio==4.4.0
 + cachetools==5.5.0
 + certifi==2024.7.4
 + charset-normalizer==3.3.2
 + click==8.1.7
 + dnspython==2.6.1
 + email-validator==2.2.0
 + fastapi==0.112.2
 + fastapi-cli==0.0.5
 + google-api-core==2.19.1
 + google-auth==2.34.0
 + google-cloud-core==2.4.1
 + google-cloud-firestore==2.17.2
 + googleapis-common-protos==1.63.2
 + grpcio==1.66.0
<snip>

Now let’s write a minimal example in main.py:

from fastapi import FastAPI
from google.cloud import firestore

app = FastAPI()

db = firestore.AsyncClient()


@app.get("/buddies/{buddy_id}")
async def get_buddy(buddy_id: str):
    budy_ref = db.collection("buddies").document(buddy_id)
    buddy_doc = await budy_ref.get()
    if buddy_doc.exists:
        return buddy_doc.to_dict()
    else:
        return {"message": "Buddy not found"}
  • Asynchronous Client: The AsyncClient is used to create an asynchronous Firestore client.
  • Asynchronous Endpoint: The get_buddy function is now defined as asynchronous using the async keyword.
  • Awaiting Operations: The await budy_ref.get() line waits for the get() operation to complete before proceeding. This allows other tasks to be handled while the operation is in progress.

Finally start up FastAPI with:

$ uv run fastapi dev main.py

INFO:     Will watch for changes in these directories: ['/<snip>/fastapi-firestore']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [39699] using WatchFiles
INFO:     Started server process [39772]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Currently, my local setup is using default credentials. You can set this up with the gcloud cli:

$ gcloud auth application-default login
$ gcloud config set project myproj

Now we test this with demo with just curl:

$ curl -s http://localhost:8000/buddies/26wFRJ8ptY7HyQaF1h8N | jq .

{
  "account_id": "yXC3jYy2rQrlvjtNjNu1",
  "emoji": true,
  "name": "Benjamin"
}

Obviously this is just the most basic starting point, more to come.