Building and Deploying a Modern API with FastAPI, Firestore, and Google Cloud Run

3 minute read

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.