From Backend to Frontend: Connecting FastAPI and Streamlit

By on 29 April 2025

In my previous Pybites article, I showed how I built and deployed a book tracking API using FastAPI, Docker, and Fly.io. That project taught me a lot about backend development, containers, and deploying modern APIs.

But a backend alone isn’t enough—users need an interface. And FastAPI’s Swagger UI, while useful for testing, just isn’t user-friendly enough for everyday use.

So, I decided to build a frontend. My goal? A fast, Python-based UI with minimal hassle. That’s when I turned to Streamlit.

In this article, I’ll explain why I used Streamlit and how I was able to connect my deployed backend to this frontend.

This article will be perfect for those wanting to build a full stack Python application, or even for developers wanting to connect their existing backends to a simple interface.


Why Streamlit?

There are plenty of frontend frameworks out there, but here’s why Streamlit was perfect for my project:

  • Speed: I could build and iterate on the interface in minutes, not hours.
  • Python-first: Since everything is in Python, I didn’t have to context-switch to HTML/CSS/JS.
  • Data-focused: Streamlit is made for data apps, so it has built-in components for tables, charts, and layout.
  • Deployable: I could host the frontend on Streamlit Cloud with minimal configuration and connect it to my deployed backend.

Streamlit gave me the option to build a nice looking frontend, without the hassle of going through HTML, CSS, JS, or another tool. I could focus on Python 🐍, which is more of my skill set.

Making API Calls from Streamlit

With the backend already being deployed on Fly.io, I just needed to write some client-side logic in the Streamlit app to call it.

I used the requests library to send GET and POST requests. Below is a simplified example of how to connect the Streamlit saved books input to my FastAPI /user-books/ endpoint:

import requests
import streamlit as st

API_URL = "https://your-fastapi-backend.com"
SAVED_BOOKS_URL = f"{API_URL}/user-books/"

st.title("📚 My Saved Books")

# Assume user is already authenticated and token stored in session
access_token = st.session_state.get("access_token")
user_id = st.session_state.get("user_id")

if not access_token or not user_id:
    st.error("Please log in to view saved books.")
    st.stop()

headers = {"Authorization": f"Bearer {access_token}"}
response = requests.get(SAVED_BOOKS_URL, params={"user_id": user_id}, headers=headers)

if response.status_code != 200:
    st.error("Failed to load saved books.")
    st.stop()

saved_books = response.json()

for book in saved_books:
    st.subheader(book["title"])
    st.write(f"**Author(s):** {book['authors']}")
    st.write(f"**Published:** {book.get('published_date', 'N/A')}")
    st.markdown("---")

This basic example shows how little code is needed to connect and display the data. With that knowledge, it was easy to connect the rest of the FastAPI endpoints to have the app fully functioning.


Handling Environment Variables and CORS

Connecting the frontend and backend isn’t just about sending these API calls. It also requires some behind the scenes work with environment variables and CORS settings to make sure it all runs smoothly.

Environment Variable with a Config Module

Instead of hardcoding environment variables or scattering them around my codebase, I built a dedicated config.py module using Pydantic Settings.

This lets me load configuration from my .env file during local development, use os.environ in Fly.io, and access st.secrets in Streamlit Cloud.

Here is a simplified example that shows the basics of what I did:

from pydantic_settings import BaseSettings
import streamlit as st
import os

class Settings(BaseSettings):
    API_URL: str
    # other secrets...

    class Config:
        env_file = ".env"

def load_settings():
    if "database" in st.secrets:  # running in Streamlit Cloud
        return Settings(API_URL=st.secrets["api"]["API_URL"])
    return Settings()  # fallback to env vars for local/dev/deploy

settings = load_settings()

This made it easy to access settings across the app with settings.API_URL, whether running locally, on Fly.io, or on Streamlit Cloud.

I just had to:

  • Set up a .env file for local use.
  • Add secrets to Streamlit via the Secrets tab in the cloud dashboard under advanced settings.
  • Ensure Fly.io had the same environment variables configured via fly secrets.

CORS Configuration in FastAPI

Since my frontend and backend live on different domains, I ran into a few problems where the browser would block requests. This is where I learned about CORS, or Cross-Origin Resource Sharing. 

In my main.py file, I used CORSMiddleware to explicitly allow requests from the Streamlit frontend. The code looks like this:

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://your-streamlit-app.streamlit.app"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

This simple addition ensured that the frontend could securely access protected API endpoints (like /user-books/).

Without this, the browser will block requests made from your frontend to a different domain (like your backend API), resulting in errors like CORS policy blocked this request.


Deploying the Streamlit Frontend

Once the frontend was ready, I deployed it using Streamlit Cloud. This is a great service as it’s free and is an easy way to deploy the frontend.

I just followed this simple process:

  1. Push my code to GitHub
  2. Link the repo in Streamlit Cloud
  3. Add my environment variables to the Streamlits Secrets in the app’s setting on Streamlit Cloud
  4. Click deploy

That was it!

In just a few minutes my full-stack app was live and usable on the web. It was entirely built in Python using FastAPI and Streamlit.


Final Thoughts

This project was a highlight of my Pybites PDM experience. 🎉

It showed me how powerful it is to build full-stack apps entirely in Python. 🚀

FastAPI gave me a modern, efficient backend, and Streamlit let me ship a clean, interactive frontend in record time.

Another key takeaway was how Docker simplified everything — from local development to deployment on Fly.io.

With containerization, my backend ran the same way everywhere, giving me confidence and consistency.

If you’re a Python dev looking to go full-stack without diving into JavaScript, this approach is perfect.

And if you add Docker into the mix, you’ll be learning modern development workflows that scale well for future projects.

Want a career as a Python Developer but not sure where to start?