Supa-Fast API (Supabase FastAPI) : Building the API

Supa-Fast API (Supabase FastAPI) : Building the API

In this is the second part of the series, we will be looking at building the API using FastAPI and Supabase python client.


Prerequisites

  • Have a database set up on Supabase(can match the database set up in the last tutorial)
  • Comfortable with Python (including Virtual Environments)
  • An Idea of what Environment Variables are
  • JSON format

Setting up FastAPI

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.

Installing python 3.7.5

To get started , make sure you have python > 3.7 as the latest supabase client uses that.

Download python 3.7.5 from here.

Install it with the Add to path flag checkbox enabled.

Install Python 3.7.5

To list all the installed python on your system use the below command

> py -0  

Installed Pythons found by C:\Windows\py.exe Launcher for Windows
 -3.7-32 *
 -3.6-32

Creating test FastAPI

Navigate to the folder where you want to create your project and run the following commands

mkdir supafast-api

cd .\supafast-api\

Create and activate a Virtual Environment with the latest installed python using the given commands

virtualenv venv-supaFastApi --python=python3.7

.\venv-supaFastApi\Scripts\activate

Create an api folder in which the main login for the API will reside.. Name the file as main.py

Note: Do not name the file other than main.py as it is required for deployment.

mkdir api

cd .\api

cd. >main.py
pip install fastapi

pip install "uvicorn[standard]"

In the main.py file, add the code below and run the API using uvicorn to test if everything works properly.

#main.py

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def root():
    return {"message": "Hello World Test"}
uvicorn main:app --reload

http://127.0.0.1:8000/

A response similar to this should appear on the screen at http://127.0.0.1:8000/

// 20211127160738
// http://127.0.0.1:8000/

{
  "message": "Hello World Test"
}

TIP : use JSON Viewer Chrome Extension to view prettified JSON in the browser.

Now that the test FastAPI is working, let's get started and add the Supabase Python client to get data in the API.


Using Data from Supabase

pip install supabase

We need to use the SUPABASE_URL as well as SUPABASE_KEY which will be present in the settings of the Supabase project that we created in the first part of this series.

Supabase Keys

#main.py

from fastapi import FastAPI
from supabase import create_client, Client
app = FastAPI()


url = YOUR_SUPABASE_URL
key = YOUR_SUPABASE_KEY

supabase: Client = create_client(url, key)

Getting Data from Database

This was the structure of the database in the Supabase and for the API ,we are going to add two endpoints in FastAPI...

  • To list down all the themes - /themes
  • To list down the monsters of a particular theme - /monsters/?theme={theme_name}
#main.py

from fastapi import FastAPI
from supabase import create_client, Client
app = FastAPI()

url = YOUR_SUPABASE_URL
key = YOUR_SUPABASE_KEY

@app.get("/themes")
def themes():
    themes = supabase.table('themes').select('*').execute()
    return themes

@app.get("/monsters/")
def monsters(theme : str = "demo-theme-1"):
    monsters = supabase.table('monsters').select('*').eq('monsterTheme',theme).execute()
    return monsters

For the URL http://127.0.0.1:8000/themes , the result would be

// 20211127163945
// http://127.0.0.1:8000/themes

{
  "data": [
    {
      "id": 1,
      "created_at": "2021-11-26T15:28:21+00:00",
      "monsterTheme": "demo-theme-1"
    },
    {
      "id": 2,
      "created_at": "2021-11-26T15:29:45+00:00",
      "monsterTheme": "demo-theme-2"
    },
    {
      "id": 3,
      "created_at": "2021-11-26T15:30:04+00:00",
      "monsterTheme": "demo-theme-3"
    },
    {
      "id": 4,
      "created_at": "2021-11-26T15:30:12+00:00",
      "monsterTheme": "demo-theme-4"
    },
    {
      "id": 6,
      "created_at": "2021-11-26T15:30:21+00:00",
      "monsterTheme": "demo-theme-5"
    }
  ],
  "status_code": 200
}

For the "monsters" endpoint, we need a query parameter, but in case we don't send a query parameter, we will have a default value set so that a response is available at the endpoint.

In the main.py above , we have a default set to demo-theme-1.. So in case the API call is to the endpoint http://127.0.0.1:8000/monsters/ we will get the result as follows

// 20211129131036
// http://127.0.0.1:8000/monsters/

{
  "data": [
    {
      "id": 1,
      "created_at": "2021-11-27T02:47:11+00:00",
      "monsterName": "demo-monster-1",
      "monsterQuote": "demo-quote-1",
      "monsterTheme": "demo-theme-1",
      "monsterLevel": 5
    },
    {
      "id": 2,
      "created_at": "2021-11-27T02:49:03+00:00",
      "monsterName": "demo-monster-2",
      "monsterQuote": "demo-quote-2",
      "monsterTheme": "demo-theme-1",
      "monsterLevel": 10
    },
    {
      "id": 4,
      "created_at": "2021-11-27T02:50:21+00:00",
      "monsterName": "demo-monster-3",
      "monsterQuote": "demo-quote-3",
      "monsterTheme": "demo-theme-1",
      "monsterLevel": 20
    }
  ],
  "status_code": 200
}

