Communicating with your database from a frontend using FastAPI Backend

Backend work is cool, but if you want to be able to sell a real product you’re gonna need to know how to display data on a frontend. As always, if you wanna skip the bullshit, here’s the github repo with the source code:

Communicating with your database from a frontend using FastAPI Backend

Backend work is cool, but if you want to be able to sell a real product you’re gonna need to know how to display data on a frontend.

As always, if you wanna skip the bullshit, here’s the github repo with the source code:

Now, here are the things I needed to set this up:
TailwindUI for components

FastAPI Backend

Supabase for the database

Highly recommend these for speed.

Anyways, In order to cvommunicate with your frontend, you need endpoints set up lik so:

@router.get('/retrieve_all')
def pull_data():
    data = retrieveAll()
    return data

@router.get('/retrieve/{id}')
def pull_data(id: str):
    data =  retrieveOne(id)
    return data

@router.delete('/delete/{id}')
def delete_data(id: str):
    data = deleteItem(id)
    return data

@router.post('/create')
def create_data(personData: person):
    data =  createItem(personData.dict())
    return data

These ideally would be set up in your main.py or in an endpoints.py file.

For readability, I put functions in a logic.py file like this:

from supabase import * import os from dotenv import load_dotenv load_dotenv() db_url: str = os.getenv('url') db_key: str = os.getenv('key') supabase: Client = create_client(db_url, db_key) def retrieveAll(): people = supabase.table('people').select('*').execute() if people.data: return people.data def retrieveOne(id): person = supabase.table('people').select('*').eq('id', id).execute() if person.data: return person.data def deleteItem(id): delete_response = supabase.table('people').delete().eq('id', id).execute() return delete_response def createItem(data): person = supabase.table('people').insert(data).execute() return person.data

This makes it very easy for me to edit code without it becoming a hot mess.

With supabase, you want to insert to create, and the (‘table’) is how you select what table to do work on. doing .select(‘*’) is how you select every item in that table, and adding .eq(‘column’, variable) is how you can filter what you’re pulling.

Then obviously we need to add JS logic for the frontend to be able to interact with the backend.

in our logic.js file:

document.addEventListener('DOMContentLoaded', () => {
    fetchData()

    document.getElementById('createPerson').addEventListener('click', function() {
        createData('firstName', 'lastName', 'phoneNumber', 'email')
    })

})

This will set up what we need to be able to do shit.

document.addEventListener('DOMContentLoaded', () => {}

is how we activate functions on page load. fetchData() is how we retrieve the data and that looks like this:

async function fetchData() {
    const url = 'http://127.0.0.1:8000/api/retrieve_all';

    try {
        const response = await fetch(url, {
            method: 'GET', 
            headers: {
                'Accept': 'application/json' 
            }
        });

        if (!response.ok) {
            
            throw new Error(`Error! status: ${response.status}`);
        }

        const data = await response.json(); 
        console.log(data); 
        const tableBody = document.getElementById('peopleTable');

        data.forEach(person => {
            const row = document.createElement('tr');

            const firstNameCell = document.createElement('td');
            firstNameCell.textContent = `${person.first_name} ${person.last_name}`;
            firstNameCell.classList.add('whitespace-nowrap', 'py-4', 'pl-4', 'pr-3', 'text-sm', 'font-medium', 'text-gray-900', 'sm:pl-0');

            

            const emailCell = document.createElement('td');
            emailCell.textContent = person.email;
            emailCell.classList.add('whitespace-nowrap', 'px-3', 'py-4', 'text-sm', 'text-gray-500');

            const phoneCell = document.createElement('td');
            phoneCell.textContent = person.phone_number;
            phoneCell.classList.add('whitespace-nowrap', 'px-3', 'py-4', 'text-sm', 'text-gray-500');

            const deleteCell = document.createElement('td');
            deleteCell.classList.add('relative', 'whitespace-nowrap', 'py-4', 'pl-3', 'pr-4', 'text-right', 'text-sm', 'font-medium', 'sm:pr-0');

            const deleteSpan = document.createElement('span');
            deleteSpan.textContent = 'Delete'
            deleteSpan.classList.add ('text-red-600', 'hover:text-red-900', 'cursor-pointer')

            deleteCell.appendChild(deleteSpan)

            row.appendChild(firstNameCell);
            row.appendChild(emailCell);
            row.appendChild(phoneCell);
            row.appendChild(deleteCell);

            deleteCell.addEventListener('click', function() {
                deleteData(person.id, row)
            })

        
            tableBody.appendChild(row);
        }); 
    } catch (error) {
        console.error('There was an error fetching the data:', error);
    }
}

Here, we hit our (locally hosted) api, and create an element for every item that is returned from the DB.

document.getElementById('createPerson').addEventListener('click', function() {
        createData('firstName', 'lastName', 'phoneNumber', 'email')
    })

This code right here will add an event listener to my createPerson button in the html. What this does is when its clicked, this function will be triggered:

async function createData(firstName, lastName, phoneNumber, email) { const url = 'http://127.0.0.1:8000/api/create'; const input = { first_name: firstName, last_name: lastName, phone_number: phoneNumber, email: email }; try { const response = await fetch(url, { method: 'POST', // Method itself headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(input) }); if (!response.ok) { throw new Error(`Error! status: ${response.status}`); } const data = await response.json(); console.log(data); const tableBody = document.getElementById('peopleTable'); data.forEach(person => { const row = document.createElement('tr'); const firstNameCell = document.createElement('td'); firstNameCell.textContent = `${person.first_name} ${person.last_name}`; firstNameCell.classList.add('whitespace-nowrap', 'py-4', 'pl-4', 'pr-3', 'text-sm', 'font-medium', 'text-gray-900', 'sm:pl-0'); const emailCell = document.createElement('td'); emailCell.textContent = person.email; emailCell.classList.add('whitespace-nowrap', 'px-3', 'py-4', 'text-sm', 'text-gray-500'); const phoneCell = document.createElement('td'); phoneCell.textContent = person.phone_number; phoneCell.classList.add('whitespace-nowrap', 'px-3', 'py-4', 'text-sm', 'text-gray-500'); const deleteCell = document.createElement('td'); deleteCell.classList.add('relative', 'whitespace-nowrap', 'py-4', 'pl-3', 'pr-4', 'text-right', 'text-sm', 'font-medium', 'sm:pr-0'); const deleteSpan = document.createElement('span'); deleteSpan.textContent = 'Delete' deleteSpan.classList.add ('text-red-600', 'hover:text-red-900', 'cursor-pointer') deleteCell.appendChild(deleteSpan) row.appendChild(firstNameCell); row.appendChild(emailCell); row.appendChild(phoneCell); row.appendChild(deleteCell); deleteCell.addEventListener('click', function() { deleteData(person.id, row) }) tableBody.appendChild(row); }); return data; } catch (error) { console.error('There was an error creating the data:', error); } }

createData will pass the following: firstName, lastName, phoneNumber, email

to the api. you’d want to reconfigure this to be dynamic, and you could add a popup with input fields to accomplish this.

But this code will pass that data to the frontend, and if the code works, it will create an item for that person inside of the table.

But yea this is pretty much how you can interact with a DB from the frontend. You just need an API to connect with the DB, you need a frontend with data tables/buttons, and you need some JS logic that will interact with your endpoints with FETCH code.