/logrocket-fastapi-graphql-tutorial

Educational project - learning to build a GraphQL server using FastAPI, Graphene, uvicorn

Primary LanguagePython

logrocket-fastapi-graphql-tutorial

Educational project - learning to build a GraphQL server using FastAPI, Graphene, uvicorn

Based on https://blog.logrocket.com/building-a-graphql-server-with-fastapi/

Dockerizing FastAPI for Google Cloud Run based on https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker

Google Cloud Run build & deploy based on https://cloud.google.com/run/docs/quickstarts/build-and-deploy/python

Local Dev with virtualenv

Requires Python 3.6+ (tested with Python 3.9 on MacOS)

git clone git@github.com:itamaro/logrocket-fastapi-graphql-tutorial.git
cd logrocket-fastapi-graphql-tutorial
python3 -m venv fastapi-graphql-venv
source fastapi-graphql-venv/bin/activate
pip install fastapi uvicorn graphene
cd fastapi-graphql
uvicorn main:app --reload

GraphiQL should be accessible on http://localhost:8000/

Build & Run Locally With Docker

docker build -t fastapi-graphql-app -f docker/Dockerfile .
docker run -it --rm -v $PWD/fastapi-graphql:/app -p 8000:80 fastapi-graphql-app /start-reload.sh

GraphiQL should be accessible on http://localhost:8000/

Build & Deploy On Google Cloud Run

export PROJECT_ID="$( gcloud config get-value project )"
gcloud builds submit
gcloud run deploy fastapi-graphql-demo --image=gcr.io/$PROJECT_ID/fastapi-graphql-app --platform=managed --allow-unauthenticated

GraphiQL should be accessible on https://fastapi-graphql-demo-mrbe4zqmdq-uw.a.run.app/

Testing in GraphiQL

Example getCourses query:

query GetCoursesQuery {
  getCourses(id: "3") {
    id
    title
    instructor
    publishDate
  }
}

Example createCourse mutation:

mutation CreateCourseMutation {
  createCourse(
    id: "11"
    title: "Python Lists"
    instructor: "Jane Melody"
  ) {
    course {
      id
      title
      instructor
    }
  }
}

Testing with curl

>curl 'https://fastapi-graphql-demo-mrbe4zqmdq-uw.a.run.app/' -XPOST -H "Content-Type: application/json" --data '{"query":"query GetCoursesQuery { getCourses { id title instructor publishDate } }","variables":null,"operationName":"GetCoursesQuery"}'
{"data":{"getCourses":[{"id":"1","title":"Python variables explained","instructor":"Tracy Williams","publishDate":"12th May 2020"},{"id":"2","title":"How to use functions in Python","instructor":"Jane Black","publishDate":"9th April 2018"},{"id":"3","title":"Asynchronous Python","instructor":"Matthew Rivers","publishDate":"10th July 2020"},{"id":"4","title":"Build a REST API","instructor":"Babatunde Mayowa","publishDate":"3rd March 2016"},{"id":"11","title":"Python Lists","instructor":"Jane Melody","publishDate":null}]}}

Using jq for pretty printing:

>curl 'https://fastapi-graphql-demo-mrbe4zqmdq-uw.a.run.app/' -XPOST -H "Content-Type: application/json" --data '{"query":"query GetCoursesQuery { getCourses { id title instructor publishDate } }","variables":null,"operationName":"GetCoursesQuery"}' | jq -C
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   661  100   526  100   135   5057   1298 --:--:-- --:--:-- --:--:--  6355
{
  "data": {
    "getCourses": [
      {
        "id": "1",
        "title": "Python variables explained",
        "instructor": "Tracy Williams",
        "publishDate": "12th May 2020"
      },
      {
        "id": "2",
        "title": "How to use functions in Python",
        "instructor": "Jane Black",
        "publishDate": "9th April 2018"
      },
      {
        "id": "3",
        "title": "Asynchronous Python",
        "instructor": "Matthew Rivers",
        "publishDate": "10th July 2020"
      },
      {
        "id": "4",
        "title": "Build a REST API",
        "instructor": "Babatunde Mayowa",
        "publishDate": "3rd March 2016"
      },
      {
        "id": "11",
        "title": "Python Lists",
        "instructor": "Jane Melody",
        "publishDate": null
      }
    ]
  }
}

Require Auth

export PROJECT_ID="$( gcloud config get-value project )"
gcloud builds submit
gcloud run deploy fastapi-graphql-auth-demo --image=gcr.io/$PROJECT_ID/fastapi-graphql-app --platform=managed --no-allow-unauthenticated

Now curl gets 403:

>curl -i 'https://fastapi-graphql-auth-demo-mrbe4zqmdq-uw.a.run.app/' -XPOST -H "Content-Type: application/json" --data '{"query":"query GetCoursesQuery { getCourses { id title instructor publishDate } }","variables":null,"operationName":"GetCoursesQuery"}'
HTTP/2 403
date: Sun, 25 Apr 2021 18:22:01 GMT
content-type: text/html; charset=UTF-8
server: Google Frontend
content-length: 295
...

If we add ID token as bearer authorization header then it works again:

>curl 'https://fastapi-graphql-auth-demo-mrbe4zqmdq-uw.a.run.app/' -XPOST -H "Content-Type: application/json" -H "Authorization: Bearer $(gcloud auth print-identity-token)" --data '{"query":"query GetCoursesQuery { getCourses { id title instructor publishDate } }","variables":null,"operationName":"GetCoursesQuery"}'
{"data":{"getCourses":[{"id":"1","title":"Python variables explained","instructor":"Tracy Williams","publishDate":"12th May 2020"},{"id":"2","title":"How to use functions in Python","instructor":"Jane Black","publishDate":"9th April 2018"},{"id":"3","title":"Asynchronous Python","instructor":"Matthew Rivers","publishDate":"10th July 2020"},{"id":"4","title":"Build a REST API","instructor":"Babatunde Mayowa","publishDate":"3rd March 2016"}]}}