Getting Images from Storage

The Supabase Python Client also provides various methods to interact with the Supabase Storage and we are going to get the public URL for the stored images.

For that we need to have the exact location of the file in the Bucket as well as the correct name for the images.

image.png

From the last blog of the series, we had the above structure of files in the Bucket supafast-api

#main.py

from fastapi import FastAPI
from supabase import create_client, Client
app = FastAPI()

url = YOUR_SUPABASE_URL
key = YOUR_SUPABASE_KEY

supabase: Client = create_client(url, key)

@app.get("/themes")
def themes():
    themes = supabase.table('themes').select('*').execute()

    for theme in themes['data']:
#theme['monsterThemeBg'] = supabase.storage().StorageFileAPI(BUCKET_NAME).get_public_url(FILE_LOCATION)
        theme['monsterThemeBg'] = supabase.storage().StorageFileAPI('supafast-api').get_public_url('themes/'+ theme['monsterTheme'].replace(' ','-') + '.png')

    return themes

Here, the FILE_LOCATION is easier to construct because the way these files are stored. The response we receive after adding the monsterThemeBg is like

// 20211129184109
// http://127.0.0.1:8000/themes

{
  "data": [
    {
      "id": 1,
      "created_at": "2021-11-26T15:28:21+00:00",
      "monsterTheme": "demo-theme-1",
      "monsterThemeBg": "https://jtpcokcjjqbizziufohl.supabase.co/storage/v1/object/public/supafast-api/themes/demo-theme-1.png"
    },
    {
      "id": 2,
      "created_at": "2021-11-26T15:29:45+00:00",
      "monsterTheme": "demo-theme-2",
      "monsterThemeBg": "https://jtpcokcjjqbizziufohl.supabase.co/storage/v1/object/public/supafast-api/themes/demo-theme-2.png"
    },

    ...


  ],
  "status_code": 200
}

The final main.py file will look something like this..

#main.py

from fastapi import FastAPI
from supabase import create_client, Client
app = FastAPI()

url = YOUR_SUPABASE_URL
key = YOUR_SUPABASE_KEY
supabase: Client = create_client(url, key)


@app.get("/themes")
def themes():
    themes = supabase.table('themes').select('*').execute()
    print(themes['data'])
    for theme in themes['data']:
        theme['monsterThemeBg'] = supabase.storage().StorageFileAPI('supafast-api').get_public_url('themes/'+ theme['monsterTheme'] + '.png')

    return themes

@app.get("/monsters/")
def monsters(theme : str = "demo-theme-1"):
    monsters = supabase.table('monsters').select('*').eq('monsterTheme',theme).execute()
    for monster in monsters['data']:
        monster['monsterBg'] = supabase.storage().StorageFileAPI('supafast-api').get_public_url(monster['monsterTheme']+'/'+ monster['monsterName'].replace(' ','-') + '.png')

    return monsters

Setting Up Environment Variables

As you can see in the main.py , SENSITIVE properties like the url and key are directly defined in the file. Such sensitive data should never be in a file and to overcome it, we use environment variables.

I found this really good article on dev.to to use environment variables from a .env file.

To do that,

//Stop the uvicorn server

cd. >.env

Now in the newly created .env file, add your variables in the format

#.env

SUPABASE_SUPAFAST_URL=your-supabase-url
SUPABASE_SUPAFAST_KEY=you-supabase-key

#make sure you don't have spaces before/after the equal sign

In main.py file,

import os
from fastapi import FastAPI
from supabase import create_client, Client
from dotenv import load_dotenv

load_dotenv()
app = FastAPI()

url = os.getenv('SUPABASE_SUPAFAST_URL')
key = os.getenv('SUPABASE_SUPAFAST_KEY')
supabase: Client = create_client(url, key)

'''
Rest of the code for the routes of the API
'''

Here, we additionally import the os and the load_dotenv packages, run the load_dotenv() function which loads the KEY=VALUE pairs into the environment so that os.getenv('KEY') can retrieve the value from the environment.

We didn't need to install a package for load_dotenv because the python-dotenv gets automatically installed when running pip install "uvicorn[standard] "


In case you use git(or some other VCS), make sure to put the .env in the .gitignore file, so that it is not visible to the general public.


Next Steps

Now that we have our API fully working on our local environment, in the next tutorial will deploy it on DETA and make it available publicly.

In case you have any doubts or suggestions regarding the article, feel free to contact me on my email or on Twitter at @aUnicornDev.

The API built in this series is used in the tabsMonster project. Everyone is more than welcome to checkout and contribute in this Open Source Project.

Below are the links for docs and products if you are more interesed.


Thank you for reading this far

image.png

Did you find this article valuable?

Support Yashasvi Singh by becoming a sponsor. Any amount is appreciated!