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@v3

    - 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.

Let me know if you need any clarification or have questions about specific parts of the implementation!