Building and Deploying a Modern API with FastAPI, Firestore, and Google Cloud Run
In this guide, we’ll build a modern REST API using FastAPI, store data in Firestore, and deploy it to Google Cloud Run using GitHub Actions for continuous deployment. This stack gives us a scalable, serverless solution with minimal operational overhead.
Prerequisites
- Python 3.8+
- Google Cloud Platform account
- GitHub account
- Basic knowledge of Python and REST APIs
1. Project Setup
First, let’s create our project structure:
mkdir fastapi-firestore-api
cd fastapi-firestore-api
python -m venv venv
source venv/bin/activate # On Windows: .\venv\Scripts\activate
Create a requirements.txt
:
fastapi==0.104.1
uvicorn==0.24.0
google-cloud-firestore==2.13.1
pydantic==2.4.2
python-dotenv==1.0.0
Install dependencies:
pip install -r requirements.txt
2. Building the API
Let’s create a simple task management API. First, create main.py
:
from fastapi import FastAPI, HTTPException
from google.cloud import firestore
from pydantic import BaseModel
from typing import Optional
import os
app = FastAPI(title="Task Manager API")
# Initialize Firestore client
db = firestore.Client()
class Task(BaseModel):
title: str
description: Optional[str] = None
completed: bool = False
@app.get("/")
async def root():
return {"message": "Welcome to Task Manager API"}
@app.post("/tasks/")
async def create_task(task: Task):
task_dict = task.dict()
doc_ref = db.collection('tasks').document()
doc_ref.set(task_dict)
return {"id": doc_ref.id, **task_dict}
@app.get("/tasks/")
async def list_tasks():
tasks = []
for doc in db.collection('tasks').stream():
tasks.append({"id": doc.id, **doc.to_dict()})
return tasks
@app.get("/tasks/{task_id}")
async def get_task(task_id: str):
doc = db.collection('tasks').document(task_id).get()
if not doc.exists:
raise HTTPException(status_code=404, detail="Task not found")
return {"id": doc.id, **doc.to_dict()}
@app.put("/tasks/{task_id}")
async def update_task(task_id: str, task: Task):
doc_ref = db.collection('tasks').document(task_id)
if not doc_ref.get().exists:
raise HTTPException(status_code=404, detail="Task not found")
doc_ref.update(task.dict())
return {"id": task_id, **task.dict()}
@app.delete("/tasks/{task_id}")
async def delete_task(task_id: str):
doc_ref = db.collection('tasks').document(task_id)
if not doc_ref.get().exists:
raise HTTPException(status_code=404, detail="Task not found")
doc_ref.delete()
return {"message": "Task deleted"}
3. Local Development
Create a .env
file for local development:
GOOGLE_APPLICATION_CREDENTIALS=path/to/your/service-account-key.json
Run the API locally:
uvicorn main:app --reload
Visit http://localhost:8000/docs to see the Swagger UI documentation.
4. Docker Setup
Create a Dockerfile:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
5. Google Cloud Setup
Enable required APIs:
gcloud services enable run.googleapis.com
gcloud services enable firestore.googleapis.com
gcloud services enable containerregistry.googleapis.com
Create a Firestore database in Native mode.
6. GitHub Actions Setup
Create .github/workflows/deploy.yml
:
name: Deploy to Cloud Run
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2
with:
project_id: $
service_account_key: $
- name: Configure Docker for GCP
run: gcloud auth configure-docker
- name: Build and push Docker image
run: |
docker build -t gcr.io/$/task-api:$GITHUB_SHA .
docker push gcr.io/$/task-api:$GITHUB_SHA
- name: Deploy to Cloud Run
run: |
gcloud run deploy task-api \
--image gcr.io/$/task-api:$GITHUB_SHA \
--region $ \
--platform managed \
--allow-unauthenticated \
--set-env-vars "GOOGLE_CLOUD_PROJECT=$"
7. GitHub Repository Setup
- Create a new repository on GitHub
- Add these secrets in your repository settings:
- GCP_PROJECT_ID: Your Google Cloud project ID
- GCP_SA_KEY: Your service account key JSON
- REGION: Your preferred GCP region (e.g., us-central1)
8. Testing the API
Once deployed, you can test your API using curl or any HTTP client:
# Create a task
curl -X POST "https://your-cloud-run-url/tasks/" \
-H "Content-Type: application/json" \
-d '{"title": "Learn FastAPI", "description": "Build a REST API"}'
# List tasks
curl "https://your-cloud-run-url/tasks/"
# Get a specific task
curl "https://your-cloud-run-url/tasks/{task_id}"
# Update a task
curl -X PUT "https://your-cloud-run-url/tasks/{task_id}" \
-H "Content-Type: application/json" \
-d '{"title": "Learn FastAPI", "description": "Build a REST API", "completed": true}'
# Delete a task
curl -X DELETE "https://your-cloud-run-url/tasks/{task_id}"
9. Best Practices and Considerations
- Error Handling: Add more robust error handling and validation
- Authentication: Add authentication using Firebase Auth or similar
- Rate Limiting: Implement rate limiting for production
- Logging: Add structured logging
- Monitoring: Set up Cloud Monitoring
- Testing: Add unit and integration tests
- CI/CD: Add testing steps to GitHub Actions
10. Next Steps
- Add user authentication
- Implement pagination for list endpoints
- Add filtering and sorting
- Set up monitoring and alerting
- Add automated testing
- Implement caching
Conclusion
This setup gives you a modern, scalable API that’s easy to maintain and deploy. The combination of FastAPI, Firestore, and Cloud Run provides a powerful yet simple solution for building and deploying APIs. The code is available on GitHub, and you can deploy it to your own GCP project by following the setup steps.