FastAPI and Firebase
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 theget()
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.